在 JavaScript 中创建省略索引的数组结构

Create array structure in JavaScript that omits indexing

数组中的索引(维护索引)使得 Array.prototype.shiftArray.prototype.unshift O(N) 而不是 O(1)。

但是,如果我们只想使用 pop() / push() / shift() 和 unshift() 而从不使用索引进行查找,有没有办法实现一个 JavaScript 数组来省略编制索引?

我想不出办法。 我能想到的唯一方法是使用数组,并且只使用 pop() / push() (因为它们是 O(1))...但即使有多个数组,也不确定是否可行。

想要做到这一点 w/o 如果可能的话,一个链表。 我用一个双向链表实现了一个解决方案,但想知道是否可以这样做这 w/o 一个链表。

最终目标: 尝试创建一个 FIFO 队列,其中所有操作都在恒定时间内进行,而不使用链表。

如果您要存储的数据是原始数据(字符串、整数、浮点数或原始数据的组合),您可以使用 JavaScript TypedArray,将其转换为适当的类型化数组视图,用数据加载它,然后自己跟踪偏移量。

在您的示例中,popshiftunshift 都可以通过 incrementing/decrementing 整数索引来实现。 push 比较难,因为 TypedArray 是固定大小的:如果 ArrayBuffer 已满,只有两个选择:截断数据,或者分配一个新的类型化数组,因为 JS 不能存储指针。

如果要存储同类对象(它们具有相同的属性),则可以使用不同的视图和偏移量将每个值保存到 TypedArray 中以模仿 C 结构(参见 MDN 示例),然后使用 JS 函数从 TypedArray 到 serialize/unserialize 它们,基本上将数据从二进制表示转换为成熟的 JS 对象。

用整数索引的 ES2015 Map 怎么样?

让我们调用地图myFIFOMap

您将 firstlast 整数成员保留为您的 FIFO class 的一部分。将它们都从零开始。

每次你想push()进入你的FIFO队列,你调用myFIFOMap.set(++last,item)pop() 看起来像:

const item = myFIFOMap.get(first); 
myFIFOMap.delete(first++); 
return item;

应该是 O(1) 来推送或弹出。

不要忘记检查边界条件(例如,当 first===last 时不要让它们 pop())。

鉴于 JavaScript 实际上使用双精度浮点数,您应该能够 运行 ~2^53 objects 通过您的 FIFO,然后再遇到整数精度问题。因此,如果您 运行 每秒通过 FIFO 处理 10,000 个项目,那应该可以使用大约 28,000 年的 运行 时间。

按照@SomeCallMeTim 的回答,我认为这是正确的,我有这个:

export class Queue {

  lookup = new Map<number, any>();
  first = 0;
  last = 0;
  length = 0;
  elementExists = false; // when first === last, and item exists there

  peek() {
    return this.lookup.get(this.first);
  }

  getByIndex(v: number) {
    return this.lookup.get(v);
  }

  getLength() {
    return this.length;
  }

  pop() {

    const last = this.last;

    if (this.elementExists && this.first === this.last) {
      this.length--;
      this.elementExists = false;
    }
    else if (this.last > this.first) {
      this.length--;
      this.last--;
    }

    const v = this.lookup.get(last);
    this.lookup.delete(last);
    return v;
  }

  shift() {

    const first = this.first;

    if (this.elementExists && this.first === this.last) {
      this.length--;
      this.elementExists = false;
    }
    else if (this.first < this.last) {
      this.length--;
      this.first++;
    }

    const v = this.lookup.get(first);
    this.lookup.delete(first);
    return v;
  }

  push(v: any) {

    this.length++;

    if (this.elementExists && this.first === this.last) {
      this.last++;
    }
    else if (this.first === this.last) {
      this.elementExists = true;
    }
    else {
      this.last++;
    }

    return this.lookup.set(this.last, v);

  }

  enq(v: any) {
    return this.push.apply(this, arguments);
  }

  enqueue(v: any) {
    return this.push.apply(this, arguments);
  }

  deq() {
    return this.shift.apply(this, arguments);
  }

  dequeue() {
    return this.shift.apply(this, arguments);
  }

  unshift(v: any) {

    this.length++;

    if (this.elementExists && this.first === this.last) {
      this.first--;
    }
    else if (this.first === this.last) {
      this.elementExists = true;
    }
    else {
      this.first--;
    }

    return this.lookup.set(this.first, v);
  }

  addToFront(v: any){
    return this.unshift.apply(this,arguments);
  }

  removeAll() {
    return this.clear.apply(this, arguments);
  }

  clear(): void {
    this.length = 0;
    this.elementExists = false;
    this.first = 0;
    this.last = 0;
    this.lookup.clear();
  }

}

外卖:

  1. 事实证明,你可以调用 getByIndex(),正如 Tim 的建议所指出的那样。

  2. 使用 Map 比 POJSO 快约 10%,这可能只是因为使用 POJSO 需要将整数转换为字符串以供查找。

  3. Map 实现比双向链表快大约 20%,因此双向链表并没有慢多少。它可能更慢,主要是因为我们必须为队列中的每个项目创建一个带有 next/prev 指针的容器对象,而使用非链表实现,我们可以在队列中插入基元等

  4. 双向链表允许我们在常数时间内从队列中间remove/insert项;我们不能对非链表实现做同样的事情。

  5. 当对超过 10,000 个元素的数组进行操作时,以上所有项的性能都比普通数组高几个数量级。

我这里有一些常量时间队列实现: https://github.com/ORESoftware/linked-queue

Tim 有一个很好的建议,让 getByIndex() 更容易使用 - 我们可以这样做:

  getByIndex(v: number) {

    if(!Number.isInteger(v)){
      throw new Error('Argument must be an integer.');
    }

    return this.lookup.get(v + this.first);
  }

这样得到队列中的第5个元素,我们需要做的就是:

getByIndex(4);