Mysterious duplicate arrays in Python - python

I am trying to understand why the first for loop does not return what the second for loop is returning. Can anyone explain this clearly to me? My searching did not unearth any results since I don't know what this problem is called.
arr = [1,2,3]
tempArr = []
res = []
for num in range(0,3):
tempArr.append(arr[num])
res.append(tempArr)
print(tempArr, res)
print()
tempArr = []
res = []
for num in range(0,3):
tempArr.append(arr[num])
res.append(list(tempArr))
print(tempArr, res)
Returns:
[1] [[1]]
[1, 2] [[1, 2], [1, 2]]
[1, 2, 3] [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
[1] [[1]]
[1, 2] [[1], [1, 2]]
[1, 2, 3] [[1], [1, 2], [1, 2, 3]]

Lists in Python are mutable sequences. This means that you can modify the elements of a list once it is created.
You are creating a list and assigning it to the variable 'tempArr'. Then in the first loop you are appending 'tempArr' to the 'res' list.
Note that because lists are mutable, you are never changing what 'tempArr' is, you only modify its content, so you are adding three times the same list to 'res'. When you modify the 'tempArr' list, you modify all the lists inside 'res' (again, because they are the same list)
An explicit example you can play with is the following:
# Create a new list
tempArr = []
# Create a list containing the initial one (which is empty)
res = [tempArr]
# Now, lets modify the 'tempArr'
tempArr.append("This wasn't here before")
# Make your prediction on what will happen now:
print(res)
Another thing we can do is play with the 'is' comparison, in your code:
arr = [1,2,3]
tempArr = []
res = []
for num in range(0,3):
tempArr.append(arr[num])
res.append(tempArr)
# Check if the element we have appended into 'res' is the 'tempArr' list. The result is always True.
print(tempArr is res[-1])
print(tempArr, res)
# Let's compare the three elements of the 'res' list and check if they are the same list, we see that they are.
print(res[0] is res[1] is res[2])
On the other hand, in the second loop, you are not appending the 'tempArr' list into 'res', you are first creating a new list by calling "list(tempArr)", and then appending this new list into 'res'. Now we can play a little bit with this:
tempArr = []
res = []
for num in range(0,3):
tempArr.append(arr[num])
res.append(list(tempArr))
# Check if the element we have appended into 'res' is the 'tempArr' list.
print(tempArr is res[-1])
# This time the result is False. So we are not appending 'tempArr', but a new list with the same elements.
print(tempArr, res)
# Let's compare again the three elements of the 'res' list and see if they are the same elements:
print(res[0] is res[1] is res[2])
As a consequence, because in the second loop we are creating new lists, when you modify the initial one, this doesn't affect the elements inside the 'res' list.

list(...) makes a copy. Without making a copy, the same reference is appended, thus subsequent changes show up later in earlier elements as well.
To see the references, do a print(list(map(id, res))). If the printed number is the same, then it's the same list object.

Because we use list() to make shallow copy
This means list(tempArr) will now be a new and independent object with the same contents as tempArr

Related

Recursively defining a list in python, all in the list are replaced by the last item

I've just started working with Python, and I just came across some behavior that I don't understand. I've searched the site for an explanation, but I haven't been able to find it. Perhaps I don't know the right keywords to search.
I (think I)'m trying to define a list recursively, but in stead of repeatedly appending a new item to the list, all items in the list get replaced by this new item. See the snippet below. The code is supposed to generate a list I containing all (ordered) sublists of [0,...,n] of length d.
n = 5
d = 2
def next(S):
m = S.index(min([s for s in S if s+1 not in S]))
for i in range(m):
S[i] = i
S[m] += 1
return S
I = [[i for i in range(d)]]
while I[-1][0] <= n-d:
I += [next(I[-1])]
print(I)
I expected this to return the following list:
[[0,1],[0,2],[1,2],[0,3],[1,3],[2,3],[0,4],[1,4],[2,4],[3,4],[0,5],[1,5],[2,5],[3,5],[4,5]]
But in stead it returns the following list:
[[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5]]
Can anyone point me to an explanation as to why this code does not do what I expect it to? Thanks in advance.
Each call to next should get its own copy of the list; for example
I += [next(I[-1][:])]
Your code was building a list with multiple references to the same list. By sending each call to next its own copy, all of the elements in the result are distinct.
This could also have been accomplished by changing next to build a new list from scratch instead of modifying the list it is passed.
Your function next() alters the list it is given in-place, instead of creating a new copy. This is why all items in I are actually just the same list.
A statement like S = list(S) in next() would be sufficient to replace S with a (shallow) copy and ensure that changes to it are not applied to the original list.
You're passing a reference to an existing list rather than a copy of the list, so the list is modified as you iterate. The following might help explain this behavior:
def modify(li, i=3):
print(li)
li += [i] # This adds to the passed list
if i > 0:
modify(li, i-1)
def modify_copy(li, i=3):
print(li)
li = li + [i] # This overwrites "li" on every call
if i > 0:
modify_copy(li, i-1)
one = [4]
modify(one)
print("Result:", one)
two = [4]
modify_copy(two)
print("Result:", two)
Output
[4]
[4, 3]
[4, 3, 2]
[4, 3, 2, 1]
Result: [4, 3, 2, 1, 0]
[4]
[4, 3]
[4, 3, 2]
[4, 3, 2, 1]
Result: [4]

New list created by slicing old list; modifying 'new' changes 'old' as well?

Summary: I created a new list variable by taking a slice of an original one. When I tweak the new list, the previous one also changes, but I'm fairly certain they are two distinct lists! Here's my general method:
1.Create initial list all_info by calling a function. Each list item is itself a list with 5 elements.
all_info = CrossReference(start_info, RBS_info, promoter_info)
2.Create a new list all_info_mod by slicing the initial list to remove the first item (I need to do this for later code). Then confirm thet are distinct.
all_info_mod = all_info[1:]
all_info #Shows original list
all_info_mod #Shows sliced list
3.Increment the first 3 elements of each outer list by 1. Then check.
for i in all_info_mod:
for j in range(0, 3):
i[j] += 1
all_info_mod #Successfully altered
all_info #Also altered?!
The initial list is also altered when it shouldn't be. I have tested with simpler examples and get the same result. Perhaps it's a result of my previous code that comes before this? (Can provide more details if needed).
Thanks in advance!
You should import copy module and make a deepcopy if you don't want changes in original list.
import copy
all_info_mod = copy.deepcopy(all_info[1:])
When you do this:
all_info = [[1,2,3], [2,3,4], [5,6,7]]
all_info_mod = all_info[1:]
You perform slicing on all_info to create a new list all_info_mod.
The problem is that the elements of all_info are (references to) lists, so all_info_mod will contain the copy of the references to lists. The references are copied, so at the end both lists contain references to the same lists.
This explains why when you modifies the lists referenced by the elements of all_info_mod, you also modify the lists referenced by the elements of all_info.
Note that if you modify directly an element of all_info_mod by assignment, i.e. if you replace the reference to a list with another one, then all_info is not changed:
all_info_mod[1] = [9,9,9]
print(all_info) # [[1, 2, 3], [2, 3, 4], [5, 6, 7]]
print(all_info_mod) # [[2, 3, 4], [9, 9, 9]]
If you really want to duplicate the data in another list, you have to use the method copy.deepcopy(), as follows:
import copy
all_info = ...
all_info_mod = copy.deepcopy(all_info[1:])
This is happening because you're modifying mutable objects that both lists contain references to. Modifying the list you sliced won't change the slice, but modifying a mutable object in that list will also be visible from the other list, because both lists contain the same object.
list_ = [[1, 2, 3], [4, 5, 6]]
slice_ = list_[:]
list_[0] = [7, 8, 9]
print(slice_)
# [[1, 2, 3], [4, 5, 6]]
list_[1].append(0)
print(slice_[1])
# [4, 5, 6, 0]
print(list_[1] is slice_[1]) # The two lists both contain the same list object
# True

Python: why appending to a list results in same values

This is my code:
a = []
res = []
for i in range(0, 3):
a.append(i)
res.append(a)
print(res)
The result is:
[[0, 1, 2], [0, 1, 2], [0, 1, 2]]
But I want the result to be:
[[0], [0, 1], [0, 1, 2]]
I know the solution is using the shallow copy: res.append(a[:]). But can somebody tell me why?
You appended the same thing (a) to res three times, so it appears 3 times. The fact that you changed the contents of a between each call to append doesn't matter. If each call to append was given its own copy of a, then you'd get the result you expect.
when you append the object "a", python actually appends a pointer that points to the original list "a". In the next iteration of the loop you change the original object, so all of the pointers to that object show the latest state of the object.
you can add print the lists in every iteration to see that in action.
What you want is to create a copy of "a", that would remain unchanged, and append the copy to "res". Like you said, using the syntax a[:] would do the job.
When you append a to the res array, you are appending a pointer to the a variable. Not the 'value' of the a variable. So when you are done, the res array has the 'value' - [a, a, a].
When you shallow copy, you are copying the 'value' of the a variable at that stage of the loop into the res array, giving you a 'value' of - [[0],[0,1],[0,1,2]].
a = [] # (1) list created here
res = []
for i in range(0, 3):
a.append(i) # (2) list modified here
res.append(a) # (3) res is modified here
print(res)
What your code is saying is this:
At (1) a list is created and a refers to this list.
At (2) you modify the list from (1), but the list itself remains at the same memory location and a still refers to this list.
At (3) you just make a copy of a reference and add it to res, and still neither a nor the list at (1) change.
The end results is that res gets 3 copies of the reference to the list at (1).
Here is a side effect:
a[1] = 42
print(res)
Output:
[[0, 42, 2], [0, 42, 2], [0, 42, 2]]
You say that you know that this is the code you are after:
a = []
res = []
for i in range(0, 3):
a.append(i)
res.append(a[:]) # (4) new list created
print(res)
At (4) a new list is created whose contents is the same as the list that a refers to. This new list is not referred to by a, but instead, one of the elements of res has a reference to this new list.
Firstly this means that res holds references to lists, which is why they hang around long enough to be printed. Secondly a still refers to the original list.
Here is a side effect:
a[1] = 42
print(res)
Output:
[0, 42, 2] [[0], [0, 1], [0, 1, 2]]
However, this is not the end of the story if you examine this code:
a = []
res = []
for i in range(0, 3):
a.append([i]) # (5) Create a new list with one element
res.append(a[:]) # (6) Shallow copy as above
print(res)
a[1].append(42)
print(a, res)
Output:
[[[0]], [[0], [1]], [[0], [1], [2]]]
[[0], [1, 42], [2]] [[[0]], [[0], [1, 42]], [[0], [1, 42], [2]]]
This occurs because at (6) there was only a shallow copy made.

function that given a list returns a list of list decreasing

I'd make a function in python, that given a list returns a list of list, in which every element is the list given decreased by one.
Input: list_decreaser([0,3,4,5,6,7,8)
Output: [[0,3,4,5,6,7],[0,3,4,5,6],[0,3,4,5],[0,3,4],[0,3],[0]]
My attempt:
def list_decreaser(list):
listresult = []
for x in range(len(list)-1):
list.remove(list[x])
listresult.append(list)
return listresult
The code appends the same list multiple times. It should append copy of the list.
And use del list[..] instead of list.remove(list[..]) to delete an item at specific index.
def list_decreaser(xs):
listresult = []
for i in range(len(xs)-1, 0, -1): # <--- interate backward
del xs[i]
listresult.append(xs[:]) # <----
return listresult
print(list_decreaser([0,3,4,5,6,7,8]))
Or using list comprehension:
>>> xs = [0,3,4,5,6,7,8]
>>> [xs[:i] for i in range(len(xs)-1, 0, -1)]
[[0, 3, 4, 5, 6, 7], [0, 3, 4, 5, 6], [0, 3, 4, 5], [0, 3, 4], [0, 3], [0]]
BTW, don't use list as a variable name. It shadows builtin list function.
The problem is that you're appending the same list over and over again. You keep mutating the list in-place, but you're never creating a new list. So you end up with a list of N references to the same empty list.
This is the same problem discussed in two FAQ questions. I think How do I create a multidimensional list explains it best.
Anyway, what you need to do is append a new list each time through the loop. There are two ways to do that.
First, you can append a copy of the current list, instead of the list itself:
def list_decreaser(list):
listresult = []
for x in range(len(list)-1):
list.remove(list[x])
listresult.append(list[:]) # this is the only change
return listresult
This solves your problem, but it leaves a few new problems:
First, list.remove(list[x]) is a very bad idea. If you give it, say, [0, 1, 2, 0], what happens when you try to remove that second 0? You're calling list.remove(0), and there's no way the list can know you wanted the second 0 rather than the first! The right thing to do is call del list[x] or list.pop(x).
But once you fix that, you're removing the elements from the wrong side. x is 0, then 1, then 2, and so on. You remove element 0, then element 1 (which is the original element 2), then element 2 (which is the original element 4), and eventually get an IndexError. Even if you fixed the "skipping an index" issue (which is also explained in the FAQ somewhere), you'd still be removing the first elements rather than the last ones. You can fix that by turning the range around. However, there's an even easier way: Just remove the last element each time, instead of trying to figure out which x is the right thing, which you can do by specifying -1, or just calling pop with no argument. And then you can use a much simpler loop, too:
def list_decreaser(list):
listresult = []
while list:
list.pop()
listresult.append(list[:])
return listresult
Of course this appends the last, empty list, which you apparently didn't want. You can fix that by doing while len(list) >= 1, or putting an if list: listresult.append(list[:]), or in various other ways.
Alternatively, you can make new truncated lists instead of truncating and copying the same list over and over:
def list_decreaser(list):
listresult = []
while len(list):
list = list[:-1]
listresult.append(list)
return listresult
Note that in this second version, rather than changing the value stored in list, we're creating a new list and storing that new list in list.
use this
def list_decreaser(list1):
listresult = []
for i in list1:
list1 = list[:-1]
listresult.append(list1)
return listresult

Python index usage

Here is some code:
# Question 9: Deep Reverse
# Define a procedure, deep_reverse, that takes as input a list,
# and returns a new list that is the deep reverse of the input list.
# This means it reverses all the elements in the list, and if any
# of those elements are lists themselves, reverses all the elements
# in the inner list, all the way down.
# Note: The procedure must not change the input list.
# The procedure is_list below is from Homework 6. It returns True if
# p is a list and False if it is not.
def is_list(p):
return isinstance(p, list)
#For example,
def deep_reverse(n):
n.reverse()
for entry in n:
if is_list(entry):
entry.reverse()
deep_reverseA(entry)
return n
def deep_reverseA(n):
for entry in n:
if is_list(entry):
entry.reverse()
deep_reverseA(entry)
return n
p = [1, [2, 3, [4, [5, 6]]]]
print deep_reverse(p)
#>>> [[[[6, 5], 4], 3, 2], 1]
print p
#>>> [1, [2, 3, [4, [5, 6]]]]
q = [1, [2,3], 4, [5,6]]
print deep_reverse(q)
#>>> [ [6,5], 4, [3, 2], 1]
print q
#>>> [1, [2,3], 4, [5,6]]
My problem is that once I run the code the values of p and q change. How can I make them not change. I know that in python indexes are connected so if indexA = indexB and you change indexA then indexB will change. That is the problem I am having with fixing this problem.
I'll just tell you the answer right here, right now, with an explanation.
In python, variables are merely pointers to stored objects. So as you said in your post, if you declare foo = bar then foo not only is equal to bar, but foo is bar. This won't change unless you explicitly say so (for example you set bar = 2). So you need a way to make a copy of the original list.
There's this thing in python called list slicing, and I'm sure you heard of it. Basically you can get a portion of the list from indexA to indexB with my_list[indexA:indexB].
But you can also leave these spaces blank. indexA if not specified defaults to 0, and indexB defaults to -1 (the last element of the list).
So my_list[2:] returns all the elements from my_list[2] to my_list[-1]. Likewise, my_list[:3] returns my_list[0] to my_list[3].
Therefore, calling my_list[:] returns an exact copy of my_list, but not the actual list itself. This is what you need to do.
So applying it to your code:
def deep_reverse(n):
ncopy = n[:] #this is the part you need
#rest of function, replace `n` with `ncopy`
return ncopy
Also, do not apply this to deep_reverseA because in that function you are changing the original list in the copied list. You aren't changing the list you input into deep_reverse. If you did apply this to deep_reverseA, the lists wouldn't actually change (you would be returning a reverse of the copy but not the original)

Categories