Failed to add element to nested lists - python

Background (not the subject of the question):
I am trying to solve the problem of generating all paths of length 3 from a list of all paths of length two in a list (p2 in the code below).
To that end I want to store all pairs of indices (i,j) for which p2[i][1]==p2[j][0] and `p2[i][2]==p2[j][1] (a path of length 2 is defined by a sequence of 3 vertices)
Question:
I tried store the indices in 2D lists whose entries are initially empty list that are appended newly encountered indices, but what is generated is a 2D list whose entries are lists that contain all indices.
Can someone explain what is actually going on behind the curtains and how to correctly add the indices to the innermost lists?
# -*- coding: utf-8 -*-
"""
Created on Sun May 29 10:10:40 2022
#author: Manfred Weis
"""
# generare all unsorted 3-tuples
from itertools import permutations
n=4
p2 = list(permutations(range(n),3))
print('p2:')
print(p2)
print()
N = len(p2)
Rows = [[[]]*n]*n # create 3D list
Cols = [[[]]*n]*n # create 3D list
for i in range(N):
print(str(i)+': Rows['+str(p2[i][1])+']['+str(p2[i][2])+']')
print(str(i)+': Cols['+str(p2[i][0])+']['+str(p2[i][1])+']')
(Rows[p2[i][1]][p2[i][2]]).append(i) # add to the most nested list
(Cols[p2[i][0]][p2[i][1]]).append(i) # add to the most nested list
for i in range(n):
for j in range(n):
print(str(i)+','+str(j)+':')
print(str(Rows[i][j]))
print(str(Cols[i][j]))
print()

You are having a shallow copy problem when you initialize your Rows and Cols 3D lists. Instead of using multiplication, you need to initialize them like so:
for k in range(n):
Rows.append([])
Cols.append([])
for l in range(n):
Rows[k].append([])
Cols[k].append([])
The problem with doing multiplication in order to initialize the 3D list of lists is that all n lists inside are all pointing to the same list, so when you append to one of them, you append to all of them, since they are technically only shallow copies of each other. Therefore, when you print them at the end, all the lists have all the numbers added to them, since they all point to the same place in memory. By initializing the 3D list using the method above, you are creating separate lists in separate locations in memory and you are inserting each of them into your Rows and Cols 3D lists.
Check out what happens when you do the following:
x = [[]]*2
x[0].append(5)
Output:
[[5], [5]]
But if you do the following, you solve the problem:
x = [[],[]]
x[0].append(5)
Output:
[[5], []]
Here are some other people's explanations which are better than mine:
List of lists changes reflected across sublists unexpectedly
Edit:
Here is a better more pythonic way to initialize Rows and Cols:
Rows = [[ [] for j in range(n)] for i in range(n)]
Cols = [[ [] for j in range(n)] for i in range(n)]

Related

Calling indices in an expression while iterating over a variable number of indices (Python)

I want to iterate over a variable number of indices given by the length of a list, using the values on the list as the ranges. Further, I want to call the indices in my expression.
For example, if I have a list [2,4,5], I would want something like:
import itertools
for i0, i1, i2 in itertools.product(range(2),range(4),range(5)):
otherlist[i0]**i0 + otherlist[i2]**i2
The closest I can get is
for [i for i in range(len(mylist))] in itertools.product(*[range(i) for i in mylist]):
But I don't know how to call the indices from here.
You were so close already. When you use a for statement, I find it best to
keep the target list simple and access components of the target list in
the for-loop; in this code, the tuples generated by product().
import itertools
mylist = [2,4,5]
otherlist = list(range(5))
for t in itertools.product(*[range(i) for i in mylist]):
print(otherlist[t[0]]**t[0] + otherlist[t[2]]**t[2])
# 2
# 2
# 5
# 28
# 257
# ...

Find indexes of common items in two python lists

I have two lists in python list_A and list_B and I want to find the common item they share. My code to do so is the following:
both = []
for i in list_A:
for j in list_B:
if i == j:
both.append(i)
The list common in the end contains the common items. However, I want also to return the indexes of those elements in the initial two lists. How can I do so?
It is advised in python that you avoid as much as possible to use for loops if better methods are available. You can efficiently find the common elements in the two lists by using python set as follows
both = set(list_A).intersection(list_B)
Then you can find the indices using the build-in index method
indices_A = [list_A.index(x) for x in both]
indices_B = [list_B.index(x) for x in both]
Instead of iterating through the list, access elements by index:
both = []
for i in range(len(list_A)):
for j in range(len(list_B)):
if list_A[i] == list_B[j]:
both.append((i,j))
Here i and j will take integer values and you can check values in list_A and list_B by index.
You can also get common elements and their indexes with numpy.intersect1d()
common_elements, a_indexes, b_indexes = np.intersect1d(a, b, return_indices=True)

Updating list of lists at iteration

I've a list of lists and I want to update each list at iteration.
I initialized my list as follows:
my_list = [[0]*n]*n
When I want to update the inner lists, by something like:
for i in range(something):
for j in range(anotherthing):
my_list[i][j] = something
What happens is that the whole list is updated rather than the ith list only at each iteration, e.g. [[1,2], [1,2]].
What I want is at the first iteration to be [[1,2], [0,0]] as I initialized it, and in to be [[1,2], [values]]. What am I doing wrong?
The list multiplication operator * does not create copies, but creates multiple references to the same data. Instead of using * consider using comprehensions for initializing your list:
my_list = [[0 for i in range(n)] for j in range(n)]
Use this to initialize your list of lists and it will work fine.
x = [[0]*n for i in range(n)]
The original code creates a list of sublists too but every single one of the sublists reference the same object.
Note: I am using Python3. If you are using 2, you might need to use xrange() instead of range().

Making a list comprehension for a dict within a list within a list

I need to create a list comprehension that extracts values from a dict within a list within a list, and my attempts so far are failing me. The object looks like this:
MyList=[[{'animal':'A','color':'blue'},{'animal':'B','color':'red'}],[{'animal':'C','color':'blue'},{'animal':'D','color':'Y'}]]
I want to extract the values for each element in the dict/list/list so that I get two new lists:
Animals=[[A,B],[C,D]]
Colors=[[blue,red],[blue,Y]]
Any suggestions? Doesn't necessarily need to use a list comprehension; that's just been my starting point so far. Thanks!
Animals = [[d['animal'] for d in sub] for sub in MyList]
Colors = [[d['color'] for d in sub] for sub in MyList]
Gives the desired result:
[['A', 'B'], ['C', 'D']]
[['blue', 'red'], ['blue', 'Y']] # No second 'red'.
What I have done here is take each sub-list, then each dictionary, and then access the correct key.
In a single assignment (with a single list comprehension, and the help of map and zip):
Colors, Animals = map(list,
zip(*[map(list,
zip(*[(d['color'], d['animal']) for d in a]))
for a in MyList]))
If you are fine with tuples, you can avoid the two calls to map => list
EDIT:
Let's see it in some details, by decomposing the nested comprehension.
Let's also assume MyList have m elements, for a total of n objects (dictionaries).
[[d for d in sub] for sub in MyList]
This would iterate through every dictionary in the sublists. For each of them, we create a couple with its color property in the first element and its animal property in the second one:
(d['color'], d['animal'])
So far, this will take time proportional to O(n) - exatly n elements will be processed.
print [[(d['color'], d['animal']) for d in sub] for sub in MyList]
Now, for each of the m sublists of the original list, we have one list of couples that we need to unzip, i.e. transform it into two lists of singletons. In Python, unzip is performed using the zip function by passing a variable number of tuples as arguments (the arity of the first tuple determines the number of tuples it outputs). For instance, passing 3 couples, we get two lists of 3 elements each
>>> zip((1,2), (3,4), (5,6)) #Prints [(1, 3, 5), (2, 4, 6)]
To apply this to our case, we need to pass array of couples to zip as a variable number of arguments: that's done using the splat operator, i.e. *
[zip(*[(d['color'], d['animal']) for d in sub]) for sub in MyList]
This operation requires going through each sublist once, and in turn through each one of the couples we created in the previous step. Total running time is therefore O(n + n + m) = O(n), with approximatively 2*n + 2*m operations.
So far we have m sublists, each one containing two tuples (the first one will gather all the colors for the sublist, the second one all the animals). To obtain two lists with m tuples each, we apply unzip again
zip(*[zip(*[(d['color'], d['animal']) for d in sub]) for sub in MyList]
This will require an additional m steps - the running time will therefore stay O(n), with approximatively 2*n + 4*m operations.
For sake of simplicity we left out mapping tuples to lists in this analysis - which is fine if you are ok with tuples instead.
Tuples are immutable, however, so it might not be the case.
If you need lists of lists, you need to apply the list function to each tuple: once for each of the m sublists (with a total of 2*n elements), and once for each of the 2 first level lists, i.e. Animals and Colors, (which have a total of m elements each). Assuming list requires time proportional to the length of the sequence it is applied to, this extra step requires 2*n + 2*m operations, which is still O(n).

making list inside a list using python

So I have two lists:
shape = [1,2,4]
board = [0,0,1,0,0,0,1,1,1]
I want to assign the board index to the shape index using a for loop so that i can put any shape size. I want the output of the shape to be [0,0,1].
I have tried doing:
list1 = []
for x in shape:
list1.append(board[x])
But the output it gives me is [0,1,0].
I want the output to be [0,0,1], the first three index of the board.
You are currently using
list1 = []
for x in shape:
list1.append(board[x])
This code means to make list1 equal to [board[1], board[2], board[4]], since you start with the empty list and then append board[x] for each x in shape (and since list indices start at 0 in Python). You probably want something like
list1 = []
for i in xrange(len(shape)):
list1.append(board[i])
Now i ranges from 0 to 2, as desired.
So what you mean is that the numbers in shape make no difference? You just want the first len(shape) items in board regardless of the numbers in shape?
In that case:
board[0:len(shape)]
list indices are 0-based, the items of board at indices [1,2,4] are infact [0,1,0].
if you want to use 1-based indices you need to adjust them:
list1 = [board[i-1] for i in shape]

Categories