Ich lerne Haskell und lese einige Artikel über Leistungsunterschiede bei Haskell-Listen und (fügen Sie Ihre Sprache ein) Arrays.
Als Lernender benutze ich natürlich nur Listen, ohne über Leistungsunterschiede nachzudenken. Ich habe kürzlich mit der Untersuchung begonnen und zahlreiche Datenstrukturbibliotheken in Haskell gefunden.
Kann jemand bitte den Unterschied zwischen Listen, Arrays, Vektoren, Sequenzen erklären, ohne sich eingehend mit der Theorie der Datenstrukturen in der Informatik zu befassen?
Gibt es auch einige gängige Muster, bei denen Sie eine Datenstruktur anstelle einer anderen verwenden würden?
Gibt es andere Formen von Datenstrukturen, die mir fehlen und die nützlich sein könnten?
Antworten:
Listet Rock auf
Die mit Abstand freundlichste Datenstruktur für sequentielle Daten in Haskell ist die Liste
Listen geben Ihnen ϴ (1) Nachteile und Mustervergleich. Die Standardbibliothek, und dass das Vorspiel Materie, ist voll von nützlichen Liste Funktionen , die sollte Streu Code (
foldr
,map
,filter
). Listen sind persistent , auch bekannt als rein funktional, was sehr schön ist. Haskell-Listen sind nicht wirklich "Listen", weil sie koinduktiv sind (andere Sprachen nennen diese Streams)wunderbar arbeiten. Unendliche Datenstrukturen rocken.
Listen in Haskell bieten eine Schnittstelle ähnlich wie Iteratoren in imperativen Sprachen (wegen Faulheit). Es macht also Sinn, dass sie weit verbreitet sind.
Andererseits
Das erste Problem bei Listen ist, dass das Indizieren in Listen
(!!)
ϴ (k) Zeit benötigt, was ärgerlich ist. Anhänge können auch langsam sein++
, aber das faule Bewertungsmodell von Haskell bedeutet, dass diese als vollständig amortisiert behandelt werden können, wenn sie überhaupt auftreten.Das zweite Problem bei Listen besteht darin, dass sie eine schlechte Datenlokalität aufweisen. Bei realen Prozessoren treten hohe Konstanten auf, wenn Objekte im Speicher nicht nebeneinander angeordnet sind. In C ++
std::vector
gibt es also einen schnelleren "Snoc" (Objekte am Ende setzen) als jede mir bekannte reine verknüpfte Listendatenstruktur, obwohl dies keine persistente Datenstruktur ist, die so weniger freundlich ist als Haskells Listen.Das dritte Problem bei Listen ist, dass sie eine schlechte Raumeffizienz aufweisen. Bündel zusätzlicher Zeiger erhöhen Ihren Speicher (um einen konstanten Faktor).
Sequenzen sind funktionsfähig
Data.Sequence
basiert intern auf Fingerbäumen (ich weiß, das wollen Sie nicht wissen), was bedeutet, dass sie einige schöne Eigenschaften habenData.Sequence
ist eine vollständig persistente Datenstruktur.Data.Sequence
ist höchstens eine Konstante langsamer.Auf der anderen Seite
Data.Sequence
wird nicht viel für das Problem der Datenlokalität getan und funktioniert nur für endliche Sammlungen (es ist weniger faul als Listen).Arrays sind nichts für schwache Nerven
Arrays sind eine der wichtigsten Datenstrukturen in CS, passen aber nicht sehr gut in die faule reine Funktionswelt. Arrays bieten ϴ (1) Zugriff auf die Mitte der Sammlung und außergewöhnlich gute Datenlokalität / konstante Faktoren. Aber da sie nicht sehr gut in Haskell passen, sind sie ein Schmerz zu benutzen. In der aktuellen Standardbibliothek gibt es tatsächlich eine Vielzahl verschiedener Array-Typen. Dazu gehören vollständig persistente Arrays, veränderbare Arrays für die E / A-Monade, veränderbare Arrays für die ST-Monade und nicht verpackte Versionen der oben genannten. Weitere Informationen finden Sie im Haskell-Wiki
Vektor ist ein "besseres" Array
Das
Data.Vector
Paket bietet alle Array-Vorteile in einer übergeordneten und saubereren API. Sofern Sie nicht wirklich wissen, was Sie tun, sollten Sie diese verwenden, wenn Sie eine Array-ähnliche Leistung benötigen. Natürlich gelten immer noch einige Einschränkungen - veränderbare Array-ähnliche Datenstrukturen spielen in reinen faulen Sprachen einfach nicht gut. Trotzdem möchten Sie manchmal diese O (1) -Leistung undData.Vector
geben sie Ihnen in einem verwendbaren Paket.Sie haben andere Möglichkeiten
Wenn Sie nur Listen möchten, die am Ende effizient eingefügt werden können, können Sie eine Differenzliste verwenden . Das beste Beispiel für Listen, die die Leistung
[Char]
beeinträchtigen , stammt in der Regel, als die der Vorspiel als Alias giltString
.Char
Listen sind praktisch, laufen jedoch in der Regel 20-mal langsamer als C-Zeichenfolgen. Sie können sie also gerneData.Text
oder sehr schnell verwendenData.ByteString
. Ich bin mir sicher, dass es andere sequenzorientierte Bibliotheken gibt, an die ich momentan nicht denke.Fazit
90 +% der Zeit, in der ich eine sequentielle Sammlung in Haskell-Listen benötige, sind die richtige Datenstruktur. Listen sind wie Iteratoren. Funktionen, die Listen verwenden, können mit den mitgelieferten Funktionen problemlos mit jeder dieser anderen Datenstrukturen verwendet werden
toList
. In einer besseren Welt wäre das Vorspiel vollständig parametrisch, welchen Containertyp es verwendet, aber derzeit wird[]
die Standardbibliothek verschmutzt. Es ist also definitiv in Ordnung, Listen (fast) überall zu verwenden.Sie können vollständig parametrische Versionen der meisten Listenfunktionen erhalten (und sind gut geeignet, sie zu verwenden).
Definiert in der Tat
Data.Traversable
eine API, die mehr oder weniger universell für alle "Listen wie" ist.Obwohl Sie gut sein und nur vollständig parametrischen Code schreiben können, sind die meisten von uns dies nicht und verwenden die Liste überall. Wenn Sie lernen, empfehle ich Ihnen dringend, dies auch zu tun.
EDIT: Aufgrund von Kommentaren wurde mir klar, dass ich nie erklärt habe, wann ich
Data.Vector
vs verwenden sollData.Sequence
. Arrays und Vektoren bieten extrem schnelle Indizierungs- und Slicing-Operationen, sind jedoch grundsätzlich vorübergehende (zwingende) Datenstrukturen. Reine funktionale Datenstrukturen mögenData.Sequence
und[]
lassen neue Werte aus alten Werten effizient erzeugen , als hätten Sie die alten Werte geändert.ändert die alte Liste nicht und muss sie nicht kopieren. Selbst wenn
oldList
es unglaublich lang ist, wird diese "Modifikation" sehr schnell sein. Ähnlichwird
newValue
anstelle seines 3000-Elements eine neue Sequenz mit einem for erzeugen . Auch hier wird die alte Sequenz nicht zerstört, sondern nur eine neue erstellt. Dies geschieht jedoch sehr effizient, indem O (log (min (k, kn)) verwendet wird, wobei n die Länge der Sequenz und k der von Ihnen geänderte Index ist.Sie können dies nicht einfach mit
Vectors
und tunArrays
. Sie können geändert werden, aber das ist eine wirklich zwingende Änderung, und kann daher nicht im regulären Haskell-Code durchgeführt werden. Das bedeutet, dass Operationen imVector
Paket, die Änderungen vornehmensnoc
undcons
den gesamten Vektor kopieren müssen, alsoO(n)
Zeit brauchen . Die einzige Ausnahme ist, dass Sie die veränderbare Version (Vector.Mutable
) innerhalb derST
Monade (oderIO
) verwenden und alle Ihre Änderungen so vornehmen können, wie Sie es in einer imperativen Sprache tun würden. Wenn Sie fertig sind, "frieren" Sie Ihren Vektor ein, um sich in die unveränderliche Struktur zu verwandeln, die Sie mit reinem Code verwenden möchten.Meiner Meinung nach sollten Sie standardmäßig verwenden,
Data.Sequence
wenn eine Liste nicht geeignet ist. VerwendenData.Vector
Sie diese Option nur, wenn Sie in Ihrem Verwendungsmuster nicht viele Änderungen vornehmen müssen oder wenn Sie eine extrem hohe Leistung innerhalb der ST / IO-Monaden benötigen.Wenn all das Gerede über die
ST
Monade Sie verwirrt: umso mehr Grund, sich an schnell und schön zu haltenData.Sequence
.quelle
[1..]
Liste in Haskell verwenden. Listen können auch für lustige Dinge wie das Zurückverfolgen verwendet werden. Wenn man sie als Kontrollstrukturen betrachtet, hat das wirklich Sinn gemacht, wie sie verwendet werden.Data.Sequence
. Fingerbäume sind eine der beeindruckendsten Erfindungen in der Geschichte des Computing (Guibas sollte wahrscheinlich eines Tages einen Turing-Preis erhalten). SieData.Sequence
sind eine hervorragende Implementierung und verfügen über eine sehr benutzerfreundliche API.import qualified Data.Vector.Unboxed as VU; main = print (VU.cons 'a' (VU.replicate 100 'b'))
Kompiliert zu einer einzigen Zuordnung von 404 Bytes (101 Zeichen) in Core: hpaste.org/65015