web-dev-qa-db-fra.com

Quand devrais-je choisir Vector en Scala?

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é?

182
Duncan McGregor

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:

  • Les mises à jour en tête sont plus lentes que 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é.

257
Daniel Spiewak

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.

87
Daniel C. Sobral

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. Ranges et WrappedStrings sont également IndexedSeqs.

Pour un LinearSeq, vous choisirez normalement un List ou son équivalent paresseux Stream. D'autres exemples sont Queues et Stacks.

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.

22

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:

  • Nous avons seulement besoin de transformer des séquences en opérations telles que map, filter, fold, etc.: peu importe, nous devrions programmer notre algorithme de manière générique et gagner à accepter des séquences parallèles. Pour les opérations séquentielles, la liste est probablement un peu plus rapide. Mais vous devriez le comparer si vous devez optimiser.
  • Nous avons besoin de beaucoup d’accès aléatoire et de mises à jour différentes, nous devrions donc utiliser des vecteurs, la liste sera extrêmement lente.
  • Nous opérons sur des listes de manière fonctionnelle classique, en les construisant par prépositionnement et en itérant par décomposition récursive: utiliser liste, le vecteur sera plus lent d'un facteur 10-100 ou plus.
  • Nous avons un algorithme de performances critique qui est fondamentalement impératif et qui effectue beaucoup d’accès aléatoire sur une liste, comme par exemple un tri rapide sur place: utilisez une structure de données impérative, par exemple. ArrayBuffer, localement et copiez vos données depuis et vers celui-ci.
20
dth

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 )

2
Debilski

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.

0
Joshua Hartman