반복하는 동안 값 변경


153

이 유형이 있다고 가정 해 봅시다.

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

노드 속성을 반복하여 변경하고 싶습니다.

나는 할 수 있기를 바랐습니다.

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

그러나 attr포인터가 아니기 때문에 이것은 작동하지 않으며 수행해야합니다.

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

더 간단하거나 빠른 방법이 있습니까? 직접 포인터를 얻을 수 range있습니까?

분명히 반복을 위해 구조를 변경하고 싶지 않으며 더 자세한 솔루션은 솔루션이 아닙니다.


2
그래서 당신 Array.prototype.forEach은 JavaScript에서 어떤 종류를 원 하십니까?
Florian Margaine

그것은 흥미로운 아이디어이며 솔루션 일 수는 있지만 각 반복에서 함수를 호출하는 함수를 호출하면 서버 측 언어에서 무겁고 잘못 보입니다. 그리고 제네릭의 부족은 이것을 더 무겁게 만들 것입니다.
Denys Séguret

솔직히, 나는 그것이 그렇게 무겁지 않다고 생각합니다. 하나 또는 두 개의 함수를 호출하는 것은 매우 저렴합니다. 이것은 일반적으로 컴파일러가 가장 최적화하는 것입니다. 나는 그것을 시도하고 그것이 법안에 맞는지 확인하기 위해 벤치 마크 할 것입니다.
Florian Margaine

Go에는 제네릭이 없기 때문에 전달 된 함수 forEach가 형식 어설 션으로 시작해야 할까봐 걱정 됩니다. 그것은 실제로보다 낫지 않습니다 attr := &n.Attr[i].
Denys Séguret

답변:


152

아니요, 원하는 약어는 불가능합니다.

그 이유 range는 반복하는 슬라이스의 값 을 복사하기 때문입니다 . 범위에 대한 사양은 말합니다 :

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

따라서 range는 a[i]배열 / 슬라이스의 두 번째 값으로 사용 됩니다. 즉, 값이 복사되어 원래 값을 건드리지 못하게됩니다.

이 동작은 다음 코드로 설명됩니다 .

x := make([]int, 3)

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

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

이 코드는 범위의 값과 슬라이스의 실제 값에 대해 완전히 다른 메모리 위치를 인쇄합니다.

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

따라서 jnml과 peterSO에서 이미 제안한 것처럼 포인터 또는 색인을 사용하는 것이 유일한 방법입니다.


16
이것을 생각하는 한 가지 방법은 값을 할당하면 사본이 생성된다는 것입니다. val : = x [1]을 본 경우, val이 x [1]의 사본이라는 것은 전혀 놀라운 일이 아닙니다. 범위를 특별한 무언가로 생각하는 대신, 범위의 각 반복은 인덱스 및 값 변수를 할당하는 것으로 시작하며 복사를 유발하는 범위가 아니라 할당이라는 것을 기억하십시오.
Andy Davis

미안하지만 여전히 조금 혼란 스럽습니다. for 루프의 두 번째 값이 a [i]이면 a[i]for 루프와 a[i]우리가 쓰는 것과 의 차이점은 무엇 입니까? 같은 것 같지만 그렇지 않습니까?
Tiến Nguyễn Hoàng 2016 년

1
@ TiếnNguyễnHoàng rangea[i]두 번째 반환 값으로 반환합니다. 이 작업 val = a[i]을 수행 range하면 값의 복사본이 만들어 지므로 모든 쓰기 작업 val이 복사본에 적용됩니다.
nemo

37

당신은 이것과 동등한 것을 요구하는 것 같습니다 :

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

산출:

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

이렇게하면 Attribute슬라이스 경계 검사를 희생시키면서 유형 값을 복사 할 수 있습니다 . 귀하의 예에서 type Attributestring64 비트 아키텍처 시스템에서 비교적 작은 두 개의 슬라이스 참조입니다 (2 * 3 * 8 = 48 바이트).

간단히 쓸 수도 있습니다.

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

그러나 range복사본을 만들지 만 슬라이스 경계 검사를 최소화 하는 절로 동등한 결과를 얻는 방법 은 다음과 같습니다.

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

2
다음과 같은 value := &someMap[key]경우 작동하지 않는 것이 유감입니다 someMap.map
warvariuc

첫 번째 코드 스 니펫의 peterSO 당신은 그것에 attr을 할당하기 위해 attr을 연기하지 않아도됩니까? 즉,*attr.Val = "something"
호맘 Bahrani

25

마지막 제안을 수정하고 색인 전용 버전의 범위를 사용하고 싶습니다.

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

n.Attr[i]테스트 Key하는 줄과 설정하는 줄 모두 를 명시 적으로 언급하는 Val것이 attr하나와 n.Attr[i]다른 것을 사용 하는 것보다 간단합니다 .


15

예를 들면 다음과 같습니다.

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

운동장


산출

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

대체 접근법 :

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

운동장


산출:

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

나는 그것이 명백했다 생각하지만 난 (그들이에서있어 내가 얻는 구조를 변경하지 않으 go.net/html패키지)
데니스 Séguret

1
@dystroy : 위의 두 번째 접근 방식 은 OP의 유형 ( "구조")을 변경 하지 않습니다 .
zzzz

예, 알고 있지만 실제로는 아무것도 가져 오지 않습니다. 나는 내가 놓쳤을 수있는 아이디어를 기대하고 있었다. 나는 당신이 그보다 더 간단한 해결책이 없다고 확신한다고 생각합니다.
Denys Séguret

1
@dystroy : 그것은 않는 무언가를 가지고, 그것은 하지 않습니다 여기에 복사 전체 특성을 백업 할 수 있습니다. 그리고 예, 요소의 이중 복사 (r + w) 업데이트를 피하기 위해 슬라이스 요소의 주소를 취하는 것이 최적의 해결책이라고 확신합니다.
zzzz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.