Я изучаю Haskell и читаю пару статей, касающихся различий в производительности списков Haskell и массивов (вставьте свой язык).
Будучи учеником, я, очевидно, просто использую списки, даже не задумываясь о разнице в производительности. Я недавно начал исследовать и нашел множество библиотек структур данных, доступных в Haskell.
Может кто-нибудь объяснить, пожалуйста, разницу между списками, массивами, векторами, последовательностями, не углубляясь в теорию информатики структур данных?
Кроме того, есть ли какие-то общие шаблоны, в которых вы бы использовали одну структуру данных вместо другой?
Существуют ли какие-либо другие формы структур данных, которые мне не хватает и которые могут быть полезны?
Ответы:
Lists Rock
Безусловно, самая удобная структура данных для последовательных данных в Haskell - это List
Списки дают вам cons (1) минусов и сопоставление с образцом. Стандартная библиотека, а для этого важно , прелюдия, полна полезных функций списка , которые должны помет ваш код (
foldr
,map
,filter
). Списки являются постоянными , то есть чисто функциональными, что очень приятно. Списки на Haskell на самом деле не являются «списками», потому что они являются коиндуктивными (другие языки называют эти потоки), поэтому такие вещи, какработать чудесно. Бесконечные структуры данных рок.
Списки в Haskell предоставляют интерфейс, очень похожий на итераторы в императивных языках (из-за лени). Итак, имеет смысл, что они широко используются.
С другой стороны
Первая проблема со списками состоит в том, что для индексации в них
(!!)
требуется ϴ (k) времени, что раздражает. Кроме того, добавления могут быть медленными++
, но ленивая модель оценки Haskell означает, что они могут рассматриваться как полностью амортизированные, если они вообще происходят.Вторая проблема со списками состоит в том, что они имеют плохую локальность данных. Реальные процессоры получают высокие константы, когда объекты в памяти не расположены рядом друг с другом. Таким образом, в C ++
std::vector
есть более быстрый «snoc» (помещающий объекты в конец), чем любая известная мне структура данных чистого связанного списка, хотя это не является устойчивой структурой данных, поэтому менее дружественной, чем списки на Haskell.Третья проблема со списками состоит в том, что они имеют низкую эффективность использования пространства. Связки дополнительных указателей увеличивают ваше хранилище (постоянным фактором).
Последовательности являются функциональными
Data.Sequence
внутренне основан на пальцах (я знаю, вы не хотите знать это), что означает, что у них есть некоторые хорошие свойстваData.Sequence
это полностью постоянная структура данных.Data.Sequence
самое большее постоянные медленнее.С другой стороны,
Data.Sequence
мало что делает для проблемы локальности данных, а работает только для конечных коллекций (это менее лениво, чем списки)Массивы не для слабонервных
Массивы являются одной из наиболее важных структур данных в CS, но они не очень хорошо вписываются в ленивый чистый функциональный мир. Массивы обеспечивают ϴ (1) доступ к середине коллекции и исключительно хорошую локальность данных / постоянные факторы. Но, поскольку они не очень хорошо вписываются в Haskell, их неудобно использовать. На самом деле в текущей стандартной библиотеке есть множество различных типов массивов. К ним относятся полностью персистентные массивы, изменяемые массивы для монады ввода-вывода, изменяемые массивы для монады ST и неупакованные версии описанного выше. Для более подробной информации ознакомьтесь с Haskell Wiki
Вектор - «лучший» массив
Data.Vector
Пакет предоставляет все благости массива, на более высоком уровне и чистого API. Если вы действительно не знаете, что делаете, вы должны использовать их, если вам нужна производительность, подобная массиву. Конечно, некоторые предостережения все еще применимы - изменяемый массив, такой как структуры данных, просто не воспроизводится на чистых ленивых языках. Тем не менее, иногда вам нужна производительность O (1), иData.Vector
вы получаете ее в пригодном для использования пакете.У вас есть другие варианты
Если вам просто нужны списки с возможностью эффективной вставки в конце, вы можете использовать список различий . Наилучший пример того, как списки портят производительность, как правило, связан с
[Char]
прелюдиейString
.Char
списки удобны, но, как правило, работают в 20 раз медленнее, чем строки C, поэтому не стесняйтесь использоватьData.Text
или очень быстроData.ByteString
. Я уверен, что есть другие ориентированные на последовательность библиотеки, о которых я сейчас не думаю.Вывод
90 +% времени, когда мне нужен последовательный сбор в списках Haskell, являются правильной структурой данных. Списки подобны итераторам, функции, которые используют списки, могут легко использоваться с любой из этих других структур данных, используя
toList
функции, которые они поставляют. В лучшем мире прелюдия была бы полностью параметрической относительно того, какой тип контейнера он использует, но в настоящее время[]
засоряет стандартную библиотеку. Таким образом, использование списков (почти) везде где угодно.Вы можете получить полностью параметрические версии большинства функций списка (и можете использовать их)
Фактически,
Data.Traversable
определяет API, который является более или менее универсальным для любой вещи, подобной списку.Тем не менее, хотя вы можете быть хорошими и писать только полностью параметрический код, большинство из нас не так и используют список повсеместно. Если вы учитесь, я настоятельно рекомендую вам тоже.
РЕДАКТИРОВАТЬ: На основе комментариев я понимаю, что я никогда не объяснял, когда использовать
Data.Vector
противData.Sequence
. Массивы и векторы обеспечивают чрезвычайно быстрые операции индексирования и среза, но являются принципиально переходными (обязательными) структурами данных. Чистые функциональные структуры данных, такие какData.Sequence
и[]
позволяют эффективно создавать новые значения из старых значений, как если бы вы изменили старые значения.не изменяет старый список и не должен копировать его. Так что даже если
oldList
это невероятно долго, эта «модификация» будет очень быстрой. так жесоздаст новую последовательность с
newValue
for вместо 3000 элементов. Опять же, это не разрушает старую последовательность, она просто создает новую. Но он делает это очень эффективно, принимая O (log (min (k, kn)), где n - длина последовательности, а k - индекс, который вы модифицируете.Вы не можете легко сделать это с
Vectors
иArrays
. Они могут быть изменены, но это действительно обязательное изменение, и поэтому не может быть сделано в обычном коде на Haskell. Это означает, что операции вVector
пакете, которые вносят изменения, такие какsnoc
иcons
должны копировать весь вектор, требуютO(n)
времени. Единственное исключение из этого - то, что вы можете использовать изменяемую версию (Vector.Mutable
) внутриST
монады (илиIO
) и делать все ваши модификации так же, как вы делали бы это на императивном языке. Когда вы закончите, вы «заморозите» свой вектор, чтобы превратиться в неизменную структуру, которую вы хотите использовать с чистым кодом.Я чувствую, что вы должны использовать по умолчанию,
Data.Sequence
если список не подходит. ИспользуйтеData.Vector
только в том случае, если ваш шаблон использования не предполагает внесения большого количества изменений или если вам требуется чрезвычайно высокая производительность в монадах ST / IO.Если все эти разговоры о
ST
монаде приводят вас в замешательство, тем больше причин оставаться чистыми, быстрыми и красивымиData.Sequence
.источник
[1..]
список в Haskell. Списки также могут быть использованы для забавных вещей, таких как возврат. Думая о них как о структурах управления (вроде), действительно помогло понять, как они используются.Data.Sequence
. Деревья пальцев - одно из самых удивительных изобретений в истории вычислительной техники (возможно, когда-нибудь Guibas должен получить награду Тьюринга)Data.Sequence
, отличная реализация и очень удобный API.import qualified Data.Vector.Unboxed as VU; main = print (VU.cons 'a' (VU.replicate 100 'b'))
компилируется в одно выделение 404 байта (101 символ) в Core: hpaste.org/65015