Estou aprendendo Haskell e leio alguns artigos sobre diferenças de desempenho das listas Haskell e (insira seu idioma) as matrizes.
Como aprendiz, obviamente, apenas uso listas sem nem pensar na diferença de desempenho. Recentemente, comecei a investigar e encontrei inúmeras bibliotecas de estrutura de dados disponíveis em Haskell.
Alguém pode explicar a diferença entre Listas, Matrizes, Vetores, Sequências sem aprofundar a teoria das estruturas de dados em ciência da computação?
Além disso, existem alguns padrões comuns em que você usaria uma estrutura de dados em vez de outra?
Existem outras formas de estruturas de dados que estão faltando e que podem ser úteis?
Respostas:
Listas de rock
De longe, a estrutura de dados mais amigável para dados seqüenciais em Haskell é a Lista
As listas fornecem cons (1) contras e correspondência de padrões. A biblioteca padrão, e para que o assunto o prelúdio, é cheio de funções de lista úteis que deve ninhada seu código (
foldr
,map
,filter
). As listas são persistentes , também conhecidas como puramente funcionais, o que é muito bom. As listas de Haskell não são realmente "listas" porque são coindutivas (outros idiomas chamam esses fluxos), então coisas comotrabalhar maravilhosamente. Estruturas infinitas de dados.
As listas em Haskell fornecem uma interface semelhante aos iteradores em linguagens imperativas (devido à preguiça). Portanto, faz sentido que eles sejam amplamente utilizados.
Por outro lado
O primeiro problema com as listas é que indexá-las
(!!)
leva ϴ (k), o que é irritante. Além disso, os anexos podem ser lentos++
, mas o modelo de avaliação preguiçoso de Haskell significa que eles podem ser tratados como totalmente amortizados, se acontecerem.O segundo problema com as listas é que elas têm pouca localidade de dados. Processadores reais incorrem em altas constantes quando objetos na memória não são dispostos um ao lado do outro. Portanto, em C ++
std::vector
, o "snoc" é mais rápido (colocando objetos no final) do que qualquer estrutura de dados de lista vinculada pura que eu conheço, embora essa não seja uma estrutura de dados persistente tão menos amigável que as listas de Haskell.O terceiro problema com as listas é que elas têm pouca eficiência de espaço. Muitos ponteiros extras aumentam seu armazenamento (por um fator constante).
Sequências são funcionais
Data.Sequence
é baseado internamente em árvores de dedos (eu sei, você não quer saber disso), o que significa que elas têm algumas propriedades legaisData.Sequence
é uma estrutura de dados totalmente persistente.Data.Sequence
é no máximo uma constante mais lenta.Por outro lado,
Data.Sequence
não faz muito pelo problema da localidade dos dados e funciona apenas para coleções finitas (é menos preguiçoso que as listas)Matrizes não são para os fracos de coração
As matrizes são uma das estruturas de dados mais importantes no CS, mas não se encaixam muito bem no mundo funcional puro e preguiçoso. As matrizes fornecem ϴ (1) acesso ao meio da coleta e excepcionalmente boa localização de dados / fatores constantes. Mas, como eles não se encaixam muito bem em Haskell, eles são uma dor de usar. Na verdade, existem vários tipos diferentes de matrizes na biblioteca padrão atual. Isso inclui matrizes totalmente persistentes, matrizes mutáveis para a mônada de E / S, matrizes mutáveis para a mônada ST e versões sem caixa das opções acima. Para saber mais, confira o wiki do haskell
Vetor é uma matriz "melhor"
O
Data.Vector
pacote fornece toda a qualidade da matriz, em uma API mais limpa e de nível superior. A menos que você realmente saiba o que está fazendo, use-os se precisar de uma matriz como desempenho. Obviamente, algumas advertências ainda se aplicam - array mutável, como estruturas de dados, simplesmente não funcionam bem em linguagens lentas e puras. Ainda assim, às vezes você deseja o desempenho O (1) e oData.Vector
entrega em um pacote utilizável.Você tem outras opções
Se você deseja apenas listas com a capacidade de inserir com eficiência no final, use uma lista de diferenças . O melhor exemplo de lista que estraga o desempenho tende a vir de
[Char]
onde o prelúdio aliasou comoString
.Char
As listas são convenientes, mas tendem a ser executadas na ordem 20 vezes mais lenta que as seqüências C, portanto, fique à vontade para usarData.Text
ou muito rapidamenteData.ByteString
. Tenho certeza de que existem outras bibliotecas orientadas a sequência nas quais não estou pensando agora.Conclusão
Mais de 90% do tempo em que preciso de uma coleção seqüencial nas listas Haskell são a estrutura de dados correta. As listas são como iteradores, funções que consomem listas podem ser facilmente usadas com qualquer uma dessas outras estruturas de dados, usando as
toList
funções fornecidas. Em um mundo melhor, o prelúdio seria totalmente paramétrico quanto ao tipo de contêiner que ele usa, mas atualmente[]
desarruma a biblioteca padrão. Então, usar listas (quase) em todos os lugares é definitivamente bom.Você pode obter versões totalmente paramétricas da maioria das funções da lista (e é nobre usá-las)
De fato,
Data.Traversable
define uma API que é mais ou menos universal em qualquer "lista como".Ainda assim, embora você possa ser bom e escrever apenas códigos totalmente paramétricos, a maioria de nós não é e usa a lista em todo o lugar. Se você está aprendendo, sugiro fortemente que você também.
EDIT: Com base nos comentários eu percebo que eu nunca explicou quando usar
Data.Vector
vsData.Sequence
. Matrizes e vetores fornecem operações de indexação e divisão extremamente rápidas, mas são estruturas de dados fundamentalmente transitórias (imperativas). Estruturas de dados funcionais puras gostamData.Sequence
e[]
permitem produzir eficientemente novos valores a partir de valores antigos, como se você tivesse modificado os valores antigos.não modifica a lista antiga e não precisa copiá-la. Portanto, mesmo que
oldList
seja incrivelmente longa, essa "modificação" será muito rápida. similarmenteproduzirá uma nova sequência com a
newValue
no lugar de seu elemento 3000. Novamente, não destrói a sequência antiga, apenas cria uma nova. Mas isso é feito com muita eficiência, usando O (log (min (k, kn)), em que n é o comprimento da sequência ek é o índice que você modifica.Você não pode fazer isso facilmente com
Vectors
eArrays
. Eles podem ser modificados, mas essa é uma modificação imperativa real e, portanto, não pode ser feita no código Haskell regular. Isso significa operações noVector
pacote que fazem modificaçõessnoc
econs
precisam copiar o vetor inteiro, para levarO(n)
tempo. A única exceção é que você pode usar a versão mutável (Vector.Mutable
) dentro daST
mônada (ouIO
) e fazer todas as suas modificações como faria em uma linguagem imperativa. Quando você termina, "congela" seu vetor para se transformar na estrutura imutável que deseja usar com código puro.Meu sentimento é que você deve usar o padrão
Data.Sequence
se uma lista não for apropriada. UseData.Vector
apenas se o seu padrão de uso não envolver muitas modificações ou se você precisar de desempenho extremamente alto nas mônadas do ST / IO.Se toda essa conversa sobre a
ST
mônada está deixando você confuso: mais um motivo para se ater ao puro rápido e bonitoData.Sequence
.fonte
[1..]
lista em Haskell. As listas também podem ser usadas para coisas divertidas, como retroceder. Pensar nelas como estruturas de controle (mais ou menos) realmente ajudou a entender como elas são usadas.Data.Sequence
. As árvores de dedos são uma das invenções mais impressionantes da história da computação (Guibas provavelmente deve receber um prêmio Turing algum dia) eData.Sequence
é uma excelente implementação e possui uma API muito útil.import qualified Data.Vector.Unboxed as VU; main = print (VU.cons 'a' (VU.replicate 100 'b'))
compila para uma única alocação de 404 bytes (101 caracteres) no núcleo: hpaste.org/65015