JavaScriptのunshift()メソッドとPush()メソッドの違いは知っていますが、時間の複雑さの違いは何だと思いますか?
Push()メソッドはO(1)であると仮定します。これは、配列の最後に項目を追加しているだけですが、unshift()メソッドについてはよくわかりません。他のすべての既存の要素を前方に「移動」する必要があり、O(log n)またはO(n)であると仮定しますか?
JavaScript言語仕様は、私が知る限り、これらの関数の時間的な複雑さを強制していません。
配列のようなデータ構造(O(1)ランダムアクセス)をO(1) Push
およびunshift
操作で実装することは確かに可能です。 C++ std::deque
は例です。したがって、C++の両端キューを使用して内部的にJavascript配列を表すJavascript実装では、O(1) Push
およびunshift
操作があります。
ただし、このような時間制限を保証する必要がある場合は、次のように独自にロールする必要があります。
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
私見それはjavascript-engineに依存します...リンクリストを使用する場合、unshiftはかなり安いはずです...
V8の実装に興味がある人のための source です。 unshift
は任意の数の引数を取るため、配列はすべての引数に対応するようにシフトします。
UnshiftImpl
はstart_position
of AT_START
でAddArguments
を呼び出して、これにキックします 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)(要素の数に応じて直線的に拡大縮小します)私が間違っている場合は私を修正してください。
高速シフト解除とプッシュの両方を使用して配列を実装する1つの方法は、Cレベルの配列の中央にデータを配置することです。それがPerlのやり方です、IIRC。
もう1つの方法は、2つの個別のCレベル配列を使用して、Pushが一方に追加し、Shiftを使用せずに他方に追加することです。私が知っている以前のアプローチに対するこのアプローチには、本当の利点はありません。
実装方法に関係なく、Pushまたはunshiftは、内部Cレベルのアレイに十分なスペアメモリがある場合にO(1)時間を要します。それ以外の場合は、少なくとも= O(N)古いデータをメモリの新しいブロックにコピーする時間。