Goでリストがあまり使用されないのはなぜですか?


82

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

Golangには確かにlist.List構造体がありますが、それに関するドキュメントはほとんどありません-Go By Exampleでも、私が持っている3冊のGoブック(Summerfield、Chisnal、Balbaert)でも、それらはすべて配列とスライスに多くの時間を費やしています。次に、マップにスキップします。ソースのコード例では、list.List。もほとんどまたはまったく使用されていません。

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

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


9
Pythonのlistタイプは、リンクリストを使用して実装されていないことに注意してください。これは、Goスライスと同様に動作し、場合によってはデータコピーを展開する必要があります。
James Henstridge 2014年

@ JamesHenstridge-適切に記録され、修正されました。
ベクトル

2
C ++はリストを広範囲に使用しません。std::listほとんどの場合、悪い考えです。std::vector一連のアイテムを管理したいものです。同じ理由std::vectorで、Goスライスも推奨されます。
deft_code 2014年

@ deft_code-理解しました。私の質問でstd::vector<T>list、初期化に定数値を必要とせず、動的にサイズ変更できるため、カテゴリに含まれていました。私が質問したとき、Gosliceが同じように使用できるかどうかはわかりませんでした。当時読んだすべてのことで、スライスは「配列のビュー」であり、他のほとんどの言語と同様に、Goのプレーンなバニラ配列であると説明されていました。一定のサイズで宣言する必要があります。(しかし、頭を上げてくれてありがとう。)
ベクター2014年

回答:


83

リストについて考えているときは、ほぼ常に、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)

更新:これは、goチーム自体からのスライスに関するすべてのブログ投稿へのリンクです。これは、スライスと配列、およびスライス内部の関係をうまく説明しています。


1
OK-これが私が探していたものです。スライスについて誤解していました。スライスを使用するために配列を宣言する必要はありません。スライスを割り当てることができ、それによってバッキングストアが割り当てられます。DelphiまたはC ++のストリームに似ています。今、私はスライスについてのすべてのフープラがなぜかを理解しました。
ベクトル

2
@ComeAndGo、「静的」配列を指すスライスを作成するsomethimesは便利なイディオムであることに注意してください。
kostix 2014年

2
@ FelikZ、スライスはバッキング配列に「ビュー」を作成します。多くの場合、関数が操作するデータのサイズは固定されていることを事前に知っています(または、既知のバイト数以下のサイズになります。これはネットワークプロトコルでは非常に一般的です)。あなただけの、あなたの関数内でこのデータを保持してして配列を宣言する可能性があるので、意志としてそれをスライス-などと呼ばれる機能にこれらのスライスを渡す
kostix

52

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

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

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

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

var sl []int = make([]int,len,cap)

このコード割り当てスライスslサイズの、lenの容量を持つcap-lenとはcap、実行時に割り当てることができる変数です。

なぜlist.List無視されるのですか?

list.ListGoでほとんど注目されていない主な理由は次のとおりです。

  • @Nick Craig-Woodの回答で説明されているように、スライスでは実行できないリストでは、多くの場合、より効率的で、よりクリーンでエレガントな構文で実行できることは事実上ありません。たとえば、範囲構成は次のとおりです。

    for i:=range sl {
      sl[i]=i
    }
    

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

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

    私がPythonを放棄してGoに移行した理由の1つは、Pythonが「強く型付けされている」と主張しているにもかかわらず、Pythonの型システムにこの種の弱点があるためです(IMOはそうではありません)。Golist.Listは、C ++vector<T>とPythonから生まれた一種の「雑種」のよう List()であり、Go自体ではおそらく少しずれています。

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

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


@Alok Goは、システムプログラミングを念頭に置いて設計された汎用言語です。強く型付けされています...-彼らはまた、彼らが何について話しているのか見当がつかないのですか?型推論の使用は、GoLangが強く型付けされていないことを意味するものではありません。また、この点を明確に説明しました。アップキャストの場合でも、GoLangでは暗黙的な型変換は許可されていません。(感嘆符はあなたをより正確にするものではありません。子供向けブログ用に保存してください。)
ベクトル

@ Alok-modは私ではなくあなたのコメントを削除しました。誰かが「彼らが何について話しているのかわからない!」と言うだけです。説明と証拠を提供しない限り、役に立たない。さらに、これはプロの会場になるはずなので、感嘆符と誇張を省くことができます-子供向けブログ用に保存してください。問題がある場合は、「A、B、Cが矛盾しているように見えるのに、GoLangがこれほど強く型付けされているとは言えません」とだけ言ってください。おそらく、OPは同意するか、あなたが間違っていると彼らが考える理由を説明します。それは有用で専門的な響きのコメントになるでしょう
Vector

4
静的にチェックされた言語。コードが実行される前にいくつかのルールを適用します。Cのような言語は、プリミティブ型システムを提供します。コードは型チェックを正しく行うことができますが、実行時に爆発します。このスペクトルを続けていくと、Goが得られ、Cよりも優れた保証が得られます。ただし、OCamlのような言語の型システムにはほど遠いです(これもスペクトルの終わりではありません)。「Goはおそらく最も強く型付けされた言語だ」と言うのは明らかに間違っています。開発者が情報に基づいた選択を行えるように、さまざまな言語の安全特性を理解することが重要です。
Alok 2015

4
Goに欠けているものの具体例:ジェネリックスがないため、動的キャストを使用する必要があります。スイッチの完全性をチェックする列挙/機能の欠如は、他の言語が静的な保証を提供できる動的チェックをさらに意味します。
Alok 2015

@ Alok-1 I)おそらく 2)私たちはかなり一般的に使用されている言語について話している。最近のGoはそれほど強力ではありませんが、Goには10545の質問がタグ付けされており、ここではOCamlには3,230の質問があります。3)Go you cite IMOの欠陥は、「強く型付けされた」(コンパイル時のチェックと必ずしも相関しないあいまいな用語)とはあまり関係がありません。4)「重要です..」-申し訳ありませんが、それは意味がありません-誰かがこれを読んでいる場合、彼らはおそらくすでにGoを使用しています。Goが自分に適しているかどうかを判断するために、この回答を使用している人はいないと思います。IMO、あなたは...について「深く気にする」ことがより重要な何かを見つける必要があります
ベクトル

11

それらについて言ってあまりありませんので、私はそれの思いcontainer/listパッケージはむしろ自明である一度あなたが一般的なデータを扱うためのチーフ囲碁イディオムであるものを吸収しました。

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

移動において、container/listタイプの記憶値interface{}(または値に含まれる値の型情報への1つ、およびポインタ:ポインタの組を格納する型により他の(実際の)の値を表現することができる特別なタイプでありますサイズがポインタのサイズ以下の場合は、直接値を指定します)。したがって、リストに要素を追加する場合は、型の関数パラメーターがinterface{}任意の型の値を受け入れるので、それを行うだけです。ただし、リストから値を抽出し、それらの実際の型をどのように処理するかは、型をアサートするか、型を切り替える必要があります。どちらのアプローチも、本質的に同じことを行うための異なる方法です。

ここから取った例を次に示します

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元の挿入された値の型として型アサートします。

タイプアサーションとタイプスイッチについては、「EffectiveGo」またはその他の紹介書で読むことができます。container/listパッケージのドキュメント要約すべてのメソッドリストのサポート。


Goリストは他のリストやベクターのようには機能しないため、インデックスを作成することはできません(List [i])AFAIK(何かが足りないかもしれません...)また、Rangeもサポートしていません。いくつかの説明があります。順調だろう。しかし、型アサーション/スイッチに感謝します-それは私が今まで欠けていたものでした。
ベクトル

@ComeAndGo、はい、範囲をサポートしていません。rangeこれは、組み込み型(配列、スライス、文字列、マップ)にのみ適用できる組み込み言語であるためです。これは、各「呼び出し」またはrange実際には、コンテナーをトラバースするための異なるマシンコードを生成するためです。に適用されます。
kostix 2014年

2
@ComeAndGo、インデックス作成に関して...パッケージのドキュメントから、container/list二重リンクリストを提供していることは明らかです。つまり、インデックス作成はO(N)操作であり(頭から始めて、各要素を尾に向かってトラバースし、カウントする必要があります)、Goの基本的な設計パラダイムの1つには、隠れたパフォーマンスコストがありませんもう1つは、プログラマーに少し余分な負担をかけることです(二重リンクリストのインデックス関数を実装するのは、10行で簡単です)。したがって、コンテナは、その種類に適した「標準的な」操作のみを実装します。
kostix 2014年

@ ComeAndGo、DelphiTListやその他の同類では、その下に動的配列が使用されているため、このようなリストを拡張するの安価でありませんが、インデックスを作成するの安価です。したがって、Delphiの「リスト」は実際には抽象的なリストのように見えますが、実際には配列です。つまり、Goでスライスを使用するのです。私が強調したいのは、Goは、プログラマーから詳細を「隠す」「美しい抽象化」を積み上げることなく、物事を明確にレイアウトしようと努めているということです。Goのアプローチは、データのレイアウト方法とデータへのアクセス方法を明示的に知っているCのアプローチに似ています。
kostix 2014年

3
@ComeAndGo、正確には、長さと容量の両方を持つGoのスライスで何ができるか。
kostix 2014年

6

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

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

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


それでも、スライスはハードコードされたサイズの配列によって戻される必要がありますよね?それは私が好きではないものです。
ベクトル

3
スライスのサイズは、プログラムのソースコードにハードコーディングされていません。append()説明したように、操作を通じて動的に拡張できます(データのコピーが必要になる場合があります)。
James Henstridge 2014年


4

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


3

差出人:https//groups.google.com/forum/#!msg / golang-nuts / mPKCoYNwsoU / tLefhE7tQjMJ

それはあなたのリストの要素の数に大きく依存します、
 真のリストとスライスのどちらがより効率的か
 リストの「中央」で多くの削除を行う必要がある場合。

#1
要素が多いほど、スライスの魅力は低下します。 

#2
要素の順序が重要でない場合は、
 スライスを使用するのが最も効率的であり、
 スライス内の最後の要素に置き換えて要素を削除し、
 スライスを再スライスしてlenを1縮小します
 (SliceTricks wikiで説明されているように)

したがって
、スライス
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.

したがって
、スライス
1を使用します。トラバーサルで速度が必要な場合

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.