web-dev-qa-db-ja.com

JavaScriptでのunshift()とPush()の時間の複雑さ

JavaScriptのunshift()メソッドとPush()メソッドの違いは知っていますが、時間の複雑さの違いは何だと思いますか?

Push()メソッドはO(1)であると仮定します。これは、配列の最後に項目を追加しているだけですが、unshift()メソッドについてはよくわかりません。他のすべての既存の要素を前方に「移動」する必要があり、O(log n)またはO(n)であると仮定しますか?

57
dperitch

JavaScript言語仕様は、私が知る限り、これらの関数の時間的な複雑さを強制していません。

配列のようなデータ構造(O(1)ランダムアクセス)をO(1) Pushおよびunshift操作で実装することは確かに可能です。 C++ std::dequeは例です。したがって、C++の両端キューを使用して内部的にJavascript配列を表すJavascript実装では、O(1) Pushおよびunshift操作があります。

ただし、このような時間制限を保証する必要がある場合は、次のように独自にロールする必要があります。

http://code.stephenmorley.org/javascript/queues/

20
Nemo

Push()は高速です。

js>function foo() {a=[]; start = new Date; for (var i=0;i<100000;i++) a.unshift(1); return((new Date)-start)}
js>foo()
2190
js>function bar() {a=[]; start = new Date; for (var i=0;i<100000;i++) a.Push(1); return((new Date)-start)}
js>bar()
10
54
Shanti

私見それはjavascript-engineに依存します...リンクリストを使用する場合、unshiftはかなり安いはずです...

4
TheHe

V8の実装に興味がある人のための source です。 unshiftは任意の数の引数を取るため、配列はすべての引数に対応するようにシフトします。

UnshiftImplstart_position of AT_STARTAddArgumentsを呼び出して、これにキックします elseステートメント

  // If the backing store has enough capacity and we add elements to the
  // start we have to shift the existing objects.
  Isolate* isolate = receiver->GetIsolate();
  Subclass::MoveElements(isolate, receiver, backing_store, add_size, 0,
                         length, 0, 0);

MoveElements に移動します。

  static void MoveElements(Isolate* isolate, Handle<JSArray> receiver,
                           Handle<FixedArrayBase> backing_store, int dst_index,
                           int src_index, int len, int hole_start,
                           int hole_end) {
    Heap* heap = isolate->heap();
    Handle<BackingStore> dst_elms = Handle<BackingStore>::cast(backing_store);
    if (len > JSArray::kMaxCopyElements && dst_index == 0 &&
        heap->CanMoveObjectStart(*dst_elms)) {
      // Update all the copies of this backing_store handle.
      *dst_elms.location() =
          BackingStore::cast(heap->LeftTrimFixedArray(*dst_elms, src_index))
              ->ptr();
      receiver->set_elements(*dst_elms);
      // Adjust the hole offset as the array has been shrunk.
      hole_end -= src_index;
      DCHECK_LE(hole_start, backing_store->length());
      DCHECK_LE(hole_end, backing_store->length());
    } else if (len != 0) {
      WriteBarrierMode mode = GetWriteBarrierMode(KindTraits::Kind);
      dst_elms->MoveElements(heap, dst_index, src_index, len, mode);
    }
    if (hole_start != hole_end) {
      dst_elms->FillWithHoles(hole_start, hole_end);
    }
  }

また、v8には、配列に含まれる内容に応じて異なる element kinds の概念があります。これもパフォーマンスに影響する可能性があります。

実際にパフォーマンスが何であるかを言うのは難しいです。なぜなら、実際には、どのタイプのエレメントが渡されるか、配列にいくつの穴があるかなどに依存するからです。 unshiftは配列により多くのスペースを割り当てる必要があるため、一般的にはO(N)(要素の数に応じて直線的に拡大縮小します)私が間違っている場合は私を修正してください。

3
aug

高速シフト解除とプッシュの両方を使用して配列を実装する1つの方法は、Cレベルの配列の中央にデータを配置することです。それがPerlのやり方です、IIRC。

もう1つの方法は、2つの個別のCレベル配列を使用して、Pushが一方に追加し、Shiftを使用せずに他方に追加することです。私が知っている以前のアプローチに対するこのアプローチには、本当の利点はありません。

実装方法に関係なく、Pushまたはunshiftは、内部Cレベルのアレイに十分なスペアメモリがある場合にO(1)時間を要します。それ以外の場合は、少なくとも= O(N)古いデータをメモリの新しいブロックにコピーする時間。

2
BenGoldberg