George
George

Reputation: 97

SUM numbers in a nested list

Currently I am trying to sum numbers from two nested loops. I am very close but for some reason, the numbers are not adding properly.

def addTables(table1,table2):
counter = 0
index = 0
table3 = [[None] * len(table1[0])] * len(table1)
print(table3)
for row in table1:
    for i in table1[0]:
        table3[counter][index] = table1[counter][index] + table2[counter][index]
        index = index +1
    index = 0
    counter = counter + 1
    print (table3)

My values are table1 = [[1,2,3],[4,5,6]] and table2 = [[1,2,3],[4,5,6]]. For some reason, it is printing

[[None, None, None], [None, None, None]]
[[2, 4, 6], [2, 4, 6]]
[[8, 10, 12], [8, 10, 12]]

but i want it to print

[[None, None, None], [None, None, None]]
[[2, 4, 6], [None, None, None]]
[[2, 4, 6], [8, 10, 12]]

I think this line is wrong, but I have no idea how to fix it.

table3[counter][index] = table1[counter][index] + table2[counter]

Upvotes: 2

Views: 875

Answers (1)

marcelm
marcelm

Reputation: 1070

The problem is in

table3 = [[None] * len(table1[0])] * len(table1)

Multiplying a list actually copies references to its items; it doesn't duplicate the items in the list. To see what happens, look at your code like this:

subtable = [None] * len(table1[0])]
# subtable = [None, None, None]
table3 = [subtable] * len(table1)
# table3 = [subtable, subtable]

Thus, table3[0] and table3[1] are actually the same list. So when you set table3[0][0], you're actually modifying subtable, which is table3[0] and table3[1]!

You can see this effect for yourself:

>>> table3 = [[None] * 3] * 2
>>> table3
[[None, None, None], [None, None, None]]
>>> table3[0][1] = 5
>>> table3
[[None, 5, None], [None, 5, None]]

You can fix this by using list comprehensions to construct table3:

>>> table3 = [[None for x in table1[0]] for y in table1]
>>> table3
[[None, None, None], [None, None, None]]
>>> table3[0][1] = 5
>>> table3
[[None, 5, None], [None, None, None]]

Alternatively, using list multiplication for the inner list is fine. (This is because the references to None get replaced, while references to a sublist get modified in place):

>>> table3 = [[None] * len(table1[0]) for y in table1]
>>> table3
[[None, None, None], [None, None, None]]
>>> table3[0][1] = 5
>>> table3
[[None, 5, None], [None, None, None]]

This subtlety may be confusing though. Using a nested list comprehension is more explicit.

But personally, I wouldn't construct the list ahead of time like that in the first place. Instead, I would recommend starting with an empty list, and appending to that as you go

table3 = []
for row in table1:
    sumrow = []
    index = 0
    for i in table1[0]:
        sumrow.append(table1[counter][index] + table2[counter][index])
        index += 1
    table3.append(sumrow)
    counter += 1

And, building upon that, it's usually cleaner to iterate over lists directly, rather than iterating over their indices. You can iterate over two equally-sized lists by using zip, like so:

for row1, row2 in zip(table1, table2):
    sumrow = []
    for item1, item2 in zip(row1, row2):
        sumrow.append(item1 + item2)
    table3.append(sumrow)

And once we're there, we could express sumrow as a list comprehension:

for row1, row2 in zip(table1, table2):
    table3.append([item1 + item2 for item1, item2 in zip(row1, row2)])

Note that this pairwise addition in the list comprehension can also be achieved by applying sum to every pair using map:

for row1, row2 in zip(table1, table2):
    table3.append(list(map(sum, zip(row1, row2))))

And then we could also replace the outer for loop by a list comprehension.

table3 = [list(map(sum, zip(row1, row2))) for row1, row2 in zip(table1, table2)]

Which can be slightly improved by using list unpacking for the rows:

table3 = [list(map(sum, zip(*rows))) for rows in zip(table1, table2)]

I'm a little conflicted if this is actually the best / most readable approach, so maybe I should've stopped a few versions ago. But hey, here we are ;)

Upvotes: 3

Related Questions