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



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



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

591   6  

6 ответов:

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

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

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

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

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

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

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

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

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

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

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

на IndexedSeq обычно вы выбираете Vector. Ranges и WrappedStrings также IndexedSeqs.

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

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

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

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

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

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

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

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

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

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

Comments

    Ничего не найдено.