【问题标题】:Javascript data structure for fast lookup and ordered looping?用于快速查找和有序循环的 Javascript 数据结构?
【发布时间】:2011-04-02 18:27:47
【问题描述】:

Javascript 中是否存在可用于快速查找(通过键,如关联数组)和有序循环的数据结构或模式?

好吧,现在我使用对象文字来存储我的数据,但我发现 Chrome 在循环属性名称时不会保持顺序。

在 Javascript 中有解决这个问题的通用方法吗?

感谢任何提示。

【问题讨论】:

  • 注意,JavaScript 现在有一个原生的Map,它已经有序循环了。

标签: javascript loops object-literal


【解决方案1】:

自己创建数据结构。将排序存储在结构内部的数组中。将键映射的对象存储在常规对象中。我们称之为OrderedMap,它将有一个映射、一个数组和四个基本方法。

OrderedMap
    map
    _array

    set(key, value)
    get(key)
    remove(key)
    forEach(fn)

function OrderedMap() {
    this.map = {};
    this._array = [];
}

插入元素时,将其添加到数组中所需位置以及对象中。按索引或末尾插入是在 O(1) 中。

OrderedMap.prototype.set = function(key, value) {
    // key already exists, replace value
    if(key in this.map) {
        this.map[key] = value;
    }
    // insert new key and value
    else {
        this._array.push(key);
        this.map[key] = value;
    }
};

删除对象时,将其从数组和对象中删除。如果通过键或值删除,复杂度为 O(n),因为您需要遍历保持排序的内部数组。按索引删除时,复杂度为 O(1),因为您可以直接访问数组和对象中的值。

OrderedMap.prototype.remove = function(key) {
    var index = this._array.indexOf(key);
    if(index == -1) {
        throw new Error('key does not exist');
    }
    this._array.splice(index, 1);
    delete this.map[key];
};

查找将在 O(1) 中进行。从关联数组(对象)中按键检索值。

OrderedMap.prototype.get = function(key) {
    return this.map[key];
};

遍历将被排序并且可以使用任何一种方法。当需要有序遍历时,使用对象(仅限值)创建一个数组并返回它。作为一个数组,它不支持键控访问。另一种选择是要求客户端提供一个回调函数,该函数应该应用于数组中的每个对象。

OrderedMap.prototype.forEach = function(f) {
    var key, value;
    for(var i = 0; i < this._array.length; i++) {
        key = this._array[i];
        value = this.map[key];
        f(key, value);
    }
};

查看 Google 对闭包库中 LinkedMap 的实现,以获取此类类的文档和源代码。

【讨论】:

  • 如何查找 O(1)?如果您使用的不是数组索引,那么 JS 属性解析是 O(n)(它必须通过循环原型链来解析)请参阅 bonsaiden.github.io/JavaScript-Garden上的属性查找@
  • 它必须向上遍历原型链的事实仍然没有使它成为 O(n)。想象一个原型链层次结构有 4 层深,每一层都维护一个散列结构来保存键和值。那么它平均只需要 4 次这样的 O(1) 查找。那仍然是 O(1)。现在,据我所知,ECMAScript 不强制要求实现细节,因此如果他们愿意,可以在 O(n²) 中完成。换句话说,它依赖于实现,但考虑到大多数体面的实现,您可以预期平均 O(1) 次查找。
  • “O(1) 描述了一种算法,无论输入数据集的大小如何,它都将始终在同一时间(或空间)内执行。”即哈希图查找,它不会根据哈希图的大小而改变。此查找将始终根据链中属性和原型的数量而变化。这使它成为 O(n)。它仍然线性发展,但查找从来不是 O(1)。 rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation
  • 您正在讨论两个变量。第一个是哈希图中的项目数,第二个是原型链的深度。原型链的深度(在这个特定场景中)是一个常数,并且不会随着哈希图中项目数量的增加而增长。有了这个,请重新阅读我之前的声明,现在这一切对你来说都是有意义的。如果没有,请查看此article
  • @ginman 不过,这不是 O(n); O 适用于您正在查找的项目数。添加一个常量(原型链)并不会改变这一点,而您自己的引用“[...] 不管输入数据集的位置如何”也是如此。
【解决方案2】:

Chrome 不维护对象字面量中键顺序的唯一实例似乎是键是数字的。

  var properties = ["damsonplum", "9", "banana", "1", "apple", "cherry", "342"];
  var objLiteral = {
    damsonplum: new Date(),
    "9": "nine",
    banana: [1,2,3],
    "1": "one",
    apple: /.*/,
    cherry: {a: 3, b: true},
    "342": "three hundred forty-two"
  }
  function load() {
    var literalKeyOrder = [];
    for (var key in objLiteral) {
      literalKeyOrder.push(key);
    }

    var incremental = {};
    for (var i = 0, prop; prop = properties[i]; i++) {
      incremental[prop] = objLiteral[prop];
    }

    var incrementalKeyOrder = [];
    for (var key in incremental) {
      incrementalKeyOrder.push(key);
    }
    alert("Expected order: " + properties.join() +
          "\nKey order (literal): " + literalKeyOrder.join() +
          "\nKey order (incremental): " + incrementalKeyOrder.join());
  }

在 Chrome 中,上述生成:“1,9,342,damsonplum,banana,apple,cherry”。

在其他浏览器中,它会生成“damsonplum,9,banana,1,apple,cherry,342”。

因此,除非您的密钥是数字,否则我认为即使在 Chrome 中,您也是安全的。如果你的键是数字的,也许只是在它们前面加上一个字符串。

【讨论】:

  • 这是否普遍适用于 V8,因此也适用于 Node.js?
【解决方案3】:

作为 has been noted,如果您的键是数字 您可以在它们前面加上一个字符串以保持顺序。

var qy = {
  _141: '256k AAC',
   _22: '720p H.264 192k AAC',
   _84: '720p 3D 192k AAC',
  _140: '128k AAC'
};

Example

【讨论】:

    猜你喜欢
    • 2014-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多