Ich bin neu in Go und ziemlich aufgeregt darüber. Aber in allen Sprachen, mit denen ich ausgiebig gearbeitet habe: Delphi, C #, C ++, Python - Listen sind sehr wichtig, da sie im Gegensatz zu Arrays dynamisch in der Größe geändert werden können.
In Golang gibt es zwar eine list.List
Struktur, aber ich sehe nur sehr wenig Dokumentation darüber - ob in Go By Example oder in den drei Go-Büchern, die ich habe - Summerfield, Chisnal und Balbaert - sie alle verbringen viel Zeit mit Arrays und Slices und Fahren Sie dann mit den Karten fort. In Quellcodebeispielen finde ich auch wenig oder gar keine Verwendung list.List
.
Es scheint auch, dass im Gegensatz zu Python Range
für List - großer Nachteil IMO nicht unterstützt wird. Vermisse ich etwas
Slices sind sicherlich nett, aber sie müssen immer noch auf einem Array mit einer fest codierten Größe basieren. Hier kommt List ins Spiel. Gibt es eine Möglichkeit, ein Array / Slice in Go ohne eine fest codierte Arraygröße zu erstellen? Warum wird List ignoriert?
list
Typ nicht mithilfe einer verknüpften Liste implementiert wird: Er verhält sich ähnlich wie ein Go-Slice und erfordert gelegentlich das Kopieren von Datenkopien.std::list
ist fast immer eine schlechte Idee.std::vector
ist das, was Sie eine Folge von Elementen verwalten möchten. Aus den gleichen Gründenstd::vector
wird auch Go Slice bevorzugt.std::vector<T>
wurde in dielist
Kategorie aufgenommen, da es keinen konstanten Wert für die Initialisierung erfordert und die Größe dynamisch geändert werden kann. Als ich die Frage stellte, war mir nicht klar, dass Go'sslice
ähnlich verwendet werden kann - alles, was ich damals las, erklärte, dass ein Slice eine "Ansicht eines Arrays" war und wie in den meisten anderen Sprachen einfache Vanille-Arrays in Go müssen mit einer konstanten Größe deklariert werden. (Aber danke für die Heads-Ups.)Antworten:
Fast immer, wenn Sie an eine Liste denken - verwenden Sie stattdessen ein Slice in Go. Slices werden dynamisch in der Größe geändert. Darunter liegt ein zusammenhängender Speicherabschnitt, dessen Größe sich ändern kann.
Sie sind sehr flexibel, wie Sie sehen werden, wenn Sie die SliceTricks-Wiki-Seite lesen .
Hier ist ein Auszug: -
Update : Hier ist ein Link zu einem Blog-Beitrag über Slices des Go-Teams selbst, in dem die Beziehung zwischen Slices und Arrays sowie Slice-Interna gut erklärt wird.
quelle
Ich habe diese Frage vor einigen Monaten gestellt, als ich anfing, Go zu untersuchen. Seitdem habe ich jeden Tag über Go gelesen und in Go codiert.
Da ich auf diese Frage keine eindeutige Antwort erhalten habe (obwohl ich eine Antwort akzeptiert hatte), werde ich sie jetzt selbst beantworten, basierend auf dem, was ich gelernt habe, seit ich sie gestellt habe:
Ja. Für Slices ist kein fest codiertes Array erforderlich, um
slice
von:var sl []int = make([]int,len,cap)
Dieser Code
sl
weist Slicelen
mit einer Größe voncap
-len
undcap
Variablen zu, die zur Laufzeit zugewiesen werden können.Es scheint, dass die Hauptgründe
list.List
, die in Go wenig Beachtung finden, folgende sind:Wie in der Antwort von @Nick Craig-Wood erläutert wurde, gibt es praktisch nichts, was mit Listen gemacht werden kann, die nicht mit Slices gemacht werden können, oft effizienter und mit einer saubereren, eleganteren Syntax. Zum Beispiel das Bereichskonstrukt:
for i:=range sl { sl[i]=i }
kann nicht mit list verwendet werden - ein C-Stil für die Schleife ist erforderlich. In vielen Fällen muss die Syntax des C ++ - Sammlungsstils für Listen verwendet werden:
push_back
usw.Vielleicht noch wichtiger ist, dass
list.List
es nicht stark typisiert ist - es ist Pythons Listen und Wörterbüchern sehr ähnlich, die es ermöglichen, verschiedene Typen in der Sammlung miteinander zu mischen. Dies scheint dem Go-Ansatz zu widersprechen. Go ist eine sehr stark typisierte Sprache - zum Beispiel müssen implizite Typkonvertierungen, die in Go niemals zulässig sind, selbst ein upCast vonint
bisint64
muss explizit sein. Aber alle Methoden für list.List nehmen leere Schnittstellen - alles geht.Einer der Gründe, warum ich Python aufgegeben und zu Go gewechselt habe, ist diese Art von Schwäche im Python-Typsystem, obwohl Python behauptet, "stark typisiert" zu sein (IMO ist es nicht). Go's
list.List
scheint eine Art "Mischling" zu sein, der aus C ++vector<T>
und Pythons geborenList()
wurde und in Go selbst vielleicht etwas fehl am Platz ist.Es würde mich nicht überraschen, wenn wir irgendwann in nicht allzu ferner Zukunft eine Liste finden. Die Liste in Go ist veraltet, obwohl sie möglicherweise bestehen bleibt, um den seltenen Situationen Rechnung zu tragen, in denen selbst mit guten Entwurfspraktiken ein Problem am besten gelöst werden kann mit einer Sammlung, die verschiedene Typen enthält. Oder vielleicht ist es da, um Entwicklern der C-Familie eine "Brücke" zu bieten, damit sie sich mit Go vertraut machen können, bevor sie die Nuancen von Slices kennenlernen, die nur für Go, AFAIK, gelten. (In mancher Hinsicht scheinen Slices Stream-Klassen in C ++ oder Delphi ähnlich zu sein, aber nicht vollständig.)
Obwohl ich aus einem Delphi / C ++ / Python-Hintergrund stamme, war ich bei meiner ersten Begegnung mit Go
list.List
vertrauter als Go's Slices, da ich mich mit Go besser vertraut gemacht habe. Ich bin zurückgegangen und habe alle meine Listen in Slices geändert. Ich habe noch nichts gefundenslice
und / odermap
erlaube es mir nicht, so dass ich es verwenden musslist.List
.quelle
Ich denke, das liegt daran, dass es nicht viel zu sagen gibt, da das
container/list
Paket ziemlich selbsterklärend ist, wenn Sie die Hauptsprache für die Arbeit mit generischen Daten aufgenommen haben.In Delphi (ohne Generika) oder in C würden Sie Zeiger oder
TObject
s in der Liste speichern und sie dann beim Abrufen aus der Liste auf ihre realen Typen zurücksetzen. In C ++ sind STL-Listen Vorlagen und daher nach Typ parametrisiert, und in C # (heutzutage) sind Listen generisch.container/list
Speichert in Go Werte vom Typ, beiinterface{}
denen es sich um einen speziellen Typ handelt, der Werte eines anderen (realen) Typs darstellen kann, indem ein Paar von Zeigern gespeichert wird: einer auf die Typinfo des enthaltenen Werts und ein Zeiger auf den Wert (oder den Wert direkt, wenn seine Größe nicht größer als die Größe eines Zeigers ist). Wenn Sie also ein Element zur Liste hinzufügen möchten, tun Sie dies einfach, wenn Funktionsparameter vom Typinterface{}
Werte für jeden Typ akzeptieren. Wenn Sie jedoch Werte aus der Liste extrahieren und wissen , was mit ihren realen Typen zu tun ist, müssen Sie sie entweder typisieren oder umschalten - beide Ansätze sind nur unterschiedliche Methoden, um im Wesentlichen dasselbe zu tun.Hier ist ein Beispiel von hier :
package main import ("fmt" ; "container/list") func main() { var x list.List x.PushBack(1) x.PushBack(2) x.PushBack(3) for e := x.Front(); e != nil; e=e.Next() { fmt.Println(e.Value.(int)) } }
Hier erhalten wir den Wert eines Elements mit
e.Value()
und geben ihn dann alsint
Typ des ursprünglich eingefügten Werts ein.Informationen zu Typzusicherungen und Typschaltern finden Sie in "Effective Go" oder einem anderen Einführungsbuch. In
container/list
der Dokumentation des Pakets werden alle unterstützten Methodenlisten zusammengefasst.quelle
range
es sich um eine integrierte Sprache handelt, die nur für integrierte Typen (Arrays, Slices, Strings und Maps) gilt, da jeder "Aufruf" oderrange
tatsächlich einen anderen Maschinencode zum Durchlaufen des Containers erzeugt angewendet.container/list
eine doppelt verknüpfte Liste vorhanden ist. Dies bedeutet, dass die Indizierung eineO(N)
Operation ist (Sie müssen am Kopf beginnen und jedes Element in Richtung Schwanz durchlaufen und zählen), und eines der Eckpfeiler des Go-Designparadigmas besteht darin, keine versteckten Leistungskosten zu verursachen. Ein weiterer Grund ist, dass es in Ordnung ist, dem Programmierer eine kleine zusätzliche Belastung aufzuerlegen (die Implementierung einer Indexierungsfunktion für eine doppelt verknüpfte Liste ist ein Kinderspiel mit 10 Zeilen). Der Container implementiert also nur "kanonische" Operationen, die für seine Art sinnvoll sind.TList
und anderen seiner ilk ein dynamisches Array unter Verwendung erstreckt , um eine solche Liste nicht billig ist , während die Indizierung es ist billig. Während Delphis "Listen" wie abstrakte Listen aussehen, handelt es sich tatsächlich um Arrays - wofür Sie in Go Slices verwenden würden. Was ich hervorheben möchte, ist, dass Go sich bemüht, die Dinge klar zu gestalten, ohne "schöne Abstraktionen" anzuhäufen, die die Details vor dem Programmierer "verbergen". Der Ansatz von Go ähnelt eher dem von C, bei dem Sie explizit wissen, wie Ihre Daten angeordnet sind und wie Sie darauf zugreifen.Beachten Sie, dass Go-Slices über die
append()
integrierte Funktion erweitert werden können. Dies erfordert manchmal das Erstellen einer Kopie des Hintergrundarrays, geschieht jedoch nicht jedes Mal, da Go das neue Array überdimensioniert und ihm eine größere Kapazität als die angegebene Länge verleiht. Dies bedeutet, dass ein nachfolgender Anhängevorgang ohne eine weitere Datenkopie abgeschlossen werden kann.Während Sie am Ende mehr Datenkopien haben als mit gleichwertigem Code, der mit verknüpften Listen implementiert ist, müssen Sie die Elemente in der Liste nicht einzeln zuordnen und die
Next
Zeiger aktualisieren . Für viele Anwendungen bietet die Array-basierte Implementierung eine bessere oder ausreichend gute Leistung, so dass dies in der Sprache hervorgehoben wird. Interessanterweise ist der Standardtyp von Pythonlist
auch Array-gestützt und weist ähnliche Leistungsmerkmale beim Anhängen von Werten auf.Es gibt jedoch Fälle, in denen verknüpfte Listen die bessere Wahl sind (z. B. wenn Sie Elemente am Anfang / in der Mitte einer langen Liste einfügen oder entfernen müssen), weshalb eine Standardbibliotheksimplementierung bereitgestellt wird. Ich denke, sie haben keine speziellen Sprachfunktionen hinzugefügt, um mit ihnen zu arbeiten, da diese Fälle weniger häufig sind als diejenigen, in denen Slices verwendet werden.
quelle
append()
Operation erweitert werden, wie ich erklärt habe (was manchmal eine Datenkopie beinhaltet).Sofern das Slice nicht viel zu oft aktualisiert wird (Löschen, Hinzufügen von Elementen an zufälligen Stellen), bietet die Speicherkontiguität von Slices im Vergleich zu verknüpften Listen eine hervorragende Cache-Trefferquote.
Scott Meyers Vortrag über die Bedeutung des Cache. Https://www.youtube.com/watch?v=WDIkqP4JbkE
quelle
list.List
wird als doppelt verknüpfte Liste implementiert. Array-basierte Listen (Vektoren in C ++ oder Slices in Golang) sind unter den meisten Bedingungen die bessere Wahl als verknüpfte Listen, wenn Sie nicht häufig in die Mitte der Liste einfügen. Die amortisierte Zeitkomplexität für das Anhängen beträgt O (1) sowohl für die Array-Liste als auch für die verknüpfte Liste, obwohl die Array-Liste die Kapazität erweitern und über vorhandene Werte kopieren muss. Array-Listen haben einen schnelleren Direktzugriff, einen geringeren Speicherbedarf und sind vor allem für den Garbage Collector geeignet, da keine Zeiger in der Datenstruktur vorhanden sind.quelle
Von: https://groups.google.com/forum/#!msg/golang-nuts/mPKCoYNwsoU/tLefhE7tQjMJ
So
verwendet Scheibe
1. Wenn die Reihenfolge der Elemente in der Liste ist nicht wichtig, und Sie brauchen nur zu löschen,
verwenden Sie tauscht Liste das Element mit dem letzten Elemente zu löschen, und re-Scheibe (Länge-1)
2. wenn Elemente sind ( was auch immer mehr bedeutet)
There are ways to mitigate the deletion problem -- e.g. the swap trick you mentioned or just marking the elements as logically deleted. But it's impossible to mitigate the problem of slowness of walking linked lists.
So
verwenden Scheibe
1. Wenn Sie benötigen Geschwindigkeit in Traversal
quelle