user391339
user391339

Reputation: 8795

Why is my 2D golang array being populated with row after row of the same value?

I am taking a tour of the Go language and am trying to do the following example (populating 2D arrays): https://tour.golang.org/moretypes/18 -- see the code below...

In the example we are supposed to populate a 2D array of uint8 with values (based on a function of our choice), then displaying these as a photo/pattern.

In my code, yy should be a [][]uint8 array populated with single rows of []uint8 (xx).

For the pattern/data I kept it simple - with each index of yy (y-axis), I increase the values placed in rows of xx - so the data should output a row of 0's, then a row of 1's, then a row of 2's, etc.

Although my code appears to work correctly based on the output of the first Println statement below (listed inside the loop as I attempt to populate the 2D array yy), the final values stored in the yy 2d array are all rows of 255! It's somehow using the value of the last x-axis row for everything.

I think somehow instead of making new value assignments I am somehow "pointing" to a single row of x-axis data that's being over-written, so at the end I get a matrix filled with the values that went in that final row.

package main

import (
    "fmt"
    "golang.org/x/tour/pic"
)

func Pic(dx, dy int) [][]uint8 {


    //HERE I DYNAMICALLY CREATE THE ARRAYS BASED ON WHAT I WAS TAUGHT PREVIOUSLY
    //I UNDERSTAND THAT TECHNICALLY THESE MAY BE SLICES NOT ARRAYS ...
    //AND THUS MAYBE POINT SOMEWHERE AND DON'T ACTUALLY STORE ALL THE DATA (???)
    yy := make([][]uint8, dy)
    xx := make([]uint8, dx)

    for y := range yy {
        for x := range xx {
            xx[x] = uint8((y)) // FOR SIMPLICITY I MADE PATTERN ROW x = y
        }
        yy[y] = xx
        //THIS OUTPUT IS CORRECT; ROW OF 0's, ROW OF 1's, ROW OF 2's, ETC
        fmt.Println(y, yy[y])
    }

    for y := range yy {
        //THIS OUTPUT IS INCORRECT; ALL 255!
        //IT POPULATED ALL ROWS W/ VALUES FROM THE LAST LOOP
        fmt.Println(y, yy[y])
    }

    return yy

}

func main() {
    pic.Show(Pic)
}

Note: Because I am debugging, use of "fmt.Println()" abrogates the display of the photo in my hands (but don't worry about that aspect). If you delete the fmt.Println() calls you can get the photo to display (albeit with the wrong/unexpected data).

Upvotes: 2

Views: 71

Answers (2)

Schwern
Schwern

Reputation: 165546

The problem is yy[y] = xx does not copy xx, it assigns a reference to it.

for y := range yy {
    for x := range xx {
        xx[x] = uint8((y))
    }

    // This is not a copy.
    yy[y] = xx

    fmt.Println(y, yy[y])
}

The first iteration y = 0. It writes 0 to all of xx, then yy[y] = xx assigns a reference to xx to yy[0], not a copy of xx.

The next iteration of the loop y = 1. It writes 1 to all of xx, the same xx as before, which also alters the reference to xx in yy[0]. It then assigns a reference to xx as yy[1]. Repeat 256 times.

xx, yy[0] and yy[1] and yy[2] and so on all refer to the same data.

When you print yy at the end, you're really just printing xx 256 times. You can see this clearly by printing the pointer value, %p.

fmt.Printf("%3s %p\n", "xx", xx);
for y := range yy {
    fmt.Printf("%03v %p\n", y, yy[y])
}

 xx 0xc420096000
000 0xc420096000
001 0xc420096000
002 0xc420096000
003 0xc420096000
004 0xc420096000
005 0xc420096000

The solution is to allocate xx inside the loop, so a new xx is allocated each loop iteration.

for y := range yy {
    xx := make([]uint8, dx)

    for x := range xx {
        xx[x] = uint8((y))
    }

    // Still takes a reference to xx, but a different xx each time.
    yy[y] = xx

    fmt.Println(y, yy[y])
}

Or you can copy xx, but this approach is safer: there's no possibility to accidentally reuse the same reference.

Upvotes: 3

giorgiga
giorgiga

Reputation: 1778

It's because you are assigning (by reference) xx to all places in yy (as if yy := [][]uint8{ xx, xx, xx, xx, ... }.

Try allocating a new xx for each place in yy:

yy := make([][]uint8, dy)
for y := range yy {
    xx := make([]uint8, dx)

Upvotes: 3

Related Questions