Wygląda na to, że Vector
spóźniło się na imprezę kolekcjonerską Scali i wszystkie wpływowe posty na blogu już odeszły.
W Javie ArrayList
jest domyślną kolekcją - mogę jej użyć, LinkedList
ale tylko wtedy, gdy przemyślałem algorytm i staram się go zoptymalizować. Czy w Scali powinienem używać Vector
jako domyślnego Seq
, czy próbować ustalić, kiedy List
jest to bardziej odpowiednie?
scala
vector
scala-collections
Duncan McGregor
źródło
źródło
List<String> l = new ArrayList<String>()
blogi Scala. Czy wierzysz, że wszyscy używają Listu, aby uzyskać trwałą kolekcjonerską dobroć - ale czy Vector jest wystarczający do ogólnego zastosowania, że powinniśmy go używać zamiast Listu?List
kiedy piszęSeq()
na REPL.IndexedSeq
.Seq
ma ponad trzy lata. Od wersji Scala 2.11.4 (i wcześniejszych) domyślnym typem konkretnymSeq
jestList
.Odpowiedzi:
Zasadniczo domyślnie jest używane
Vector
. Jest to szybsze niżList
na prawie wszystko i więcej pamięci efektywne dla większych niż trywialne wielkości sekwencji. Zobacz tę dokumentację względnej wydajności Vector w porównaniu do innych kolekcji. Jest kilka wad do zrobieniaVector
. Konkretnie:List
(choć nie tak bardzo, jak mogłoby się wydawać)Kolejnym minusem przed Scalą 2.10 było to, że lepsza była obsługa dopasowania wzorca
List
, ale zostało to naprawione w 2.10 za pomocą uogólnień+:
i:+
ekstraktorów.Istnieje również bardziej abstrakcyjny, algebraiczny sposób podejścia do tego pytania: jaki rodzaj sekwencji masz koncepcyjnie ? Co koncepcyjnie z tym robicie? Jeśli widzę funkcję, która zwraca an
Option[A]
, wiem, że funkcja ma pewne dziury w swojej dziedzinie (a zatem jest częściowa). Możemy zastosować tę samą logikę do kolekcji.Jeśli mam sekwencję typów
List[A]
, skutecznie stwierdzam dwie rzeczy. Po pierwsze, mój algorytm (i dane) ma całkowicie strukturę stosu. Po drugie, twierdzę, że jedyne, co zamierzam zrobić z tą kolekcją, to pełne, O (n) wędrówki. Te dwa naprawdę idą w parze. I odwrotnie, jeśli mam coś typuVector[A]
, jedyne, co zapewniam, to to, że moje dane mają dobrze określoną kolejność i skończoną długość. Zatem twierdzenia są słabszeVector
, a to prowadzi do większej elastyczności.źródło
case head +: tail
lubcase tail :+ head
. Aby dopasować do pustych, możesz to zrobićcase Seq()
i tak dalej. Wszystko, czego potrzebujesz tam jest w API, który jest bardziej uniwersalny niżList
„sList
jest implementowany z pojedynczo połączoną listą.Vector
jest implementowany jak JavaArrayList
.Cóż,
List
może być niewiarygodnie szybko, jeśli algorytm może być realizowany wyłącznie z::
,head
itail
. Miałem lekcję tego bardzo niedawno, kiedy pokonałem Javę,split
generującList
zamiastArray
, i nie byłem w stanie pobić tego niczym innym.Jednak
List
ma zasadniczy problem: to nie działa z algorytmów równoległych. Nie mogę podzielićList
na wiele segmentów ani połączyć ich w efektywny sposób.Istnieją inne rodzaje kolekcji, które znacznie lepiej radzą sobie z równoległością - i
Vector
jest jedną z nich.Vector
ma również doskonałą lokalizację - coList
nie ma - co może być prawdziwym plusem dla niektórych algorytmów.Biorąc wszystko pod uwagę,
Vector
jest to najlepszy wybór, chyba że masz określone względy, które sprawiają, że jedna z pozostałych kolekcji jest lepsza - na przykład możesz wybrać,Stream
czy chcesz leniwej oceny i buforowania (Iterator
jest szybszy, ale nie buforuje), lubList
jeśli algorytm jest naturalnie implementowany wraz z operacjami, o których wspomniałem.Nawiasem mówiąc, to korzystne jest użycie
Seq
lubIndexedSeq
jeśli nie chcesz kawałek specyficzną API (takich jakList
„s::
), albo nawetGenSeq
czyGenIndexedSeq
jeśli algorytm może być prowadzony równolegle.źródło
Vector
jest to niezmienna struktura danych w Scali?Niektóre stwierdzenia tutaj są mylące lub nawet błędne, szczególnie idea niezmienności. Wektor w Scali przypomina coś w rodzaju ArrayList. Zarówno lista, jak i wektor są niezmiennymi, trwałymi (tzn. „Tanio, aby otrzymać zmodyfikowaną kopię”) strukturami danych. Nie ma rozsądnego domyślnego wyboru, ponieważ może to być zmienne struktury danych, ale zależy to raczej od tego, co robi twój algorytm. Lista jest pojedynczo połączoną listą, podczas gdy Vector jest liczbą całkowitą 32, tj. Jest rodzajem drzewa wyszukiwania z węzłami stopnia 32. Korzystając z tej struktury, Vector może zapewnić dość popularne operacje dość szybko, tj. W O (log_32 ( n)). Działa to w przypadku prepend, append, update, random access, dekompozycji w głowie / ogonie. Iteracja w kolejności sekwencyjnej jest liniowa. Z drugiej strony lista zapewnia po prostu liniową iterację i stały czas wyprzedzania, rozkład głowy / ogona.
Może to wyglądać tak, jakby Vector był dobrym zamiennikiem listy w prawie wszystkich przypadkach, ale poprzedzanie, dekompozycja i iteracja są często kluczowymi operacjami na sekwencjach w programie funkcjonalnym, a stałe tych operacji są (znacznie) wyższe dla wektora z powodu do bardziej skomplikowanej struktury. Zrobiłem kilka pomiarów, więc iteracja jest około dwa razy szybsza dla list, prepend jest około 100 razy szybszy na listach, rozkład głowy / ogona jest około 10 razy szybszy na listach, a generowanie z ruchu jest około 2 razy szybsze dla wektorów. (Jest tak prawdopodobnie dlatego, że Vector może przydzielić tablice 32 elementów jednocześnie, gdy budujesz go za pomocą konstruktora zamiast dodawania lub dodawania elementów jeden po drugim).
Więc jakiej struktury danych powinniśmy użyć? Zasadniczo istnieją cztery typowe przypadki:
źródło
W przypadku niezmiennych kolekcji, jeśli chcesz sekwencję, twoją główną decyzją jest, czy użyć znaku
IndexedSeq
a lub aLinearSeq
, który daje różne gwarancje wydajności. IndexedSeq zapewnia szybki losowy dostęp do elementów i szybką operację długości. LinearSeq zapewnia szybki dostęp tylko do pierwszego elementu za pośrednictwemhead
, ale ma również szybkątail
operację. (Zaczerpnięte z dokumentacji Seq.)Dla
IndexedSeq
zwykle wybieraszVector
.Range
siWrappedString
są również indeksowanymi sekwencjami.W
LinearSeq
przypadku zwykle wybieraszList
leniwy odpowiednikStream
. Inne przykłady toQueue
s iStack
s.Tak więc w języku Java,
ArrayList
używane podobnie do ScaliVector
iLinkedList
podobnie do ScaliList
. Ale w Scali wolałbym używać Listy częściej niż Vector, ponieważ Scala ma znacznie lepszą obsługę funkcji, które obejmują przechodzenie przez sekwencję, takich jak mapowanie, składanie, iterowanie itp. Będziesz miał tendencję do używania tych funkcji do manipulowania listą jako cały, a nie losowy dostęp do poszczególnych elementów.źródło
Vector
, że iteracja jest szybsza, ale ktoś musi ją przetestować, aby mieć pewność.Vector
fizycznie istnieją razem w pamięci RAM w grupach po 32, które pełniej mieszczą się w pamięci podręcznej procesora ... więc jest mniej miss pamięci podręcznejW sytuacjach, w których występuje losowy dostęp i losowa mutacja, a
Vector
(lub - jak mówią doktorzy - aSeq
) wydaje się być dobrym kompromisem. Tak sugerują również parametry wydajności .Ponadto
Vector
klasa wydaje się ładnie grać w środowiskach rozproszonych bez dużego powielania danych, ponieważ nie ma potrzeby wykonywania kopiowania przy zapisie dla całego obiektu. (Zobacz: http://akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures )źródło
IndexedSeq
. Co też jestVector
, ale to już inna sprawa.IndexedSeq
implementacjąSeq
.Seq(1, 2, 3)
jestLinearSeq
implementowany przy użyciuList
.Jeśli programujesz niezmiennie i potrzebujesz dostępu losowego, Seq jest właściwą drogą (chyba że potrzebujesz zestawu, co często robisz). W przeciwnym razie lista działa dobrze, z tym wyjątkiem, że jej operacji nie można zrównoleglać.
Jeśli nie potrzebujesz niezmiennych struktur danych, trzymaj się ArrayBuffer, ponieważ jest to Scala odpowiednik ArrayList.
źródło