Original list changes despite using independent copies - python

I would like to iterate through all elements of a matrix (in my code a list of lists) and create an independent copy of that matrix everytime, when the checked element meets a certain condition.
Every time a copy is created, I would like to change one of the elements in the copied matrix (so that the original matrix stays the same). Every copy of the matrix should get an individual name.
After that, I would like to store that copied matrix in a list.
So, for example: Consider the original Matrix is a 2x2 Matrix, containing four integers (let's say the numbers 1 to 4, as shown in the code below). Now let's loop through the matrix elements and create a copy of the matrix everytime, when the checked element is larger than 3. So we should get one copy (because only one element, the number 4, is larger than 3). In this copied matrix, I change one of the elements (e.g. let's say adding the number 10 to the element that was checked). Then I store this copied matrix in a list. My code looks like this:
matrix = [[1,2],[3,4]]
new_copies = []
counter = 0
for i in range(0,2):
for k in range(0,2):
if matrix[i][k] > 3:
exec("item%s = matrix[:]" % counter)
exec("item%s[i][k] = matrix[i][k] + 10" % counter)
exec("new_copies.append(item%s)" % counter)
counter += 1
print(matrix)
print(new_copies)
If you run this code, you will see that the copied matrix is changed correctly and also is stored in the list.
But the original matrix also is changed. Why? I only manipulate the copied versions of the matrix, which should be independent from the original, since I follow this principle:
new_matrix = original_matrix[:]

Why it is happening
Lists are mutable objects, that is why even if you are creating a new list object by doing matrix[:], your sublists are still pointing to the same objects...
A first solution
Here is a first workaround:
matrix = [[1, 2], [3, 4]]
new_copies = []
counter = 0
for i in range(0, 2):
sublist = matrix[i][:]
for k in range(0, 2):
if matrix[i][k] > 3:
sublist[k] += 10
counter += 1
new_copies.append(sublist)
print(matrix)
print(new_copies)
Or with lists comprehension
If possible, you could also use list comprehension, in this case that would be:
new_copies = [[(e + 10 if e > 3 else e) for e in l] for l in matrix]
which will give you the same result that my previous proposition
Or with mutable objects
A nice solution would be to use tuple instead of lists, because they are immutable objects. But it won't be possible if you have to modify your matrix along your program.
Or with deepcopy
You could also use the deepcopy method from the copy library...
And you saw me coming...
I have to remind that the use of eval should be avoided if possible...

Related

Why does cycle 'if' delete not all odd numbers from the list (only each second odd number)?

There is a code:
list = [1, 2]
while list[-1]+list[-2] <= 4000000:
list.append(list[-1] + list[-2])
for i in list:
if i % 2 == 1:
print(i)
list.remove(i)
print(list)
print(sum(list))
You shouldn't modify a list (or any container) while iterating through it.
One way to go around it is to use another container,
in_list = [1, 2]
while in_list[-1]+in_list[-2] <= 20:
in_list.append(in_list[-1] + in_list[-2])
print(in_list)
out_list = []
for i in in_list:
if i % 2 != 1:
print(i)
out_list.append(i)
print(out_list)
print(sum(out_list))
This code uses a different approach than yours: it creates the input list, then while iterating it adds the even elements to a new, output list. This has the same effect as removing the odd elements from the input list, however, it doesn't break the iteration by modifying the input list.
Like said in the comments, you shouldn't use built-in names ("list") for your variable names - it will shadow them. Also, when you develop and debug your code it's best to stick to smaller examples. Here I use 20 instead of 4,000,000 - much easier to track and doesn't lose the meaning.

Python - Splitting lists of integers during iteration

I have an array of data where I'm taking a slice through a 3D array of floats and appending the numbers of the elements that satisfy a set of upper and lower bounds (also floats).
The first part of my code contains a nested for loop in the style of the following:
x_lst = []
for i in range(len(x1)):
for x in range(len(floatarray[0,:,0])):
if x1[i] <= floatarray[0,x,0] <= x2[i]:
x_lst.append(x)
#issues after this point
The inner loop compares the data in the array (floatarray[0,x,0]) with the boundaries x1 and x2, returning a list of integers, whilst the outer loop iterates through the values of the boundaries.
The issue I have is that the output (x_lst) is a single list, whereas I've been trying to produce a list of lists, with each sublist corresponding to the iterable i.
e.g.
#Desired output
x_lst = [[5,6,7,13,14],[21,22,23,36,37],[44,45,...
#Actual output
x_lst = [5,6,7,13,14,21,22,23,36,37,44,45,...
I feel like there's a very simple way of doing this, but I've not been able come up with anything that works (such as trying to use x_lst.split() after the append).
Also, any feedback on the question would be great as I'm still fairly new to SO.
It seems the following should work: why not create an intermediate sublist for each i, and add appropriate values to the sublist, then finally add the sublist to the main list?
x_lst = []
for i in range(len(x1)):
inner_list = [] # The sublist to be appended.
for x in range(len(floatarray[0,:,0])):
if x1[i] <= floatarray[0,x,0] <= x2[i]:
inner_list.append(x) # Add the numbers to the sublist.
# Issues after this point...
x_lst.append(inner_list) # Add the sublist to the main list.
Everything appears to be correct in the code except that you append into a 1-d array. For solving your problem you can simply change your code so that a temporary array named temp will append data in inner loop which would then be appended to your outer array x_lst as shown below:
x_lst = []
for i in range(len(x1))
temp=[]
for x in range(len(floatarray[0,:,0])):
if x1[i] <= floatarray[0,x,0] <= x2[i]:
temp.append(x)
x_lst.append(temp);
Okay, so Sam's answer is correct insofar as appending the values through the iteration, but trying it out with my code produced lists of lists of len(i) where each sublist contained all values of x that satisfied all elements of x1 and x2, instead of each list containing values specific to the [i]th element of x1 and x2, thus all the sublists were identical.
I got around this by replacing the inner for loop with a list comprehension, as follows, that gave the right result (in the desired form x_lst = [[5,6,7,13,14],[21,22,23,36,37],[44,45,...):
x_lst = []
for i in range(len(x1)):
y = [x for x in range(len(floatarray[0,:,0])) if x1[i] <= floatarray[0,x,0] <= x2[i]]
x_lst.append(y)

How is this 2D array being sized by FOR loops?

Question background:
This is the first piece of Python code I've looked at and as such I'm assuming that my thread title is correct in explaining what this code is actually trying to achieve i.e setting a 2D array.
The code:
The code I'm looking at sets the size of a 2D array based on two for loops:
n = len(sentences)
values = [[0 for x in xrange(n)] for x in xrange(n)]
for i in range(0, n):
for j in range(0, n):
values[i][j] = self.sentences_intersection(sentences[i], sentences[j])
I could understand it if each side of the array was set with using the length property of the sentences variable, unless this is in effect what xrange is doing by using the loop size based on the length?
Any helping with explaing how the array is being set would be great.
This code is actually a bit redundant.
Firstly you need to realize that values is not an array, it is a list. A list is a dynamically sized one-dimensional structure.
The second line of the code uses a nested list comprehension to create one list of size n, each element of which is itself a list consisting of n zeros.
The second loop goes through this list of lists, and sets each element according to whatever sentences_intersection does.
The reason this is redundant is because lists don't need to be pre-allocated. Rather than doing two separate iterations, really the author should just be building up the lists with the correct values, then appending them.
This would be better:
n = len(sentences)
values = []
for i in range(0, n):
inner = []
for j in range(0, n):
inner.append(self.sentences_intersection(sentences[i], sentences[j]))
values.append(inner)
but you could actually do the whole thing in the list comprehension if you wanted:
values = [[self.sentences_intersection(sentences[i], sentences[j]) for i in xrange(n)] for j in xrange(n)]

A copy of a list is altered on operations on the original list

I was writing a snippet to find the determinant of a matrix. The matrix is represented as a nested list like,
[[2,3],[5,6]]
where the members of the outer list are the rows and that of the inner are the columns.
In a step where the matrix has to be expanded to a simpler one by removing a few elements, I had to save a backup of the original list to another list 'bak' to use it in subsequent expansions.
But after the first expansion step, when i tried to restore the value of the original list from 'bak', it seems the operation on the original list is reflected on 'bak' also.
def determinant(matrix):
if len(matrix)==2:
det = matrix[0][0]*matrix[1][1]-matrix[1][0]*matrix[0][1]
if len(matrix)>2:
flag=0
bak=[]
for x in matrix:
bak.append(x)
dump=[]
for ind,x in enumerate(matrix):
matrix.pop(ind)
for n,y in enumerate(matrix):
matrix[n].pop(ind)
dump.append(matrix)
print "bak",bak
matrix=bak
matrix=[[1,2,3],[4,5,6],[7,8,9]]
determinant(matrix)
On printing 'bak' the output was [[1, 2, 3], [5, 6], [8, 9]] where it was supposed to be [[1,2,3],[4,5,6],[7,8,9]]
Please help me out if I oversaw any concepts.
You are only storing references to the original list. You want to create copies instead:
bak.append(x[:])
or
bak.append(list(x))
The [:] syntax creates a new list from a slice from the first to the last element of the original list.
(You seem like you're in way over your head. Why do you have all these global variables? Get a handle on how functions work properly, first, before you try to do something like this.)
Because it's not really a copy. bak is a separate list-of-lists from matrix, but it contains all the same lists that matrix does.
Furthermore, each time through the outer loop, you would be "restoring" matrix from bak... but not by making a copy, just aliasing it! Thus on the second time through the loop, you end up letting matrix name the same list-of-lists that bak does, and now the intended "backup" purpose is defeated.
The entire approach to this is wrong, though. Stop trying to "make a copy and then modify it repeatedly", and start "repeatedly making modified versions".
And quit trying to tell Python how to put together lists. It knows how.
The code becomes much simpler when we separate out the small tasks into their own functions. First, let's make a function that gives us a list with everything except the specified element:
def all_except(a_list, index):
return a_list[:index] + a_list[index + 1:]
That lets us easily make a function that gives us a matrix with everything except the indicated row and column - by asking for "a copy of the row without the specified column, for each row except the specified one":
def submatrix(matrix, r, c):
return [all_except(row, c) for row in all_except(matrix, r)]
# Alternatively:
# return [all_except(row, c) for i, row in enumerate(matrix) if i != r]
And now we can actually do the recursion. There's no need to actually build the list of minors.
def determinant(matrix):
if len(matrix) < 2: raise ValueError
if len(matrix) == 2:
return matrix[0][0] * matrix[1][1] - matrix[1][0] * matrix[0][1]
return sum(
column * (-1 ** r + c) * determinant(submatrix(matrix, r, c))
for r, row in matrix
for c, column in row
)

How do I get an empty list of any size in Python?

I basically want a Python equivalent of this Array in C:
int a[x];
but in python I declare an array like:
a = []
and the problem is I want to assign random slots with values like:
a[4] = 1
but I can't do that with Python, since the Python list is empty (of length 0).
If by "array" you actually mean a Python list, you can use
a = [0] * 10
or
a = [None] * 10
You can't do exactly what you want in Python (if I read you correctly). You need to put values in for each element of the list (or as you called it, array).
But, try this:
a = [0 for x in range(N)] # N = size of list you want
a[i] = 5 # as long as i < N, you're okay
For lists of other types, use something besides 0. None is often a good choice as well.
You can use numpy:
import numpy as np
Example from Empty Array:
np.empty([2, 2])
array([[ -9.74499359e+001, 6.69583040e-309],
[ 2.13182611e-314, 3.06959433e-309]])
also you can extend that with extend method of list.
a= []
a.extend([None]*10)
a.extend([None]*20)
Just declare the list and append each element. For ex:
a = []
a.append('first item')
a.append('second item')
If you (or other searchers of this question) were actually interested in creating a contiguous array to fill with integers, consider bytearray and memoryivew:
# cast() is available starting Python 3.3
size = 10**6
ints = memoryview(bytearray(size)).cast('i')
ints.contiguous, ints.itemsize, ints.shape
# (True, 4, (250000,))
ints[0]
# 0
ints[0] = 16
ints[0]
# 16
It is also possible to create an empty array with a certain size:
array = [[] for _ in range(n)] # n equal to your desired size
array[0].append(5) # it appends 5 to an empty list, then array[0] is [5]
if you define it as array = [] * n then if you modify one item, all are changed the same way, because of its mutability.
x=[]
for i in range(0,5):
x.append(i)
print(x[i])
If you actually want a C-style array
import array
a = array.array('i', x * [0])
a[3] = 5
try:
[5] = 'a'
except TypeError:
print('integers only allowed')
Note that there's no concept of un-initialized variable in python. A variable is a name that is bound to a value, so that value must have something. In the example above the array is initialized with zeros.
However, this is uncommon in python, unless you actually need it for low-level stuff. In most cases, you are better-off using an empty list or empty numpy array, as other answers suggest.
The (I think only) way to assign "random slots" is to use a dictionary, e.g.:
a = {} # initialize empty dictionary
a[4] = 1 # define the entry for index 4 to be equal to 1
a['French','red'] = 'rouge' # the entry for index (French,red) is "rouge".
This can be handy for "quick hacks", and the lookup overhead is irrelevant if you don't have intensive access to the array's elements.
Otherwise, it will be more efficient to work with pre-allocated (e.g., numpy) arrays of fixed size, which you can create with a = np.empty(10) (for an non-initialized vector of length 10) or a = np.zeros([5,5]) for a 5x5 matrix initialized with zeros).
Remark: in your C example, you also have to allocate the array (your int a[x];) before assigning a (not so) "random slot" (namely, integer index between 0 and x-1).
References:
The dict datatype: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
Function np.empty(): https://numpy.org/doc/stable/reference/generated/numpy.empty.html

Categories