[go] Go에서 2D 슬라이스를 만드는 간결한 방법은 무엇입니까?

A Tour of Go를 통해 Go를 배우고 있습니다. 거기에있는 연습 중 하나 는를 포함하는 dy행과 dx열의 2D 슬라이스를 생성하도록 요청합니다 uint8. 내 현재 접근 방식은 다음과 같습니다.

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
}

나는 그것을 초기화하기 위해 각 슬라이스를 반복하는 것이 너무 장황하다고 생각합니다. 그리고 조각에 더 많은 차원이 있으면 코드가 다루기 어려워집니다. Go에서 2D (또는 n 차원) 슬라이스를 초기화하는 간결한 방법이 있습니까?



답변

더 간결한 방법은 없습니다. “올바른”방법을 사용했습니다. 슬라이스는 항상 1 차원이지만 더 높은 차원의 객체를 구성하도록 구성 될 수 있기 때문입니다. 자세한 내용은 다음 질문을 참조하십시오. Go : 2 차원 배열의 메모리 표현 방법 .

단순화 할 수있는 한 가지는 다음과 같은 for range구성 을 사용하는 것입니다 .

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

또한 컴포지트 리터럴로 슬라이스를 초기화 하면 “무료”를 얻을 수 있습니다. 예를 들면 다음과 같습니다.

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

예, 모든 요소를 ​​열거해야하므로 한계가 있습니다. 그러나 몇 가지 트릭이 있습니다. 즉, 모든 값을 열거 할 필요가없고 슬라이스 요소 유형의 0 값아닌 값만 열거 할 필요가 있습니다 . 이에 대한 자세한 내용은 golang 배열 초기화의 키 항목을 참조하세요 .

예를 들어, 당신은 처음 10 개 요소가 제로가 다음 다음 조각하려는 경우 12이 같이 생성 할 수 있습니다 :

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

또한 slices 대신 배열 을 사용하면 매우 쉽게 만들 수 있습니다.

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

출력은 다음과 같습니다.

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

배열의 경우 “외부”배열을 반복하고 “내부”배열을 초기화 할 필요가 없습니다. 배열은 설명자가 아니라 값이기 때문입니다. 자세한 내용은 블로그 게시물 배열, 슬라이스 (및 문자열) : ‘추가’메커니즘 을 참조하십시오.

Go Playground 에서 예제를 시도해보십시오 .


답변

슬라이스를 사용하여 행렬을 만드는 방법에는 두 가지가 있습니다. 그들 사이의 차이점을 살펴 보겠습니다.

첫 번째 방법 :

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

두 번째 방법 :

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

첫 번째 방법과 관련하여 연속 make호출을한다고해서 연속적인 행렬이 생성되지는 않으므로 행렬을 메모리에서 분할 할 수 있습니다. 이를 유발할 수있는 두 개의 Go 루틴이있는 예를 생각해 봅시다.

  1. 루틴 # 0은 make([][]int, n)에 할당 된 메모리를 가져 오기 위해 실행 되어 matrix0x000에서 0x07F까지의 메모리 조각을 가져옵니다.
  2. 그런 다음 루프를 시작하고 make([]int, m)0x080에서 0x0FF까지 가져 오는 첫 번째 행을 수행합니다 .
  3. 두 번째 반복에서는 스케줄러가 선점합니다.
  4. 스케줄러는 프로세서에 루틴 # 1을 제공하고 실행을 시작합니다. 이것은 또한 make(자신의 목적을 위해) 0x100에서 0x17F (루틴 # 0의 첫 번째 행 바로 옆)를 사용합니다.
  5. 잠시 후 선점되고 루틴 # 0이 다시 실행되기 시작합니다.
  6. 이것은 수행 make([]int, m)번째 루프 반복에 대응하고 두번째 행 0x1FF에 0x180로부터 얻는다. 이 시점에서 우리는 이미 두 개의 분할 된 행을 얻었습니다.

두 번째 방법을 사용하면 루틴이 make([]int, n*m)단일 슬라이스에 할당 된 모든 행렬을 가져 와서 연속성을 보장합니다. 그 후 각 행에 해당하는 부분 조각에 대한 행렬 포인터를 업데이트하려면 루프가 필요합니다.

Go Playground 에서 위에 표시된 코드 를 사용하여 두 방법을 사용하여 할당 된 메모리의 차이를 확인할 수 있습니다. 내가 사용하는 것이 주 runtime.Gosched()프로세서를 산출하고 다른 루틴으로 전환하려면 스케줄러를 강제의 목적으로 만.

어느 것을 사용할까요? 첫 번째 방법의 최악의 경우를 상상해보십시오. 즉, 각 행이 메모리에서 다른 행의 다음 행이 아닙니다. 그런 다음 프로그램이 행렬 요소를 반복하는 경우 (읽거나 쓰기 위해) 두 번째 방법에 비해 더 많은 데이터 지역성으로 인해 캐시 미스가 더 많을 것입니다 (따라서 지연 시간이 길어짐). 다른 한편으로, 두 번째 방법을 사용하면 이론적으로 충분한 여유 메모리가있을 수 있지만 메모리 조각화 (메모리 전체에 분산 된 덩어리)로 인해 매트릭스에 대해 단일 메모리 조각을 할당하는 것이 불가능할 수 있습니다. .

따라서 메모리 조각화가 많고 할당 할 행렬이 충분히 크지 않는 한 항상 두 번째 방법을 사용하여 데이터 지역성을 활용하는 것이 좋습니다.


답변

이전 답변에서 우리는 초기 길이를 알 수없는 상황을 고려하지 않았습니다. 이 경우 다음 논리를 사용하여 행렬을 만들 수 있습니다.

items := []string{"1.0", "1.0.1", "1.0.2", "1.0.2.1.0"}
mx := make([][]string, 0)
for _, item := range items {
    ind := strings.Count(item, ".")
    for len(mx) < ind+1 {
        mx = append(mx, make([]string, 0))
    }
    mx[ind] = append(mx[ind], item)

}

fmt.Println(mx)

https://play.golang.org/p/pHgggHr4nbB


답변