Коли я повинен вибрати вектор у Scala?


200

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

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


1
Я думаю, що тут я маю на увазі те, що в Java я б створив писати List<String> l = new ArrayList<String>()блоги Scala, якщо ти вважаєш, що всі користуються списком, щоб отримати стійку корисність для колекції - але чи достатньо загального призначення "Вектора", що ми повинні використовувати його на місці списку?
Дункан Макгрегор

9
@Debilski: Мені цікаво, що ти маєш на увазі під цим. Я отримую, Listколи Seq()набираю REPL.
зниклий фактор

1
Хм, ну, так написано в документах. Можливо, це справедливо лише для IndexedSeq.
Дебільські

1
Коментар щодо типового типу бетону за замовчуванням Seqстарше трьох років. Станом на Scala 2.11.4 (і раніше) типовим конкретним типом Seqє List.
Марк Канлас

3
Для випадкового доступу вектор краще. Для доступу до голови, хвоста, список краще. Для об'ємних операцій, таких як карта, фільтр, вектор бажаний, оскільки вектор організований з 32 елементами як фрагмент, тоді як список елементів, організованих з покажчиками один на одного, немає гарантії, що ці елементи близькі один до одного.
johnsam

Відповіді:


280

Як правило, використовувати за замовчуванням Vector. Це швидше, ніж майжеList для всього, і ефективніше пам’яті для послідовностей великих розмірів, ніж тривіальних. Дивіться цю документацію щодо відносної продуктивності Vector порівняно з іншими колекціями. У цьому є деякі недоліки . Конкретно:Vector

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

Ще одним недоліком перед Scala 2.10 було те, що підтримка відповідності зразків була кращою List, але це було виправлено у 2.10 із узагальненими +:та :+екстракторами.

Існує також більш абстрактний, алгебраїчний спосіб підходу до цього питання: яку послідовність ви маєте концептуально ? Також, що ви з цим концептуально робите? Якщо я бачу функцію, яка повертає an Option[A], я знаю, що функція має деякі діри у своїй області (і тому є частковою). Ми можемо застосувати цю саму логіку до колекцій.

Якщо у мене є послідовність типу List[A], я ефективно стверджую дві речі. По-перше, мій алгоритм (і дані) повністю структурований стеком. По-друге, я стверджую, що єдине, що я збираюся зробити з цією колекцією, - це повне, про (n) обходи. Ці двоє справді йдуть рука об руку. І навпаки, якщо у мене є щось типу Vector[A], єдине, що я стверджую, - це те, що мої дані мають чітко визначений порядок і обмежену довжину. Таким чином, твердження слабкіші Vector, і це призводить до його більшої гнучкості.


2
2.10 вже не деякий час, чи відповідність списку все ще краще, ніж вектор?
Тім Готьє

3
Відповідність структури списку вже не краща. Насправді це зовсім навпаки. Наприклад, щоб дістати голову і хвіст можна зробити case head +: tailабо case tail :+ head. Щоб відповідати порожньому, ви можете робити case Seq()і так далі. Все, що вам потрібно, є в API, який є більш універсальним, ніж List'
Kai Sellgren

Listреалізується зі спільно пов'язаним списком. Vectorреалізується щось на зразок Java ArrayList.
Йосія Йодер

6
@JosiahYoder Це реалізовано не що інше, як ArrayList. ArrayList обгортає масив, який динамічно змінює розмір. Вектор - це трие , де ключі є індексами значень.
John Colanduoni

1
Я прошу вибачення. Я збирався в веб-джерелі, яке було неясним щодо деталей. Чи варто виправити своє попереднє твердження? Або це погана форма?
Йосія Йодер

93

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

Однак Listє принципова проблема: вона не працює з паралельними алгоритмами. Я не можу ефективно розділити Listна кілька сегментів або об'єднати його назад.

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

Отже, все, що розглядається, Vector- найкращий вибір, якщо ви не маєте конкретних міркувань, які роблять перевагу однієї з інших колекцій - наприклад, ви можете вибрати, Streamякщо ви бажаєте ледачої оцінки та кешування ( Iteratorшвидше, але не кешується), або Listякщо алгоритм, природно, реалізований із зазначеними нами операціями.

До речі, бажано використовувати Seqабо IndexedSeqякщо ви не хочете певного фрагмента API (наприклад, Lists ::), або навіть, GenSeqабо GenIndexedSeqякщо ваш алгоритм можна запускати паралельно.


3
Дякую за відповідь. Що ви маєте на увазі під "має велику місцевість"?
Нгок Дао

10
@ngocdaothanh Це означає, що дані групуються між собою в пам’яті, що підвищує ймовірність того, що дані будуть знаходитися в кеші, коли вам це потрібно.
Даніель К. Собрал

1
@ user247077 Так, Списки можуть перемогти Векторів у виконанні, враховуючи деталі, які я згадав. І не всі дії векторів амортизуються O (1). Насправді, на незмінних структурах даних (це так), альтернативні вставки / вилучення в будь-якому кінці взагалі не амортизуються. У цьому випадку кеш марний, оскільки ви завжди копіюєте вектор.
Даніель К. Собрал

1
@ user247077 Можливо, ви не знаєте, що Vectorце незмінна структура даних у Scala?
Даніель К. Собрал

1
@ user247077 Це набагато складніше, ніж це, включаючи деякі внутрішньо змінені речі, щоб зробити додавання дешевше, але коли ви використовуєте його як стек, що є незмінним списком оптимального сценарію, ви все одно маєте однакові характеристики пам'яті пов'язаного списку, але зі значно більшим профілем розподілу пам'яті.
Даніель К. Собрал

29

Деякі з тверджень тут заплутані або навіть неправильні, особливо думка про те, що непорушний. Вектор у Scala - це щось на зразок ArrayList. Список і вектор є незмінними, стійкими (тобто "дешевими для отримання зміненої копії") даних. Немає розумного вибору за замовчуванням, оскільки це може бути для змінних структур даних, але це, скоріше, залежить від того, що робить ваш алгоритм. Список - це окремо пов'язаний список, тоді як вектор - це ціле число трие базової 32, тобто це своєрідне дерево пошуку з вузлами ступеня 32. Використовуючи цю структуру, Вектор може надавати найбільш поширені операції досить швидко, тобто в O (log_32 ( п)). Це працює для додавання, додавання, оновлення, випадкового доступу, розкладання в голові / хвості. Ітерація в послідовному порядку лінійна. Список, з іншого боку, просто забезпечує лінійну ітерацію та постійне збільшення часу, розкладання в голові / хвості.

Це може виглядати так, як ніби Вектор майже є гарною заміною списку майже у всіх випадках, але препендація, розкладання та ітерація часто є найважливішими операціями над послідовностями у функціональній програмі, а константи цих операцій (набагато) вищі за вектор до її більш складної структури. Я зробив кілька вимірювань, тому ітерація приблизно вдвічі швидша для списку, препендація приблизно 100 разів швидша за списками, розкладання в голові / хвості приблизно в 10 разів швидше за списками, а генерація з траверсу - приблизно в 2 рази швидше для векторів. (Це, мабуть, тому, що вектор може виділяти масиви з 32 елементів одночасно, коли ви збираєте його за допомогою конструктора замість того, щоб попередньо додавати або додавати елементи по черзі).

Отже, яку структуру даних ми повинні використовувати? В основному, є чотири поширені випадки:

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

24

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

Як IndexedSeqправило, ви вибрали б Vector. Ranges і WrappedStrings також є IndexedSeqs.

Для LinearSeqвас, як правило, вибираєте Listабо його ледачий еквівалент Stream. Інші приклади - Queues і Stacks.

Так, з точки зору Java, ArrayListвикористовується як аналогічно Scala Vector, LinkedListтак і Scala List. Але в Scala я схильний використовувати List частіше, ніж вектор, тому що Scala має набагато кращу підтримку функцій, які включають в себе обхід послідовності, наприклад, картографування, складання, ітерацію тощо. Ці функції, як правило, використовують ці функції для маніпулювання списком як цілому, а не випадковому доступу до окремих елементів.


Але якщо ітерація Вектора швидша, ніж у списку, і я також можу скласти карту складки тощо, то, крім деяких спеціалізованих випадків (по суті, всіх тих алгоритмів FP, які спеціалізуються на List), схоже, що Список по суті застарів.
Дункан МакГрегор

@Duncan, де ти чув, що ітерація Вектора швидша? Для початку вам потрібно буде відслідковувати та оновлювати поточний індекс, який вам не потрібен із пов’язаним списком. Я б не назвав перелік функцій «спеціалізованими справами» - це хліб і масло функціонального програмування. Не використовувати їх було б як спробувати запрограмувати Java без циклів for- або while.
Луїджі Плінге

2
Я впевнений Vector, що ітерація проходить швидше, але комусь потрібно це порівняти, щоб бути впевненим.
Даніель Шпієк

Я думаю, що (?) Елементи Vectorфізично існують разом на оперативній пам'яті в групах по 32, які більш повно вписуються в кеш процесора ... так що менше пропуску кешу
richizy

2

У ситуаціях, які передбачають багато випадкового доступу та випадкових мутацій, Vector(або - як кажуть доктори - а Seq), здається, хороший компроміс. Про це також підказують характеристики продуктивності .

Крім того, Vectorклас здається, що він добре грає в розподілених середовищах без особливого дублювання даних, оскільки не потрібно робити копіювати на запис для повного об'єкта. (Див.: Http://akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures )


1
Стільки навчитися ... Що означає "Вектор", що є Seq за замовчуванням? Якщо я напишу Seq (1, 2, 3), я отримую список [Int], а не вектор [Int].
Дункан МакГрегор

2
Якщо у вас є випадковий доступ, використовуйте IndexedSeq. Що також є Vector, але це інша справа.
Даніель К. Собрал

@DuncanMcGregor: вектор - це типовий варіант, IndexedSeqякий реалізується Seq. Seq(1, 2, 3)це LinearSeqреалізована за допомогою List.
pathikrit

0

Якщо ви програмуєте безперервно і вам потрібен випадковий доступ, Seq - це спосіб пройти (якщо ви не хочете встановити, що ви часто робите). В іншому випадку Список працює добре, за винятком того, що його операції не можна паралелізувати.

Якщо вам не потрібні незмінні структури даних, дотримуйтесь ArrayBuffer, оскільки це еквівалент Scala ArrayList.


Я дотримуюся царини незмінних, стійких колекцій. Моя думка, що навіть якщо мені не потрібен випадковий доступ, вектор ефективно замінив список?
Дункан Макгрегор

2
Трохи залежить від випадку використання. Вектори більш врівноважені. Ітерація швидша за список, а випадковий доступ набагато швидший. Оновлення проходять повільніше, оскільки це не лише перелік списків, якщо це не об'ємне оновлення зі складки, яке можна виконати за допомогою конструктора. Однак, я вважаю, що вектор - найкращий вибір за замовчуванням, оскільки він настільки універсальний.
Джошуа Хартман

Мені здається, що в основі мого питання - Вектори настільки хороші, що ми можемо також їх використовувати там, де приклади зазвичай показують Список.
Дункан МакГрегор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.