web-dev-qa-db-fra.com

complexité temporelle de unshift () vs Push () en Javascript

Je sais quelle est la différence entre les méthodes unshift () et Push () en Javascript, mais je me demande quelle est la différence de complexité temporelle?

Je suppose que pour la méthode Push () est O(1) parce que vous ajoutez simplement un élément à la fin du tableau, mais je ne suis pas sûr de la méthode unshift (), parce que, je supposons que vous devez "déplacer" tous les autres éléments existants vers l'avant et je suppose que c'est O (log n) ou O (n)?

57
dperitch

La spécification du langage JavaScript ne rend pas obligatoire la complexité temporelle de ces fonctions, pour autant que je sache.

Il est certainement possible d'implémenter une structure de données de type tableau (accès aléatoire O (1)) avec les opérations O(1) Push et unshift. C++ std::deque est un exemple. Une implémentation Javascript qui utilise des deques C++ pour représenter les tableaux Javascript en interne aurait donc des opérations O(1) Push et unshift.

Mais si vous devez garantir de tels délais, vous devrez lancer le vôtre, comme ceci:

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

20
Nemo

Push () est plus rapide.

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

à mon humble avis, cela dépend du moteur javascript ... s'il utilisera une liste chaînée, unshift devrait être assez bon marché ...

4
TheHe

Pour les personnes curieuses de l'implémentation de la v8, voici le source . Étant donné que unshift prend un nombre arbitraire d'arguments, le tableau se décale lui-même pour s'adapter à tous les arguments.

UnshiftImpl finit par appeler AddArguments avec un start_position de AT_START qui lui donne un coup de pied else instruction

  // 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);

et le prend dans 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);
    }
  }

Je tiens également à souligner que la v8 a un concept différent element kinds selon ce que contient le tableau. Cela peut également affecter les performances.

Il est difficile de dire quelle est la performance, car en vérité, cela dépend des types d'éléments qui sont passés, du nombre de trous dans le tableau, etc. Si je fouille davantage, je peux peut-être donner une réponse définitive, mais en général, je suppose puisque unshift a besoin d'allouer plus d'espace dans le tableau, en général, vous pouvez supposer que c'est O(N) (sera mis à l'échelle de façon linéaire en fonction du nombre d'éléments) mais quelqu'un s'il vous plaît Corrige moi si je me trompe.

3
aug

Une façon de mettre en œuvre des tableaux avec unshift rapide et Push est de simplement placer vos données au milieu de votre tableau de niveau C. C'est comme ça que Perl le fait, IIRC.

Une autre façon de le faire est d'avoir deux tableaux de niveau C distincts, de sorte que Push s'ajoute à l'un d'eux et unshift s'ajoute à l'autre. À ma connaissance, cette approche ne présente aucun avantage réel par rapport à la précédente.

Indépendamment de la façon dont il est implémenté, un push ou et unshift prendront O(1) temps lorsque le tableau de niveau C interne a suffisamment de mémoire disponible, sinon, quand la réallocation doit être effectuée, au moins = O(N) temps pour copier les anciennes données dans le nouveau bloc de mémoire.

2
BenGoldberg