Yineleme sırasında değerleri değiştirme


153

Diyelim ki bu türlere sahibim:

type Attribute struct {
    Key, Val string
}
type Node struct {
    Attr []Attribute
}

ve bunları değiştirmek için düğümümün niteliklerini yinelemek istiyorum.

Yapabilmek isterdim:

for _, attr := range n.Attr {
    if attr.Key == "href" {
        attr.Val = "something"
    }
}

ama attrbir işaretçi olmadığından, bu işe yaramaz ve ben yapmak zorundayım:

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}

Daha basit veya daha hızlı bir yol var mı? Doğrudan işaretçi almak mümkün mü range?

Açıkçası ben sadece yineleme için yapıları değiştirmek istemiyorum ve daha ayrıntılı çözümler çözüm değildir.


2
Yani bir çeşit Array.prototype.forEachJavaScript mi istiyorsunuz ?
Florian Margaine

Bu ilginç bir fikir ve bu bir çözüm olabilirdi, ancak her yinelemede bir işlevi çağıran bir işlevi çağırmak, sunucu tarafı dilinde ağır ve yanlış görünüyor. Ve jeneriklerin eksikliği bunu daha da ağırlaştıracaktı.
Denys Séguret

Dürüst olmak gerekirse, bu kadar ağır olduğunu düşünmüyorum. Bir veya iki işlevi çağırmak çok ucuzdur, bu genellikle derleyicilerin en çok optimize ettiği şeydir. Ben denemek ve onun tasarıyı uygun olup olmadığını görmek için karşılaştırmak.
Florian Margaine

Go jeneriklerden yoksun olduğu için, geçilen fonksiyonun forEachmutlaka bir tür iddia ile başlayacağından korkuyorum . Bundan daha iyi değil attr := &n.Attr[i].
Denys Séguret

Yanıtlar:


152

Hayır, istediğiniz kısaltma mümkün değil.

Bunun nedeni range, değerleri yinelediğiniz dilimden kopyalamasıdır. Aralığı hakkında şartname diyor ki:

Range expression                          1st value             2nd value (if 2nd variable is present)
array or slice  a   [n]E, *[n]E, or []E   index    i  int       a[i]       E

Bu nedenle, aralık a[i]diziler / dilimler için ikinci değeri olarak kullanır , bu da değerin kopyalandığı ve orijinal değerin dokunulmaz olduğu anlamına gelir.

Bu davranış, aşağıdaki kodla gösterilir :

x := make([]int, 3)

x[0], x[1], x[2] = 1, 2, 3

for i, val := range x {
    println(&x[i], "vs.", &val)
}

Kod, aralıktaki değer ve dilimdeki gerçek değer için tamamen farklı bellek konumları yazdırır:

0xf84000f010 vs. 0x7f095ed0bf68
0xf84000f014 vs. 0x7f095ed0bf68
0xf84000f018 vs. 0x7f095ed0bf68

Yapabileceğiniz tek şey, zaten jnml ve peterSO tarafından önerildiği gibi, işaretçiler veya dizin kullanmaktır.


16
Bunu düşünmenin bir yolu, değer atamanın bir kopyaya neden olmasıdır. Val: = x [1] görürseniz, val'in x [1] 'in bir kopyası olması tamamen şaşırtıcı olmazdı. Aralığı özel bir şey yapmak olarak düşünmek yerine, bir aralığın her yinelemesinin dizin ve değer değişkenlerini atayarak başladığını ve kopyalamaya neden olan aralık yerine o atama olduğunu unutmayın.
Andy Davis

Üzgünüm, burada hala biraz kafam karıştı. For döngüsünün 2. değeri bir [i] a[i]ise, for döngüsünden ve a[i]yazdıkça arasındaki fark nedir? Aynı şey gibi görünüyor ama değil, değil mi?
Tiến Nguyễn Hoàng

1
@ TiếnNguyễnHoàng ikinci dönüş değeri olarak rangedöner a[i]. Bu işlem, val = a[i]tarafından yapıldığı gibi range, değerin bir kopyasını oluşturur, böylece herhangi bir yazma işlemi valbir kopyaya uygulanır.
nemo

37

Buna eşdeğer bir şey istiyor gibisiniz:

package main

import "fmt"

type Attribute struct {
    Key, Val string
}
type Node struct {
    Attr []Attribute
}

func main() {

    n := Node{
        []Attribute{
            {"key", "value"},
            {"href", "http://www.google.com"},
        },
    }
    fmt.Println(n)

    for i := 0; i < len(n.Attr); i++ {
        attr := &n.Attr[i]
        if attr.Key == "href" {
            attr.Val = "something"
        }
    }

    fmt.Println(n)
}

Çıktı:

{[{key value} {href http://www.google.com}]}
{[{key value} {href something}]}

Bu Attribute, dilim sınırları kontrolleri pahasına tip değerlerin - muhtemelen büyük - bir kopyasını oluşturmayı önler . Örneğin, tür Attributenispeten küçüktür, iki stringdilim başvurusu: 64 bit mimari makinede 2 * 3 * 8 = 48 bayt.

Ayrıca şunu da yazabilirsiniz:

for i := 0; i < len(n.Attr); i++ {
    if n.Attr[i].Key == "href" {
        n.Attr[i].Val = "something"
    }
}

Ancak, rangebir kopya oluşturan ancak dilim sınırları denetimlerini en aza indiren bir cümle ile eşdeğer bir sonuç elde etmenin yolu :

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}

2
Yazık o value := &someMap[key]olmaz eser eğer someMapbir olduğunumap
warvariuc

peterSO, ilk kod snippet'inizde, ona bir şey atamak için attr'i ertelemek zorunda değil misiniz? ie*attr.Val = "something"
Homam Bahrani

25

Son önerinizi uyarlar ve aralığın yalnızca dizin sürümünü kullanırım.

for i := range n.Attr {
    if n.Attr[i].Key == "href" {
        n.Attr[i].Val = "something"
    }
}

İfade etmek bana daha basit görünüyor n.Attr[i]hat hem de açık olarak bu testler Keyve çizgi olduğunu setleri Valkullanmak yerine, attrbiri için ve n.Attr[i]diğeri için.


15

Örneğin:

package main

import "fmt"

type Attribute struct {
        Key, Val string
}

type Node struct {
        Attr []*Attribute
}

func main() {
        n := Node{[]*Attribute{
                &Attribute{"foo", ""},
                &Attribute{"href", ""},
                &Attribute{"bar", ""},
        }}

        for _, attr := range n.Attr {
                if attr.Key == "href" {
                        attr.Val = "something"
                }
        }

        for _, v := range n.Attr {
                fmt.Printf("%#v\n", *v)
        }
}

Oyun alanı


Çıktı

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}

Alternatif yaklaşım:

package main

import "fmt"

type Attribute struct {
        Key, Val string
}

type Node struct {
        Attr []Attribute
}

func main() {
        n := Node{[]Attribute{
            {"foo", ""},
            {"href", ""},
            {"bar", ""},
        }}

        for i := range n.Attr {
                attr := &n.Attr[i]
                if attr.Key == "href" {
                        attr.Val = "something"
                }
        }

        for _, v := range n.Attr {
                fmt.Printf("%#v\n", v)
        }
}

Oyun alanı


Çıktı:

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}

Açık olduğunu düşündüm ama aldığım yapıları değiştirmek istemiyorum ( go.net/htmlpaketten geliyorlar )
Denys Séguret

1
@dystroy: İkinci yaklaşım, yukarıda etmez OP wrt türleri ( "yapılar") değiştirin.
zzzz

Evet, biliyorum, ama gerçekten bir şey getirmiyor. Muhtemelen kaçırmış olabileceğim bir fikir bekliyordum. Daha basit bir çözüm olmadığından emin olabilirsiniz, o zaman cevap bu olacaktır.
Denys Séguret

1
@dystroy: Bir şey getiriyor, buraya kopyalayıp tüm Özniteliği geri getirmiyor . Ve evet, öğenin çift kopya (r + w) güncellemesinden kaçınmak için bir dilim öğesinin adresini almanın en uygun çözüm olduğundan eminim.
zzzz
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.