Thay đổi giá trị trong khi lặp


153

Giả sử tôi có những loại này:

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

và rằng tôi muốn lặp lại các thuộc tính của nút để thay đổi chúng.

Tôi rất thích có thể làm:

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

nhưng attrkhông phải là một con trỏ, điều này sẽ không hoạt động và tôi phải làm:

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

Có cách nào đơn giản hơn hay nhanh hơn? Có thể trực tiếp nhận được con trỏ từ range?

Rõ ràng tôi không muốn thay đổi các cấu trúc chỉ cho lần lặp và các giải pháp dài dòng hơn không phải là giải pháp.


2
Vì vậy, bạn muốn một số loại Array.prototype.forEachtrong JavaScript?
Florian Margaine

Đó là một ý tưởng thú vị và đó có thể là một giải pháp nhưng việc gọi một hàm sẽ lần lượt gọi một hàm ở mỗi lần lặp có vẻ nặng và sai trong ngôn ngữ phía máy chủ. Và việc thiếu thuốc generic sẽ khiến cảm giác này nặng nề hơn.
Denys Séguret

Thành thật mà nói, tôi không nghĩ nó nặng đến thế. Gọi một hoặc hai hàm là rất rẻ, đây thường là những gì trình biên dịch tối ưu hóa nhất. Tôi sẽ thử và đánh giá nó để xem nó có phù hợp với hóa đơn không.
Florian Margaine

Vì Go thiếu tính tổng quát, tôi e rằng hàm được truyền vào forEachsẽ nhất thiết phải bắt đầu bằng một xác nhận kiểu. Điều đó không thực sự tốt hơn attr := &n.Attr[i].
Denys Séguret

Câu trả lời:


152

Không, viết tắt bạn muốn là không thể.

Lý do cho điều này là rangesao chép các giá trị từ lát bạn đang lặp đi lặp lại. Các đặc điểm kỹ thuật về phạm vi nói:

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

Vì vậy, phạm vi sử dụng a[i]làm giá trị thứ hai của nó cho các mảng / lát, có nghĩa là giá trị đó được sao chép một cách hiệu quả, làm cho giá trị ban đầu không thể chạm tới được.

Hành vi này được thể hiện bằng mã sau đây :

x := make([]int, 3)

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

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

Mã in cho bạn các vị trí bộ nhớ hoàn toàn khác nhau cho giá trị từ phạm vi và giá trị thực trong lát:

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

Vì vậy, điều duy nhất bạn có thể làm là sử dụng con trỏ hoặc chỉ mục, như đã được đề xuất bởi jnml và peterSO.


16
Một cách để nghĩ về điều này là việc gán một giá trị gây ra một bản sao. Nếu bạn thấy val: = x [1], sẽ hoàn toàn không ngạc nhiên khi val là bản sao của x [1]. Thay vì nghĩ về phạm vi là làm một điều gì đó đặc biệt, hãy nhớ rằng mỗi lần lặp của một phạm vi bắt đầu bằng cách gán các biến chỉ số và giá trị, và đó là phép gán đó thay vì phạm vi gây ra bản sao.
Andy Davis

Xin lỗi tôi vẫn còn một chút nhầm lẫn ở đây. Nếu giá trị thứ 2 của vòng lặp for là [i], thì sự khác biệt giữa a[i]vòng lặp for và vòng lặp a[i]như chúng ta viết là gì? Nó trông giống như vậy nhưng không phải vậy, phải không?
Tiến Nguyễn Hoàng

1
@ TiếnNguy NhậtHoàng rangetrả về a[i]làm giá trị trả lại thứ hai của nó. Thao tác này, val = a[i]được thực hiện bằng cách rangetạo một bản sao của giá trị để mọi thao tác ghi valđược áp dụng cho một bản sao.
nemo

37

Bạn dường như đang yêu cầu một cái gì đó tương đương với điều này:

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

Đầu ra:

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

Điều này tránh tạo ra một bản sao - có thể lớn - của các Attributegiá trị loại , với chi phí kiểm tra giới hạn lát. Trong ví dụ của bạn, loại Attributetương đối nhỏ, hai stringtham chiếu lát: 2 * 3 * 8 = 48 byte trên máy kiến ​​trúc 64 bit.

Bạn cũng có thể chỉ cần viết:

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

Nhưng, cách để có được kết quả tương đương với một rangemệnh đề, tạo ra một bản sao nhưng giảm thiểu kiểm tra giới hạn lát, là:

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

2
Thật đáng tiếc nếu value := &someMap[key]nó không hoạt động nếu someMapmap
warvariuc

peterSO trong đoạn mã đầu tiên của bạn, bạn có phải trì hoãn attr để gán một cái gì đó cho nó không? tức là*attr.Val = "something"
Homam Bahrani

25

Tôi sẽ điều chỉnh đề xuất cuối cùng của bạn và sử dụng phiên bản phạm vi chỉ mục.

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

Nó có vẻ đơn giản hơn đối với tôi khi đề cập n.Attr[i]rõ ràng trong cả dòng kiểm tra Keyvà dòng đặt Val, thay vì sử dụng attrcho cái này và n.Attr[i]cái kia.


15

Ví dụ:

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

Sân chơi


Đầu ra

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

Cách tiếp cận khác:

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

Sân chơi


Đầu ra:

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

Tôi nghĩ đó là điều hiển nhiên nhưng tôi không muốn thay đổi cấu trúc tôi nhận được (chúng từ go.net/htmlgói)
Denys Séguret

1
@dystroy: Cách tiếp cận thứ hai ở trên không thay đổi các loại ("cấu trúc") khi viết OP.
zzzz

Vâng, tôi biết, nhưng nó không thực sự mang lại điều gì. Tôi đã mong đợi một ý tưởng mà tôi có thể có thể đã bỏ lỡ. Tôi cảm thấy tự tin rằng không có giải pháp đơn giản hơn thì đó sẽ là câu trả lời.
Denys Séguret

1
@dystroy: Nó không mang lại điều gì đó, nó không sao chép ở đây và sao lưu toàn bộ thuộc tính. Và vâng, tôi tự tin rằng lấy địa chỉ của phần tử lát để tránh cập nhật bản sao kép (r + w) của phần tử là giải pháp tối ưu.
zzzz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.