Python Forum

Full Version: Behaviour of 2D array
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
My intention is to populate a 2d array with booleans at a certain DENSITY. Although the function "populate" seems to do the right thing up to exiting a double for loop, it seems as if the last row of the array has been stored in all rows, as shown in the output

from array import *
import random

MAXHORZ = 15
MAXVERT = 10
DENSITY = 50

torus = array('b')
torus = [[0] * MAXHORZ] * MAXVERT


def populate_torus(torus):
    for x in range(MAXVERT):
        for y in range(MAXHORZ):
            if random.randint(1, 100) < DENSITY:
                torus[x][y] = 1
            else:
                torus[x][y] = 0
        print(torus[x])
    print()
    for i in range(MAXVERT):
        print(torus[i])
    return(torus)


def display_torus(torus):
    print('In Display:')
    for x in range(0, MAXVERT):
        print(torus[x])
    print('end')


torus = populate_torus(torus)
display_torus(torus)
print('DONE')
The output produced is as follows:

Output:
/usr/bin/python3.8 /tmp/first_run.txt/Torus.py [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] [0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0] [0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1] [1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0] [1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1] [1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0] [0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0] [0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0] [1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] In Display: [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0] end DONE Process finished with exit code 0
There is obviously something I do not understand about arrays can anyone spot the flaw for me>
The problem is how you create it on this line

torus = [[0] * MAXHORZ] * MAXVERT
lists are mutable and you populate with same object, thus when you update one, it is reflected elsewhere

rows = 3
cols = 4

spam = [[0] * cols] * rows
print(spam)
print([id(row) for row in spam])
spam[0][0] = 1
print(spam)
print('-----------------------')
eggs = [[0] * cols for _ in range(rows)]
print(eggs)
print([id(row) for row in eggs])
eggs[0][0] = 1
print(eggs)
Output:
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] [12470912, 12470912, 12470912] [[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]] ----------------------- [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] [13534272, 13534312, 13534152] [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
Thanks, I'll try the second form.
I just did, works to perfection. Grateful!
SB
You can also create the data structure on-the fly, without need to initialize it beforehand
(Jan-21-2021, 10:42 AM)buran Wrote: [ -> ]You can also create the data structure on-the fly, without need to initialize it beforehand
I see! How? (I come back to programming for the first time after 30 years - used to be a C programmer where the notion of "on the fly" is peculiar). In this particular case it is probably best to declare it for readability as this array is involved almost everywhere (it is a toroid surface for my own variation of Conway's game of Life).
what I mean:
import random

def create_torus(max_vert, max_horz, density):
    torus = []
    for x in range(max_vert):
        torus.append([])
        for y in range(max_horz):
            torus[-1].append(int(random.randint(1, 100) < density))
    return torus

def pprint_torus(torus):
    for row in torus:
        print(row)


MAXHORZ = 15
MAXVERT = 10
DENSITY = 50
torus = create_torus(MAXVERT, MAXHORZ, DENSITY)
pprint_torus(torus)
create_torus can be one-lier using list comprehension
def create_torus(max_vert, max_horz, density):
    return [[int(random.randint(1, 100) < density) for y in range(max_horz)] for x in range(max_vert)]
Now, because you say it's [variation of] Conway's Game of Life, I think it will be better to move to OOP/class structure sooner than later.

e.g.

import random

class Torus:
    def __init__(self, max_vert, max_horz, density):
        self._torus = [[int(random.randint(1, 100) < density) for y in range(max_horz)] for x in range(max_vert)]

    @property
    def rows(self):
        return len(self._torus)

    @property
    def columns(self):
        return len(self._torus[0])

    def __repr__(self):
        return f"Torus({self.rows} rows x {self.columns} columns: {', '.join(str(row) for row in self._torus)})"

    def __str__(self):
        return '\n'.join(str(row) for row in self._torus)

if __name__ == '__main__':
    MAXHORZ = 15
    MAXVERT = 10
    DENSITY = 50
    torus = Torus(MAXVERT, MAXHORZ, DENSITY)
    print(torus)
    print(repr(torus))
Note, I am using regular lists, not array.array, but you get the idea. You can use whatever you want. However note that you also don't use array.array (despite what you may think):
torus = array('b') # here torus is array.array
torus = [[0] * MAXHORZ] * MAXVERT # here you bind name torus to different object - list of lists, it is no longer array.array

Also note that star imports lile from array import * are generally considered bad practice and discouraged.
(Jan-21-2021, 12:41 PM)buran Wrote: [ -> ]what I mean:
import random

def create_torus(max_vert, max_horz, density):
    torus = []
    for x in range(max_vert):
        torus.append([])
        for y in range(max_horz):
            torus[-1].append(int(random.randint(1, 100) < density))
    return torus

def pprint_torus(torus):
    for row in torus:
        print(row)


MAXHORZ = 15
MAXVERT = 10
DENSITY = 50
torus = create_torus(MAXVERT, MAXHORZ, DENSITY)
pprint_torus(torus)
create_torus can be one-lier using list comprehension
def create_torus(max_vert, max_horz, density):
    return [[int(random.randint(1, 100) < density) for y in range(max_horz)] for x in range(max_vert)]
Now, because you say it's [variation of] Conway's Game of Life, I think it will be better to move to OOP/class structure sooner than later.

e.g.

import random

class Torus:
    def __init__(self, max_vert, max_horz, density):
        self._torus = [[int(random.randint(1, 100) < density) for y in range(max_horz)] for x in range(max_vert)]

    @property
    def rows(self):
        return len(self._torus)

    @property
    def columns(self):
        return len(self._torus[0])

    def __repr__(self):
        return f"Torus({self.rows} rows x {self.columns} columns: {', '.join(str(row) for row in self._torus)})"

    def __str__(self):
        return '\n'.join(str(row) for row in self._torus)

if __name__ == '__main__':
    MAXHORZ = 15
    MAXVERT = 10
    DENSITY = 50
    torus = Torus(MAXVERT, MAXHORZ, DENSITY)
    print(torus)
    print(repr(torus))
Note, I am using regular lists, not array.array, but you get the idea. You can use whatever you want. However note that you also don't use array.array (despite what you may think):
torus = array('b') # here torus is array.array
torus = [[0] * MAXHORZ] * MAXVERT # here you bind name torus to different object - list of lists, it is no longer array.array

Also note that star imports lile from array import * are generally considered bad practice and discouraged.

Thanks that was VERY helpful. Bye, from neighbouring Salonica (Solun)
SB