2013-04-11 12 views
96

Giả sử tôi có các loại sau:Thay đổi giá trị trong khi lặp lại trong golang

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

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

tôi đã có thể yêu để có thể làm:

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

nhưng như attr không phải là một con trỏ, điều này sẽ không làm việc và tôi phải làm:

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

Có một cách đơn giản hoặc nhanh hơn? Có thể trực tiếp nhận con trỏ từ range không?

Rõ ràng là tôi không muốn thay đổi cấu trúc chỉ dành cho các giải pháp lặp lại và tiết kiệm hơn là không có giải pháp.

+2

Vì vậy, bạn muốn một số loại 'Array.prototype.forEach' trong JavaScript? –

+0

Đó là một ý tưởng thú vị và đó có thể là một giải pháp nhưng gọi một hàm sẽ lần lượt gọi hàm ở mỗi lần lặp lại trông nặng và sai ở ngôn ngữ phía máy chủ. Và thiếu các generics sẽ làm cho cảm giác này thậm chí còn nặng hơn. –

+0

Thành thật mà nói, tôi không nghĩ nó nặng đến thế. Gọi một chức năng hoặc hai 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ử nó và đánh giá nó để xem liệu nó có phù hợp với dự luật hay không. –

Trả lời

87

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

Lý do cho việc này là range sao chép các giá trị từ slice mà bạn đang lặp lại. Các specification about range 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] như giá trị thứ hai của nó đối với các mảng/lát, mà hiệu quả có nghĩa là giá trị được sao chép, làm cho giá trị ban đầu không thể chạm.

Hành vi này được thể hiện bởi các following code:

x := make([]int, 3) 

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

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

Các bản in mã bạn địa điểm hoàn toàn khác nhau bộ nhớ cho giá trị từ cự ly và giá trị thực tế trong slice:

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

Vì vậy, các đ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.

+9

Một cách để nghĩ về điều này là gán một giá trị sẽ tạo ra một bản sao. Nếu bạn thấy val: = x [1], nó sẽ là hoàn toàn không ngạc nhiên rằng val là một bản sao của x [1]. Thay vì suy nghĩ về phạm vi khi làm đ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ỉ mục và giá trị, và rằng đó là nhiệm vụ thay vì phạm vi gây ra bản sao. –

12

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

Playground


Output

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

phương pháp thay thế:

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

Playground


Output:

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

Tôi nghĩ rằng nó là 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ừ gói 'go.net/html') –

+1

@dystroy: Cách tiếp cận thứ hai bên trên _doesn't_ thay đổi các loại (" cấu trúc ") wrt OP. – zzzz

+0

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

17

Bạn dường như được yêu cầu cho một cái gì đó tương đương như sau:

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

Output:

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

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

Bạn cũng có thể chỉ đơn giả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 một kết quả tương đương với một khoản range, mà tạo ra một bản sao nhưng giảm thiểu lát tiếp giáp séc, là:

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

Thật đáng tiếc là 'value: = & someMap [key]' sẽ không hoạt động nếu 'someMap' là một' map' – warvariuc

6

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

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

Có vẻ như đơn giản hơn với tôi để tham khảo n.Attr[i] một cách rõ ràng trong cả hai dòng kiểm tra Key và dòng mà bộ Val, thay vì sử dụng attr cho một và n.Attr[i] cho người khác.