Pourquoi l'ajout à une liste dans Scala a-t-il une complexité temporelle O (n)?

13

Je viens de lire que le temps d'exécution de l'opération d'ajout pour un List(: +) croît linéairement avec la taille du List.

L'ajout à un Listsemble être une opération assez courante. Pourquoi la manière idiomatique de procéder consiste-t-elle à ajouter les composants et à inverser la liste? Cela ne peut pas non plus être un échec de conception car l'implémentation peut être modifiée à tout moment.

De mon point de vue, le préfixe et l'ajout doivent être O (1).

Y a-t-il une raison légitime à cela?

DPM
la source
2
Dépend de votre définition de «légitime». Scala est fortement impliqué dans les structures de données immuables, les listes anonymes omniprésentes, la composition fonctionnelle, etc. L'implémentation de liste par défaut (sans pointeur mutable supplémentaire vers la queue de liste) fonctionne bien pour ce style. Si vous avez besoin d'une liste plus puissante, au moins il est très facile d'écrire votre propre conteneur qui est presque impossible à distinguer des conteneurs standard.
Kilian Foth
1
Lié au site - Ajout d'un élément à la fin d'une liste dans Scala - il y a un peu de détails sur sa nature. Il semble qu'une liste dans scala soit immuable, vous devez donc la copier, qui est O (N).
Vous pouvez utiliser l'une des nombreuses structures de données mutables disponibles ou des structures de données immuables avec le temps d'ajout O (1) (Vector) fournies par Scala. List[T]suppose que vous l'utilisez comme vous le feriez dans un langage fonctionnel pur - travaillant généralement de la tête avec la déconstruction et les préfixes.
KChaloux
3
La pré-mise en place mettra le prochain pointeur de nœud de la nouvelle tête dans la liste immuable existante - qui ne peut pas changer. C'est O (1).
1
Pour le travail séminal et l'analyse sur le thème général des mesures de la complexité de la structure des données en PF pure, lisez la thèse d' Okasaki qui a été publiée plus tard sous forme de livre. C'est une lecture très appréciée et très bonne pour quiconque apprenant la PF de comprendre comment penser à organiser les données dans la PF. Il est également bien écrit et facile à lire et à suivre, donc un texte de qualité.
Jimmy Hoffa

Réponses:

24

Je vais développer un peu mon commentaire. La List[T]structure des données scala.collection.immutableest optimisée pour fonctionner comme une liste immuable dans un langage de programmation plus purement fonctionnel. Il a des temps de préparation très rapides et il est supposé que vous travaillerez sur la tête pour presque tout votre accès.

Les listes immuables ont des temps de préparation très rapides car elles modélisent leurs listes liées comme une série de "contre-cellules". La cellule définit une valeur unique et un pointeur sur la cellule suivante (style classique de liste à liaison unique):

Cell [Value| -> Nil]

Lorsque vous ajoutez à une liste, vous créez vraiment une seule nouvelle cellule, le reste de la liste existante étant pointé vers:

Cell [NewValue| -> [Cell[Value| -> Nil]]

Parce que la liste est immuable, vous pouvez le faire sans aucune copie réelle . Il n'y a aucun danger que l'ancienne liste change et que toutes les valeurs de votre nouvelle liste deviennent invalides. Cependant, vous perdez la possibilité d'avoir un pointeur modifiable à la fin de votre liste comme compromis.

Cela se prête très bien à un travail récursif sur des listes. Disons que vous avez défini votre propre version de filter:

def deleteIf[T](list : List[T])(f : T => Boolean): List[T] = list match {
  case Nil => Nil
  case (x::xs) => f(x) match {
    case true => deleteIf(xs)(f)
    case false => x :: deleteIf(xs)(f)
  }
}

C'est une fonction récursive qui fonctionne exclusivement depuis le début de la liste, et tire parti de la correspondance de motifs via l'extracteur ::. C'est quelque chose que vous voyez beaucoup dans des langues comme Haskell.

Si vous voulez vraiment des ajouts rapides, Scala propose de nombreuses structures de données mutables et immuables. Du côté mutable, vous pourriez vous pencher sur ListBuffer. Alternativement, à Vectorpartir de scala.collection.immutablea un temps d'ajout rapide.

KChaloux
la source
Maintenant, je comprends! C'est tout à fait logique.
DPM
Je ne connais pas de Scala, mais n'est-ce pas elseune boucle infinie? Je pense que ça devrait être quelque chose comme ça x::deleteIf(xs)(f).
svick
@svick Euh ... oui. Oui, ça l'est. Je l'ai écrit rapidement et je n'ai pas vérifié mon code, car j'avais une réunion pour aller à: p (devrait être corrigé maintenant!)
KChaloux
@Jubbat Parce que headet l' tailaccès à ce type de liste est très rapide - plus rapide que l'utilisation d'une carte ou d'un tableau de hachage - c'est un excellent type pour les fonctions récursives. C'est une des raisons pour lesquelles les listes sont un type de base dans la plupart des langages fonctionnels (par exemple Haskell ou Scheme)
itsbruce
Excellente réponse. J'ajouterais peut-être un TL; DR qui dit simplement "parce que vous devriez pré-ajouter, pas ajouter" (pourrait aider à clarifier les hypothèses de base que la plupart des développeurs ont sur Lists et ajouter / pré-ajouter).
Daniel B