Il semble que Vector
soit en retard à la soirée de collecte Scala et que tous les billets de blog influents soient déjà partis.
Dans Java, ArrayList
correspond à la collection par défaut. Je pourrais utiliser LinkedList
, mais uniquement lorsque j’ai réfléchi à un algorithme et que j’ai pris suffisamment de soin pour l’optimiser. Dans Scala, devrais-je utiliser Vector
comme valeur par défaut Seq
ou essayer de déterminer quand List
est plus approprié?
En règle générale, utilisez par défaut Vector
. C’est plus rapide que List
pour presque tout et plus efficace en mémoire pour des séquences de taille plus grande que triviale. Voir ceci documentation de la performance relative de Vector par rapport aux autres collections. Il y a des inconvénients à aller avec Vector
. Plus précisément:
List
(mais pas autant que vous pourriez le penser)Un autre inconvénient avant Scala 2.10) était que la prise en charge de la correspondance de modèle était meilleure pour List
, mais cela a été corrigé dans la version 2.10 avec une généralité +:
et :+
extracteurs.
Il existe également une manière plus abstraite et algébrique d’aborder cette question: quelle sorte de séquence avez-vous conceptuellement ? Aussi, que faites-vous conceptuellement avec? Si je vois une fonction qui retourne un Option[A]
, Je sais que la fonction a des trous dans son domaine (et qu’elle est donc partielle). Nous pouvons appliquer cette même logique aux collections.
Si j'ai une séquence de type List[A]
, J’affirme effectivement deux choses. Premièrement, mon algorithme (et mes données) est entièrement structuré en pile. Deuxièmement, j’affirme que les seules choses que je vais faire avec cette collection sont complètes: des traversées O(n)). Ces deux choses vont vraiment de pair. Inversement, si je avoir quelque chose de type Vector[A]
, la chose seulement que j'affirme est que mes données ont un ordre bien défini et une longueur finie. Ainsi, les assertions sont plus faibles avec Vector
, ce qui conduit à une plus grande flexibilité.
Eh bien, un List
peut être incroyablement rapide si l'algorithme peut être implémenté uniquement avec ::
, head
et tail
. J'ai eu une leçon d'objet de cela très récemment, quand j'ai battu le split
de Java en générant un List
au lieu d'un Array
, et je ne pouvais le battre avec rien d'autre.
Cependant, List
a un problème fondamental: il ne fonctionne pas avec des algorithmes parallèles. Je ne peux pas fractionner un List
en plusieurs segments, ni le concaténer de manière efficace.
Il existe d’autres types de collections qui gèrent beaucoup mieux le parallélisme - et Vector
en fait partie. Vector
possède également une excellente localisation - ce que List
ne possède pas - ce qui peut être un réel plus pour certains algorithmes.
Donc, tout bien considéré, Vector
est le meilleur choix sauf si vous avez des considérations spécifiques qui font que l'une des autres collections est préférable - par exemple, vous pouvez choisir Stream
si vous voulez une évaluation et une mise en cache paresseuses (Iterator
est plus rapide mais ne cache pas), ou List
si l'algorithme est naturellement implémenté avec les opérations que j'ai mentionnées.
Par ailleurs, il est préférable d’utiliser Seq
ou IndexedSeq
à moins que vous ne souhaitiez utiliser un élément spécifique de l’API (tel que List
, ::
), ou même GenSeq
ou GenIndexedSeq
si votre algorithme peut être exécuté en parallèle.
Pour les collections immuables, si vous voulez une séquence, votre décision principale consiste à utiliser un IndexedSeq
ou un LinearSeq
, qui offrent différentes garanties de performance. IndexedSeq fournit un accès aléatoire rapide aux éléments et une opération de longueur rapide. Un LinearSeq fournit un accès rapide uniquement au premier élément via head
, mais possède également une opération rapide tail
. (Extrait de la documentation de Seq.)
Pour un IndexedSeq
, vous choisiriez normalement un Vector
. Range
s et WrappedString
s sont également IndexedSeqs.
Pour un LinearSeq
, vous choisirez normalement un List
ou son équivalent paresseux Stream
. D'autres exemples sont Queue
s et Stack
s.
Donc en Java termes, ArrayList
utilisé de la même manière que Scala's Vector
et LinkedList
de manière similaire à Scala's List
. Mais dans Scala J'aurais tendance à utiliser List plus souvent que Vector, car Scala supporte beaucoup mieux les fonctions qui incluent la traversée de la séquence, comme le mappage, le pliage, l'itération, etc. aura tendance à utiliser ces fonctions pour manipuler la liste dans son ensemble, plutôt que d'accéder aléatoirement à des éléments individuels.
Certaines des déclarations ici sont confuses ou même fausses, en particulier l’idée que immuable.Vector in Scala ressemble à un tableau.Liste. List et Vector sont immuables, persistants (c’est-à-dire "peu coûteux pour obtenir un modifiée ") des structures de données. Il n’existe pas de choix par défaut raisonnable pour les structures de données mutables, mais cela dépend plutôt de ce que fait votre algorithme. List est une liste chaînée, alors que Vector est un entier de base 32, c’est-à-dire qu’il s’agit d’une sorte d’arbre de recherche avec des nœuds de degré 32. En utilisant cette structure, Vector peut fournir les opérations les plus courantes assez rapidement, c’est-à-dire en langage O (log_32 (n)). en tête/queue. L'itération dans l'ordre séquentiel est linéaire. La liste, en revanche, fournit uniquement une itération linéaire et une durée constante, une décomposition en tête/queue. Tout le reste prend en général le temps linéaire.
Cela peut sembler comme si Vector remplaçait bien List dans presque tous les cas, mais préposer, décomposer et itérer sont souvent les opérations cruciales sur les séquences d'un programme fonctionnel, et les constantes de ces opérations sont (beaucoup) plus élevées pour vector due à sa structure plus compliquée. J'ai fait quelques mesures, de sorte que l'itération est environ deux fois plus rapide pour la liste, le pré-ajout est environ 100 fois plus rapide sur les listes, la décomposition en tête/queue est environ 10 fois plus rapide sur les listes et la génération à partir d'un objet parcourable est environ 2 fois plus rapide pour les vecteurs. (Ceci est probablement dû au fait que Vector peut allouer des tableaux de 32 éléments à la fois lorsque vous le construisez à l'aide d'un générateur au lieu d'ajouter ou d'ajouter des éléments l'un après l'autre). Bien sûr, toutes les opérations qui prennent du temps linéaire sur les listes mais qui, en réalité, prennent du temps constant sur les vecteurs (en accès aléatoire ou en ajout) seront excessivement lentes sur les grandes listes.
Alors, quelle structure de données devrions-nous utiliser? Fondamentalement, il existe quatre cas courants:
Dans les situations impliquant beaucoup d'accès aléatoire et de mutations aléatoires, un Vector
(ou - comme docs dire - un Seq
) semble être un bon compromis. C’est également ce que les caractéristiques de performance suggèrent.
De plus, la classe Vector
semble bien fonctionner dans les environnements distribués sans trop de duplication de données car il n’est pas nécessaire de faire une copie en écriture pour l’objet complet. (Voir: http://akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures )
Si vous programmez immuablement et avez besoin d'un accès aléatoire, Seq est la solution (sauf si vous voulez un ensemble, ce que vous faites souvent). Sinon, List fonctionne bien, sauf que ses opérations ne peuvent pas être parallélisées.
Si vous n'avez pas besoin de structures de données immuables, utilisez ArrayBuffer car c'est l'équivalent Scala équivalent à ArrayList.