Goで2Dスライスを作成する簡潔な方法は何ですか?


103

私は囲碁のツアーに参加して囲碁を学んでいます。そこでの演習の1つで、を含むdy行とdx列の2Dスライスを作成するように求められますuint8。うまくいく私の現在のアプローチはこれです:

a:= make([][]uint8, dy)       // initialize a slice of dy slices
for i:=0;i<dy;i++ {
    a[i] = make([]uint8, dx)  // initialize a slice of dx unit8 in each of dy slices
}

各スライスを繰り返して初期化するのは冗長すぎると思います。また、スライスの次元が増えると、コードが扱いにくくなります。Goで2D(またはn次元)スライスを初期化する簡潔な方法はありますか?

回答:


148

より簡潔な方法はありません。あなたがしたことは「正しい」方法です。スライスは常に1次元ですが、より高次元のオブジェクトを構築するために構成されている場合があるためです。詳細については、この質問を参照してください。Go:2次元配列のメモリ表現はどうですか

単純化できることの1つは、for range構文を使用することです。

a := make([][]uint8, dy)
for i := range a {
    a[i] = make([]uint8, dx)
}

また、スライスを複合リテラルで初期化すると、「無料」でこれが得られることに注意してください。次に例を示します。

a := [][]uint8{
    {0, 1, 2, 3},
    {4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]

はい、これには限界があるため、一見するとすべての要素を列挙する必要があります。ただし、いくつかのトリックがあります。つまり、すべての値を列挙する必要はなく、スライスの要素タイプのゼロ値でないものだけを列挙する必要があります。詳細については、「golang配列の初期化でキー設定された項目」を参照してください。

たとえば、最初の10個の要素がゼロであるスライスが必要で、次に1andと続く場合、次のよう2に作成できます。

b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]

また、slicesではなく配列を使用すると、非常に簡単に作成できます。

c := [5][5]uint8{}
fmt.Println(c)

出力は次のとおりです。

[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

配列は記述子ではなく値なので、配列の場合、「外部」配列を反復処理して「内部」配列を初期化する必要はありません。詳細については、ブログの記事「配列、スライス(および文字列):「追加」のメカニズム」を参照してください。

Go Playgroundで例を試してください。


配列を使用するとコードが簡略化されるので、それを実行したいと思います。構造体でそれをどのように指定しますか?私は得るcannot use [5][2]string literal (type [5][2]string) as type [][]string in field value私は私がGoはスライスで言っていると思うものに配列を代入しようとします。
エリックリンジー

私はそれを理解し、答えを編集して情報を追加しました。
エリックリンジー

1
@EricLindsey編集は適切ですが、初期化が簡単だからといって配列の使用を奨励したくないので、私はそれを拒否します。Goでは、配列はセカンダリであり、スライスはその先への道です。詳細については、Goで1つの配列を別の配列に追加する最速の方法を教えてください。配列にも場所があります。詳しくは、Goに配列がある理由を
icza

十分に公平ですが、情報にはまだメリットがあると思います。私が編集で説明しようとしていたのは、オブジェクト間で異なる次元の柔軟性が必要な場合は、スライスが適しているということです。一方、情報が厳密に構造化されていて常に同じである場合、配列は初期化が簡単であるだけでなく、より効率的でもあります。編集を改善するにはどうすればよいですか?
エリックリンジー

@EricLindseyすでに他の人に拒否されている別の編集を行ったようです。編集では、配列を使用して要素へのアクセスを高速化することを言っていました。Goは多くのことを最適化しますが、これは当てはまらない場合があり、スライスも同じくらい高速である場合があります。詳細については、「アレイとスライス:アクセス速度」を参照してください。
icza

12

スライスを使用して行列を作成するには、2つの方法があります。それらの違いを見てみましょう。

最初の方法:

matrix := make([][]int, n)
for i := 0; i < n; i++ {
    matrix[i] = make([]int, m)
}

2番目の方法:

matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
    matrix[i] = rows[i*m : (i+1)*m]
}

最初の方法に関しては、連続したmake呼び出しを行っても連続した行列になるとは限らないため、メモリ内で行列を分割することができます。これを引き起こす可能性のある2つのGoルーチンの例を考えてみましょう。

  1. ルーチン#0はmake([][]int, n)、に割り当てられたメモリを取得するために実行されmatrix、0x000から0x07Fまでのメモリを取得します。
  2. 次に、ループを開始して最初の行make([]int, m)を実行し、0x080から0x0FFに取得します。
  3. 2番目の反復では、スケジューラによってプリエンプトされます。
  4. スケジューラはプロセッサをルーチン#1に渡し、実行を開始します。これもmake(独自の目的で)使用し、0x100から0x17F(ルーチン#0の最初の行のすぐ隣)を取得します。
  5. しばらくすると、プリエンプトされ、ルーチン#0が再び実行を開始します。
  6. それは make([]int, m)第2のループ反復に対応し、第二行の0x180から0x1FFに到達します。この時点で、すでに2つの分割された行を取得しています。

2番目の方法では、ルーチンは make([]int, n*m)単一のスライスに割り当てられたすべての行列を取得し、連続性を確保します。その後、各行に対応するサブスライスへの行列ポインターを更新するループが必要です。

上記のコードをGo Playgroundでプレイして、両方の方法を使用して割り当てられたメモリの違いを確認できます。私が使用したことに注意してくださいruntime.Gosched()プロセッサーを譲り、スケジューラーに別のルーチンに強制的に切り替える目的でのみ。

どちらを使用しますか?最初の方法で最悪のケースを想像してください。つまり、各行はメモリ内で別の行の次ではありません。次に、プログラムがマトリックス要素を反復処理する(読み取りまたは書き込みを行う)と、データの局所性が低下するため、2番目の方法に比べてキャッシュミスが増える(レイテンシが長くなる)と考えられます。一方、2番目の方法では、理論的には十分な空きメモリがある場合でも、メモリの断片化(チャンクがメモリ全体に広がる)が原因で、行列に割り当てられた単一のメモリを取得できない場合があります。 。

したがって、メモリの断片化が大量にあり、割り当てられる行列が十分に大きい場合を除いて、常に2番目の方法を使用してデータの局所性を活用する必要があります。


2
golang.org/doc/effective_go.html#slicesは、スライスネイティブの構文を活用して連続メモリ技術を実行するための賢い方法を示しています(たとえば、(i + 1)* mのような式でスライスの境界を明示的に計算する必要がない)
Magnus

0

以前の回答では、初期の長さが不明な場合の状況を考慮していませんでした。この場合、次のロジックを使用して行列を作成できます

items := []string{"1.0", "1.0.1", "1.0.2", "1.0.2.1.0"}
mx := make([][]string, 0)
for _, item := range items {
    ind := strings.Count(item, ".")
    for len(mx) < ind+1 {
        mx = append(mx, make([]string, 0))
    }
    mx[ind] = append(mx[ind], item)

}

fmt.Println(mx)

https://play.golang.org/p/pHgggHr4nbB


1
これが「簡潔な方法」のOP境界内にあるかどうかはわかりません。「各スライスを反復して初期化して冗長にするのは冗長すぎると思います」と述べたからです。
Marcos Canales Mayo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.