19

I am trying to create a 2d array in Go:

board := make([][]string, m)
for i := range board {
    board[i] = make([]string, n)
}

However, given the verbosity of that, I am wondering if there is a better or more succinct way to handle this problem (either to generate dynamic arrays, or a different/idiomatic data-structure to handle such board-game like data)?


Background:

  • this is for a board game
  • the dimensions of the board are not known until a user starts playing (so, say, MxN).
  • I want to store an arbitrary character (or a single char string) in each cell. In my TicTacToe game that will be the 'X' or an 'O' (or any other character the user chooses).
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
or9ob
  • 2,313
  • 4
  • 25
  • 45
  • possible duplicate of [Dynamically initialize array size in go](http://stackoverflow.com/questions/8539551/dynamically-initialize-array-size-in-go) – msw May 26 '14 at 12:09
  • I've usually seen this handled by using slice literals to define the starting size and position for the board (arbitrary, it can be expanded using append), then modify that. See http://blog.golang.org/go-slices-usage-and-internals . – Intermernet May 26 '14 at 12:10
  • @msw That Q is from Dec'11. Also, it suggests what I did as the answer. I am pondering if there's a better way. – or9ob May 26 '14 at 12:13
  • @Intermernet thanks. So, `make` indeed creates slices. Would you mind expanding on your suggestion (perhaps as an answer) by showing how append can be used in this case. – or9ob May 26 '14 at 12:15
  • You could use `rune`s instead of `string`s if you will only store 1 character. – Goodwine May 26 '14 at 14:36

3 Answers3

17

What you are building in your sample code is not a 2D array, but rather a slice of slices: each of the sub-slices could be of a different length with this type, which is why you have separate allocations for each.

If you want to represent the board with a single allocation though, one option would be to allocate a single slice, and then use simple arithmetic to determine where elements are. For example:

board := make([]string, m*n)
board[i*m + j] = "abc" // like board[i][j] = "abc"
James Henstridge
  • 42,244
  • 6
  • 132
  • 114
8

The way you described creates a slice of slices, which looks similar to a 2d array that you want. I would suggest you to change the type to uint8, as you only care about 3 states nothing / first / second player.

This allocates each row separately (you will see at least m + 1 allocs/op in your benchmarks). This is not really nice because there is no guarantee that the separate allocations would be localized close to each other.

To maintain locality you can do something like this:

M := make([][]uint8, row)
e := make([]uint8, row * col)
for i := range M {
    a[i] = e[i * col:(i + 1) * col]
}

This will end up with only 2 allocations and the slice of slices will maintain data locality. Note that you will still be able to access your M in 2d format M[2][6].

A good video which explains how to do this even faster.

Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
4

For a multi dimensional array, we can have any of the 2 uses cases,

  1. You know the dimensions of array while compiling
  2. You get to know the array dimension only at runtime, ie may from the user input or so

For use case 1

matr := [5][5]int{}

For use case 2

var m, n int
fmt.Scan(&m, &n)
var mat = make([][]int, m)
for i := range mat {
    mat[i] = make([]int, n)
    fmt.Printf("Row %d: %v\n", i, mat[i])
}

In short, we have to rely on make for creating dynamic arrays

Zik
  • 202
  • 3
  • 11
Jeevan
  • 309
  • 1
  • 6