Когда я должен выбрать Vector в Scala?

173

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

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

  • 1
    Я предполагаю, что здесь я имею в виду, что в Java я бы создал запись List<String> l = new ArrayList<String>() блогах Scala вы бы поверили, что все используют List для того, чтобы получить постоянную коллекционную ценность - но это Vector Достаточно цели, что мы должны использовать его вместо Листа?
  • 9
    @Debilski: Мне интересно, что вы подразумеваете под этим. Я получаю List когда Seq() в REPL.
Показать ещё 4 комментария
Теги:
vector
scala-collections

6 ответов

241
Лучший ответ

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

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

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

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

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

  • 2
    2.10 уже давно нет, соответствие шаблона List по-прежнему лучше, чем Vector?
  • 3
    Соответствие шаблону списка больше не лучше. На самом деле это совсем наоборот. Например, чтобы получить голову и хвост, можно сделать case head +: tail или case tail :+ head . Чтобы сравнить с пустым, вы можете сделать case Seq() и так далее. Все, что вам нужно, есть в API, который более универсален, чем List
Показать ещё 6 комментариев
78

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

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

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

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

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

  • 2
    Спасибо за ответ. Что вы подразумеваете под "имеет отличную местность"?
  • 8
    @ngocdaothanh Это означает, что данные сгруппированы в памяти, что повышает вероятность того, что данные будут в кеше, когда вам это нужно.
Показать ещё 11 комментариев
19

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

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

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

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

  • 0
    Но если итерация Vector быстрее, чем List, и я также могу отображать сложение и т. Д., То, за исключением некоторых специализированных случаев (по существу, всех тех алгоритмов FP, которые специализированы для List), кажется, что List по существу унаследован.
  • 0
    @Duncan, где ты слышал, что итерация Vector быстрее? Для начала вам нужно отслеживать и обновлять текущий индекс, который вам не нужен, с помощью связанного списка. Я бы не назвал функции списка «специализированными случаями» - они - хлеб с маслом функционального программирования. Не использовать их - все равно что пытаться программировать на Java без циклов for или while.
Показать ещё 2 комментария
14

Некоторые из приведенных здесь инструкций сбивают с толку или даже ошибочны, особенно идея, что immutable.Вектор в Scala является чем-то вроде ArrayList. Список и вектор являются неизменными, постоянными (т.е. "Дешевыми для получения модифицированной копии" ) структурами данных. Разумного выбора по умолчанию не существует, поскольку они могут быть для изменяемых структур данных, но это скорее зависит от того, что делает ваш алгоритм. Список - это одиночный список, в то время как Vector является целым числом-base-32, т.е. Это своего рода дерево поиска с узлами степени 32. Используя эту структуру, вектор может обеспечить наиболее часто используемые операции достаточно быстро, то есть в O (log_32 (n)). Это работает для добавления, добавления, обновления, произвольного доступа, декомпозиции в head/tail. Итерация в последовательном порядке является линейной. Список, с другой стороны, просто обеспечивает линейную итерацию и постоянное добавление времени, декомпозицию в голове/хвосте. Все остальное занимает общее линейное время.

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

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

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

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

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

  • 1
    Так много всего, что нужно узнать ... Что означает вектор как Seq по умолчанию? Если я пишу Seq (1, 2, 3), я получаю List [Int], а не Vector [Int].
  • 2
    Если у вас есть произвольный доступ, используйте IndexedSeq . Это тоже Vector , но это другое дело.
Показать ещё 1 комментарий
0

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

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

  • 0
    Я придерживаюсь области неизменных, постоянных коллекций. Я хочу сказать, что даже если мне не нужен произвольный доступ, эффективно ли Vector заменил List?
  • 2
    Зависит немного от варианта использования. Векторы более сбалансированы. Итерация быстрее, чем список, и произвольный доступ намного быстрее. Обновления происходят медленнее, поскольку это не просто добавление в список, если только это не массовое обновление из фолда, которое может быть выполнено с помощью компоновщика. Тем не менее, я думаю, что Vector - лучший выбор по умолчанию, поскольку он очень универсален.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню