web-dev-qa-db-ja.com

Goでリストが頻繁に使用されないのはなぜですか?

私はGoが初めてで、とても興奮しています。しかし、私が広範囲に使用したすべての言語で、Delphi、C#、C++、Python-リストとは対照的に、配列とは対照的に動的にサイズ変更できるため、非常に重要です。

Golangでは、実際にlist.Liststruct、しかし、それに関するドキュメントはほとんどありません- Go By Example か、私が持っている3つのGoブック(Summerfield、Chisnal、Balbaert)で-配列とスライスに多くの時間を費やしていますその後、マップにスキップします。ソースコードの例では、list.List

また、Pythonとは異なり、Rangeはリストではサポートされていないようです-大きな欠点IMO。何か不足していますか?

スライスは確かにニースですが、ハードコードされたサイズの配列に基づいている必要があります。それがListの出番です。Goでハードコードされた配列サイズなしで配列/ sliceを作成する方法はありますか?リストが無視されるのはなぜですか?

61
Vector

数か月前にGoの調査を始めたときに、この質問をしました。それ以来、私は毎日Goについて読み、Goでコーディングしています。

私はこの質問に対する明確な回答を受け取っていなかったので(1つの回答を受け入れましたが)、私は質問したので、学んだことに基づいて自分で答えます。

ハードコードされた配列サイズなしでGoで配列/スライスを作成する方法はありますか?

はい。スライスは、sliceへのハードコードされた配列を必要としません。

_var sl []int = make([]int,len,cap)
_

このコードは、サイズslのスライスlenを、capの容量で割り当てます-lenおよびcapは、次の場所に割り当てることができる変数ですランタイム。

_list.List_が無視されるのはなぜですか?

Goで_list.List_がほとんど注目されない主な理由は次のとおりです。

  • @Nick Craig-Woodの回答で説明したように、スライスでは実行できないリストでは、ほとんどの場合、より効率的で、よりクリーンでエレガントな構文で実行できることはほとんどありません。たとえば、範囲構成:

    _for i:=range sl {
      sl[i]=i
    }
    _

    リストでは使用できません-Cスタイルのループが必要です。また、多くの場合、リストでC++コレクションスタイルの構文を使用する必要があります:_Push_back_など.

  • おそらくもっと重要なのは、_list.List_は強く型付けされていないことです。これは、Pythonのリストや辞書に非常に似ており、コレクション内でさまざまな型を混ぜることができます。これは、物事に対するGoのアプローチに反するようです。 Goは非常に強く型付けされた言語です。たとえば、Goで暗黙の型変換は許可されません。intから_int64_へのupCastでも明示的にする必要があります。しかし、list.Listのすべてのメソッドは空のインターフェイスを取ります-何でもできます。

    Pythonを放棄してGoに移行した理由の1つは、Pythonの型システムにこのような弱点があるためです。ただし、Python "(IMOではありません。)Go's _list.List_は、C++の_vector<T>_とPythonのList()から生まれた一種の「雑種」であるように見えます。 Go自体に配置します。

それほど遠くない将来のある時点でlistを見つけても、私は驚かないでしょう。ListはGoで非推奨になりましたが、おそらく残るでしょうが、それらのrare状況に対応するために、優れた設計手法を使用しても、さまざまなタイプを保持するコレクションを使用することで問題を解決できます。あるいは、Cファミリの開発者がGoに慣れる前に、Goに特有のスライスのニュアンスを理解するための「橋」を提供することもできます。 (スライスは、いくつかの点でC++またはDelphiのストリームクラスに似ていますが、完全ではありません。)

Delphi/C++/Pythonのバックグラウンドから来ましたが、Goに最初に触れたとき、Goのスライスよりも_list.List_の方が使いやすいことがわかりました。Goに慣れてきたので、すべてのリストを戻し、変更しました。スライスする。 slicemapで許可されていないものはまだ見つかっていないため、_list.List_を使用する必要があります。

43
Vector

リストを考えているときはほぼ常に-Goの代わりにスライスを使用してください。スライスは動的にサイズ変更されます。それらの基礎となるのは、サイズを変更できる連続したメモリスライスです。

SliceTricks wikiページ を読むとわかるように、これらは非常に柔軟です。

ここに抜粋があります:-

コピー

b = make([]T, len(a))
copy(b, a) // or b = append([]T(nil), a...)

切る

a = append(a[:i], a[j:]...)

削除する

a = append(a[:i], a[i+1:]...) // or a = a[:i+copy(a[i:], a[i+1:])]

順序を維持せずに削除する

a[i], a = a[len(a)-1], a[:len(a)-1]

ポップ

x, a = a[len(a)-1], a[:len(a)-1]

押す

a = append(a, x)

Update:こちらは スライスに関するすべてのブログ投稿 goチーム自体からのリンクです。スライスと配列の関係、およびスライス内部の説明。

67
Nick Craig-Wood

_container/list_パッケージは一目瞭然onceであるため、それらについて言うことはあまりないためだと思いますGo汎用データを扱うイディオム。

Delphi(ジェネリックなし)またはCでは、ポインターまたはTObjectsをリストに格納し、リストから取得するときにそれらを実際の型にキャストします。 C++では、STLリストはテンプレートであるため、タイプによってパラメーター化され、C#(最近)のリストは汎用です。

Goでは、_container/list_は、他の(実際の)型の値を表すことができる特別な型である_interface{}_型の値を格納します。これには、含まれる値の型情報へのポインターのペアが格納されます、および値へのポインター(または、サイズがポインターのサイズ以下の場合は直接値)。したがって、リストに要素を追加する場合は、タイプ_interface{}_の関数パラメーターが任意のタイプの値を受け入れるようにするだけです。ただし、リストから値を抽出し、実際の型で何を処理するかは、type-asertまたはtype switchonそれら—両方のアプローチは、本質的に同じことを行うための異なる方法です。

here からの例を以下に示します。

_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))
    }
}
_

ここでは、e.Value()を使用して要素の値を取得し、元の挿入値の型intとして型アサートします。

タイプアサーションおよびタイプスイッチについては、「Effective Go」または他の紹介書で読むことができます。 _container/list_パッケージのドキュメントには、サポートされているすべてのメソッドリストが要約されています。

9
kostix

Goスライスは、append()組み込み関数を介して展開できることに注意してください。これは、バッキング配列のコピーを作成する必要がある場合がありますが、Goは新しい配列のサイズを超過し、報告された長さよりも大きな容量を与えるため、毎回発生することはありません。これは、後続の追加操作が別のデータコピーなしで完了できることを意味します。

リンクリストを使用して実装された同等のコードよりも多くのデータコピーが作成されますが、リスト内の要素を個別に割り当てる必要がなくなり、Nextポインターを更新する必要がなくなります。多くの場合、配列ベースの実装はより良いまたは十分なパフォーマンスを提供するため、言語で強調されていることです。興味深いことに、Pythonの標準のlist型も配列に対応しており、値を追加するときに同様のパフォーマンス特性を持っています。

そうは言っても、リンクされたリストがより良い選択である場合があります(たとえば、長いリストの開始/中間から要素を挿入または削除する必要がある場合)。それが標準ライブラリ実装が提供される理由です。これらのケースは、スライスが使用されるケースよりも一般的ではないため、特別な言語機能を追加していないと思います。

4

スライスが頻繁に更新されない限り(削除、ランダムな場所での要素の追加)、スライスのメモリ連続性はリンクリストと比較して優れたキャッシュヒット率を提供します。

キャッシュの重要性に関するスコット・マイヤーの講演.. https://www.youtube.com/watch?v=WDIkqP4JbkE

3
Manohar

list.Listは、二重にリンクされたリストとして実装されます。配列ベースのリスト(C++のベクター、またはgolangのスライス)は、リストの中央に頻繁に挿入しない場合、ほとんどの条件でリンクリストよりも適切な選択です。アペンドの償却時間の複雑さは、配列リストとリンクリストの両方でO(1)です。配列リストは容量を拡張し、既存の値をコピーする必要があります。メモリフットプリント、さらに重要なことに、データ構造内にポインターがないため、ガベージコレクターに優しい。

3
woodings

から: https://groups.google.com/forum/#!msg/golang-nuts/mPKCoYNwsoU/tLefhE7tQjMJ

 
リスト内の要素の数に大きく依存します。
多くのことを行う必要があるときに、真のリストまたはスライスのどちらがより効率的か
リストの「中央」での削除。
 
#1 
要素が多いほど、スライスの魅力は低くなります。 
 
#2 
要素の順序が重要でない場合、
はスライスを使用し、
要素を削除することが最も効率的です。スライスの最後の要素で置き換え、
スライスを再スライスしてlenを1 
縮小します(SliceTricks wikiで説明)

So
スライスを使用
1。リスト内の要素の順序が重要ではなく、削除する必要がある場合は、
リストを使用して、削除する要素を最後の要素と交換し、(長さ-1)に再スライスします
2。要素がより多い場合(それ以上の意味)


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
スライスを使用
1。横断の速度が必要な場合