Когда я должен выбрать Vector в Scala? - PullRequest
182 голосов
/ 03 августа 2011

Кажется, что Vector опоздал на вечеринку коллекций Scala, и все влиятельные посты в блоге уже ушли.

В Java ArrayList является коллекцией по умолчанию - я мог бы использовать LinkedList, но только когда я продумал алгоритм и проявил достаточную осторожность для оптимизации. В Scala я должен использовать Vector в качестве значения по умолчанию Seq или пытаться работать, когда List действительно более уместно?

Ответы [ 6 ]

257 голосов
/ 04 августа 2011

Как правило, по умолчанию используется Vector.Это быстрее, чем List для почти всего и более эффективно для памяти для последовательностей, размер которых превышает тривиальный.См. документацию об относительной производительности Vector по сравнению с другими коллекциями.Есть некоторые недостатки в использовании Vector.В частности:

  • Обновления в голове происходят медленнее, чем List (хотя и не так сильно, как вы думаете)

Другим недостатком до Scala 2.10 было сопоставление с шаблономподдержка была лучше для List, но это было исправлено в 2.10 с обобщенными экстракторами +: и :+.

Существует также более абстрактный, алгебраический способ решения этого вопроса: какую последовательность выполнитьу вас концептуально есть?Кроме того, что вы концептуально делаете с этим?Если я вижу функцию, которая возвращает Option[A], я знаю, что функция имеет некоторые дыры в своей области (и, следовательно, является частичной).Мы можем применить ту же логику к коллекциям.

Если у меня есть последовательность типа List[A], я фактически утверждаю две вещи.Во-первых, мой алгоритм (и данные) полностью структурированы.Во-вторых, я утверждаю, что единственное, что я собираюсь сделать с этой коллекцией, это полные, O (n) обходы.Эти двое действительно идут рука об руку.И наоборот, если у меня есть что-то типа Vector[A], то, что я утверждаю only , это то, что мои данные имеют четко определенный порядок и конечную длину.Таким образом, утверждения слабее с Vector, и это приводит к его большей гибкости.

87 голосов
/ 04 августа 2011

Ну, List может быть невероятно быстрым, если алгоритм может быть реализован исключительно с ::, head и tail.У меня был наглядный урок этого совсем недавно, когда я победил split в Java, сгенерировав List вместо Array, и не смог побить это ни с чем другим.

Однако List имеет фундаментальную проблему: он не работает с параллельными алгоритмами.Я не могу разбить List на несколько сегментов или объединить его обратно эффективным способом.

Существуют другие виды коллекций, которые могут гораздо лучше справляться с параллелизмом - и Vector является одним из них.Vector также имеет отличную локальность - чего нет у List - что может быть реальным плюсом для некоторых алгоритмов.

Итак, учитывая все обстоятельства, Vector - лучший выбор если только у вас нет особых соображений, которые делают одну из других коллекций предпочтительной - например, вы можете выбрать Stream, если хотите ленивую оценку и кэширование (Iterator быстрее, но не кэширует), или List если алгоритм естественным образом реализован с упомянутыми мною операциями.

Кстати, предпочтительнее использовать Seq или IndexedSeq, если вам не нужен определенный кусок API (например, List 's ::), или даже GenSeq или GenIndexedSeq, если ваш алгоритм может работать параллельно.

21 голосов
/ 03 августа 2011

Для неизменяемых коллекций, если вам нужна последовательность, ваше основное решение - использовать IndexedSeq или LinearSeq, которые дают разные гарантии производительности.IndexedSeq обеспечивает быстрый произвольный доступ к элементам и быструю операцию длины.LinearSeq обеспечивает быстрый доступ только к первому элементу через head, но также имеет быструю операцию tail.(Взято из документации Seq.)

Для IndexedSeq вы обычно выбираете Vector.Range s и WrappedString s также являются IndexedSeqs.

Для LinearSeq вы обычно выбираете List или его ленивый эквивалент Stream.Другими примерами являются Queue с и Stack с.

Так что в терминах Java ArrayList используется аналогично Vector Scala, а LinkedList аналогично List Scala.Но в Scala я бы предпочел использовать List чаще, чем Vector, потому что Scala гораздо лучше поддерживает функции, включающие обход последовательности, такие как отображение, свертывание, итерации и т. Д. Вы будете склонны использовать эти функции для управления списком какцелое, а не случайный доступ к отдельным элементам.

20 голосов
/ 31 января 2016

Некоторые из приведенных здесь утверждений сбивают с толку или даже ошибочны, особенно идея о том, что immutable.Vector в Scala - это что-то вроде ArrayList.List и Vector являются неизменяемыми, постоянными (т.е. «дешевыми, чтобы получить измененную копию») структурами данных.Не существует разумного выбора по умолчанию, поскольку они могут быть для изменяемых структур данных, но это скорее зависит от того, что делает ваш алгоритм.List - это односвязный список, а Vector - целочисленное дерево с целым числом 32, т. Е. Это своего рода дерево поиска с узлами степени 32. Используя эту структуру, Vector может предоставлять наиболее распространенные операции достаточно быстро, т. Е. В O (log_32 (п)).Это работает для prepend, append, update, произвольного доступа, декомпозиции в голове / хвосте.Итерация в последовательном порядке является линейной.Список, с другой стороны, просто обеспечивает линейную итерацию и постоянное предварительное добавление, разложение в голове / хвосте.Все остальное занимает в общем линейное время.

Это может выглядеть так, как будто Vector был хорошей заменой для List почти во всех случаях, но prepend, декомпозиция и итерация часто являются критическими операциями над последовательностями в функциональной программе,и константы этих операций (намного) выше для вектора из-за его более сложной структуры.Я провел несколько измерений, поэтому итерация для списка примерно в два раза быстрее, для списков предварительная обработка в списках примерно в 100 раз, для списков разложение в голове / хвосте в списках примерно в 10 раз, а генерация из перемещаемых объектов - для векторов примерно в 2 раза быстрее.(Вероятно, это связано с тем, что Vector может выделять массивы из 32 элементов одновременно, когда вы создаете его с помощью компоновщика, вместо того, чтобы добавлять или добавлять элементы один за другим).Конечно, все операции, которые занимают линейное время в списках, но фактически постоянное время для векторов (как произвольный доступ или добавление), будут чрезмерно медленными в больших списках.

Так какую структуру данных мы должны использовать?По сути, существует четыре распространенных случая:

  • Нам нужно преобразовывать последовательности только с помощью таких операций, как отображение, фильтрация, свертывание и т. Д. В принципе это не имеет значения, мы должны программировать наш алгоритм в общем и даже выиграть отпринимая параллельные последовательности.Для последовательных операций List, вероятно, немного быстрее.Но вы должны сравнить его, если вам нужно оптимизировать.
  • Нам нужно много произвольного доступа и разных обновлений, поэтому мы должны использовать вектор, список будет слишком медленным.
  • Мы работаем со спискамиклассическим функциональным способом, построение их путем добавления и итерации с помощью рекурсивной декомпозиции: используйте список, вектор будет медленнее в 10-100 и более раз.
  • У нас есть алгоритм, критичный к производительности, который в основном необходим и делаетмного произвольного доступа к списку, что-то вроде быстрой сортировки на месте: используйте императивную структуру данных, например, ArrayBuffer, локально и копируйте свои данные с и на него.
2 голосов
/ 03 августа 2011

В ситуациях, которые включают много случайного доступа и случайной мутации, Vector (или - как говорят docs - a Seq) - хороший компромисс. Это также то, что характеристики производительности предлагают.

Кроме того, класс Vector, похоже, прекрасно работает в распределенных средах без большого дублирования данных, поскольку нет необходимости выполнять копирование при записи для всего объекта. (См .: http://akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures)

0 голосов
/ 03 августа 2011

Если вы постоянно программируете и нуждаетесь в произвольном доступе, Seq - это путь (если вы не хотите использовать Set, который вы часто делаете).В противном случае список работает хорошо, за исключением того, что его операции нельзя распараллелить.

Если вам не нужны неизменяемые структуры данных, используйте ArrayBuffer, поскольку это Scala-эквивалент ArrayList.

...