2

I'm learning Go and I'm trying to generate a 2d array [][]int that in a nested for loop just puts a value in the spot. Do I always have to create the array with a fixed size using make or is it possible to have this be done dynamically in the loop?

genMap := [][]int{}
for i := 0; i < 10; i++ {
    for j := 0; j < 10; j++ {
        genMap[i][j] = 1
    }
}
return genMap

This gives the error index out of range though. So I'm not really sure if this is possible in Go or I'm missing something crucial

  • 1
    https://tour.golang.org/moretypes/15 – Peter Aug 13 '19 at 17:22
  • Also see related: [How is two dimensional array's memory representation](https://stackoverflow.com/questions/39561140/how-is-two-dimensional-arrays-memory-representation/39561477#39561477). – icza Aug 13 '19 at 18:05

1 Answers1

8

You are indeed missing something crucial--in go, slices (which technically is the thing you are creating with the statement genMap := [][]int{}) default to length zero. You cannot access any elements of genMap because there are none to access, so any attempt to do so results in an index out of range error. For reference, check out this previous answer on SO.

There are a few ways to address this. You could preallocate the slices, append to each slice as you iterate through the loop, or pick a more convenient data type for your data.

Preallocate the slices

As you mention, you could simply allocate the slices to the known length before the loop. This could potentially lead to speed improvements because you won't be moving values around in memory every time you have to expand the extent of the slices. Remember, however, that your "2D array" is actually a slice of slice of ints, so each internal slice (the []int part) has to be allocated in a separate step. You can do this in the loop:

genMap := make([][]int, 10)      // Make the outer slice and give it size 10
for i := 0; i < 10; i ++ {
    genMap[i] = make([]int, 10)  // Make one inner slice per iteration and give it size 10
    for j := 0; j < 10; j++ {
        genMap[i][j] = 1
    }
}

See the code in action.

Append while iterating

This is useful if you don't know how large the outer or inner slices will be. The disadvantage, as stated before, is that you might be moving around a lot of memory when you call append, so this could be inefficient.

genMap := [][]int{}             // Make the outer slice with size 0
for i := 0; i < 10; i ++ {
    m := []int{}                // Make one inner slice per iteration with size 0
    for j := 0; j < 10; j++ {
        m = append(m, 1)        // Append to the inner slice
    }
    genMap = append(genMap, m)  // Append to the outer slice
}

See the code in action.

Pick a more convenient data type

Sometimes it is more useful to design a struct that meets your needs but stores your data in a contiguous slice rather than a slice-of-slices. This could improve performance and readability of your code. Such a struct could have methods defined to help inserting and looking up indices, like so:

type TwoD struct {
    data  []int
    xSpan int
    ySpan int
}

// Factory function
func NewTwoD(xspan, yspan int) *TwoD {
    return &TwoD{data: make([]int, xspan*yspan), xSpan: xspan, ySpan: yspan}
}

func (td *TwoD) Put(x, y, value int) {
    // optionally do some bounds checking here
    td.data[x*td.ySpan+y] = value
}

func (td *TwoD) Get(x, y int) int {
    // optionally do some bounds checking here
    return td.data[x*td.ySpan+y]
}

func main() {
    genMap := NewTwoD(10, 10)
    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            genMap.Put(i, j, 1)
        }
    }
}

See the code in action.

Additionally, if you are planning on doing linear algebra with these values, consider checking out Gonum for well-designed, well-tested code to help you out.

PaSTE
  • 4,050
  • 18
  • 26
  • Excellent answer, which deserves more attention than it seems to be getting, perhaps because the original question has a low score. Thanks. – Chris Dennis Dec 05 '20 at 19:38