Go'da bir 2B dilim oluşturmanın kısa yolu nedir?


111

A Tour of Go'dan geçerek Go öğreniyorum . Oradaki alıştırmalardan biri benden, aşağıdakileri içeren 2B dysatır ve dxsütun dilimi oluşturmamı istiyor.uint8 . İşe yarayan mevcut yaklaşımım şudur:

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
}

Bence her dilimde onu başlatmak için yinelemenin çok ayrıntılı olduğunu düşünüyorum. Ve dilim daha fazla boyuta sahip olsaydı, kod hantal hale gelirdi. Go'da 2B (veya n boyutlu) dilimleri başlatmanın kısa bir yolu var mı?

Yanıtlar:


165

Daha özlü bir yol yok, yaptığınız şey "doğru" yoldur; çünkü dilimler her zaman tek boyutludur ancak daha yüksek boyutlu nesneler oluşturmak için oluşturulabilir. Daha fazla ayrıntı için bu soruya bakın: Git: İki boyutlu dizinin bellek temsili nasıldır .

Bunu basitleştirebileceğiniz bir şey, for rangeyapıyı kullanmaktır :

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

Ayrıca diliminizi bileşik bir değişmez değerle başlatırsanız , bunu "ücretsiz" olarak elde edeceğinizi unutmayın, örneğin:

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

Evet, görünüşe göre tüm unsurları numaralandırmanız gerektiğinden, bunun sınırları vardır; ancak bazı hileler var, yani tüm değerleri numaralandırmanız gerekmiyor, sadece dilimin eleman türünün sıfır değerleri olmayanlar . Bununla ilgili daha fazla ayrıntı için bkz . Golang dizisi başlatmada anahtarlı öğeler .

Örneğin ilk 10 element sıfırlar, ve sonra aşağıda bir dilim istiyorsanız 1ve 2böyle oluşturulabilir:

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

Ayrıca dilimler yerine diziler kullanırsanız, çok kolay bir şekilde oluşturulabileceğini unutmayın:

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

Çıktı:

[[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]]

Diziler söz konusu olduğunda, "dış" diziyi yinelemeniz ve "iç" dizileri başlatmanız gerekmez, çünkü diziler tanımlayıcı değil değerdir. Blog gönderisine bakın Diziler, dilimler (ve dizeler): 'ekleme'nin mekaniği fazla ayrıntı için .

Go Playground'daki örnekleri deneyin .


Bir dizi kullanmak kodu basitleştirdiğinden, bunu yapmak istiyorum. Bunu bir yapıda nasıl belirtebiliriz? Ben olsun cannot use [5][2]string literal (type [5][2]string) as type [][]string in field valueben Git söylüyorum bir dilim olduğunu tahmin ne dizisi atamak çalıştığınızda.
Eric Lindsey

Kendim anladım ve bilgiyi eklemek için cevabı düzenledim.
Eric Lindsey

1
@EricLindsey Düzenlemeniz iyi olsa da, yine de reddedeceğim çünkü ilklendirme daha kolay olduğu için dizilerin kullanımını teşvik etmek istemiyorum. Go'da diziler ikincildir, dilimler gitmenin yoludur. Ayrıntılar için bkz . Go'da bir diziyi diğerine eklemenin en hızlı yolu nedir? Dizilerin de yerleri vardır, ayrıntılar için bkz . Go'da neden diziler var?
icza

Yeterince adil, ancak bilgilerin hala bir değeri olduğuna inanıyorum. Düzenlememle açıklamaya çalıştığım şey, nesneler arasında farklı boyutların esnekliğine ihtiyacınız varsa, o zaman dilimlerin gitmenin yolu olduğuydu. Öte yandan, eğer bilgileriniz katı bir şekilde yapılandırılmışsa ve her zaman aynı olacaksa, dizileri başlatmak sadece daha kolay olmakla kalmaz, aynı zamanda daha verimlidirler. Düzenlemeyi nasıl geliştirebilirim?
Eric Lindsey,

@EricLindsey Görüyorum ki başkaları tarafından zaten reddedilmiş başka bir düzenleme yapmışsın. Düzenlemenizde, daha hızlı eleman erişimine sahip olmak için dizileri kullanmanız gerektiğini söylüyordunuz. Go'nun birçok şeyi optimize ettiğini unutmayın ve durum böyle olmayabilir, dilimlerin aynı hızda olabileceğini unutmayın. Ayrıntılar için bkz.Array vs Slice: erişim hızı .
icza

14

Matris oluşturmak için dilimleri kullanmanın iki yolu vardır. Aralarındaki farklara bir göz atalım.

İlk yöntem:

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

İkinci yöntem:

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

İlk yöntemle ilgili olarak, ardışık makeçağrılar yapmak, bitişik bir matrisle sonuçlanmanızı sağlamaz, bu nedenle matrisi belleğe bölmüş olabilirsiniz. Buna neden olabilecek iki Go rutini içeren bir örnek düşünelim:

  1. 0 yordamı , 0x000'den 0x07F'ye bir bellek parçası alarak make([][]int, n)tahsis edilmiş belleği matrixalmak için çalışır.
  2. Ardından, döngüyü başlatır ve make([]int, m)0x080'den 0x0FF'ye ulaşarak ilk satırı gerçekleştirir.
  3. İkinci yinelemede, planlayıcı tarafından önlenir.
  4. Programlayıcı, işlemciye 1 numaralı rutini verir ve çalışmaya başlar. Bu da make(kendi amaçları için) kullanır ve 0x100'den 0x17F'ye (# 0 yordamının ilk satırının hemen yanında) ulaşır.
  5. Bir süre sonra önleme yapılır ve # 0 rutini tekrar çalışmaya başlar.
  6. make([]int, m)İkinci döngü yinelemesine karşılık gelen işlemi yapar ve ikinci satır için 0x180'den 0x1FF'ye döner. Bu noktada, zaten iki bölünmüş satırımız var.

İkinci yöntemle, yordam, make([]int, n*m)tek bir dilimde tahsis edilen tüm matrisi elde ederek bitişikliği sağlar. Bundan sonra, matris işaretçilerini her satıra karşılık gelen alt bölümlere güncellemek için bir döngü gerekir.

Her iki yöntemi kullanarak atanan bellekteki farkı görmek için Go Playground'da yukarıda gösterilen kodla oynayabilirsiniz . runtime.Gosched()Yalnızca işlemciyi vermek ve zamanlayıcıyı başka bir rutine geçmeye zorlamak amacıyla kullandığımı unutmayın .

Hangisini kullanmalı? İlk yöntemle en kötü durumu düşünün, yani her satır bellekte başka bir satırın yanında değildir. Daha sonra, programınız matris öğelerini yinelerse (bunları okumak veya yazmak için), daha kötü veri yerelliği nedeniyle ikinci yönteme kıyasla muhtemelen daha fazla önbellek kaçırma (dolayısıyla daha yüksek gecikme) olacaktır. Öte yandan, ikinci yöntemle, teorik olarak onun için yeterli boş bellek olsa bile, bellek parçalanması (belleğin her yerine dağılmış yığınlar) nedeniyle, matris için ayrılmış tek bir bellek parçasını elde etmek mümkün olmayabilir. .

Bu nedenle, çok fazla bellek parçalanması olmadığı ve tahsis edilecek matris yeterince büyük olmadığı sürece, veri yerelliğinden yararlanmak için her zaman ikinci yöntemi kullanmak istersiniz.


2
golang.org/doc/effective_go.html#slices , dilime özgü sözdiziminden yararlanarak bitişik bellek tekniğini yapmanın akıllıca bir yolunu gösterir (örneğin, (i + 1) * m gibi ifadelerle dilim sınırlarını açıkça hesaplamaya gerek yoktur)
Magnus
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.