Python: iterating and modifying nested lists - python

I apologize if this question has been asked before, as it seems to be very basic. Unfortunately, when I searched for my question, I could only find other questions asking how to iterate over a list of lists, and none of these questions touched on the specific behavior I am asking about.
I am aware that in python, when you equate two lists, you are not actually copying that list in memory, just creating a new alais pointing to that list in memory. so something like
listA = [1,2,3]
listB = listA
listB[0] = 5
print(listA) #prints [5,2,3]
makes perfect sense to me.
I also know that you can modify mutable types (like lists) in a for loop, while for other types (like integers), you cannot, and must modify the original list. for example
listA = [1,2,3]
listB = [4,5,6]
for Int in listA:
Int +=1
print(listA) #doesn't work, prints [1,2,3]
for List in [listA,listB]:
List[2] = 100
print(listA) #works, prints [1,2,100]
my problem appeared when I tried to combine these two principles. Here is an example of what I tried to do:
x = [1.2345,0.543895,0.0]
y = [2,3,4]
z = [65.34,3.248578493,1.11111]
for coord in [x,y,z]:
rounded_coord = [round(item,3) for item in coord]
coord = rounded_coord
print(x,y,z) #prints unmodified numbers
In this example, 'coord' is a list, and therefore I should be able to modify it, just like listA in my previous examples, but I can't. I have to use enumerate:
x = [1.2345,0.543895,0.0]
y = [2,3,4]
z = [65.34,3.248578493,1.11111]
coordlist = [x,y,z]
for idx,coord in enumerate(coordlist):
coordlist[idx] = [round(item,3) for item in coord]
print(coordlist)
Why doesn't my original attempt work?

'coord' is a list, and therefore I should be able to modify it...
Almost, but not quite. coord is a variable that stores a reference to the each of the original lists in turn per iteration.
rounded_coord is also a variable that stores a reference to a new list.
Now, doing coord = rounded_coord will make the variable coord point to the same reference as rounded_coord. Meaning, the original contents of coords will remain unchanged while the reference that coord points to changes.
Example:
>>> x = [1, 2, 3, 4, 5]
>>> for l in [x]:
... print(id(l))
... new_l = [1]
... l = new_l
... print(id(l))
...
4309421160
4309421592
By the way, id prints a 10 digit number representing the reference a variable points to. You can see that at the start vs at the end, the reference stored in l changes.

In your example:
x = [1.2345,0.543895,0.0]
y = [2,3,4]
z = [65.34,3.248578493,1.11111]
for coord in [x,y,z]:
rounded_coord = [round(item,3) for item in coord]
coord = rounded_coord
print(x,y,z) #prints unmodified numbers
coord is not a list - it's a pointer to a list in your ad-hoc created [x, y, z] list. Since this is a relatively simple example, here is how this unpacks without a loop:
coord = x
rounded_coord = [round(item,3) for item in coord]
coord = rounded_coord
coord = y
rounded_coord = [round(item,3) for item in coord]
coord = rounded_coord
coord = z
rounded_coord = [round(item,3) for item in coord]
coord = rounded_coord
See a problem with it?
If you really wanted to change the element itself, you'd either have to reference it by index in its container list on replacement (as you've noticed that it works with enumerate) or to replace the value in place:
for coord in [x,y,z]:
coord[:] = [round(item,3) for item in coord] # assuming not overriden __setslice__()

Every time you use a loop of the form:
for item in list:
#do something
The item becomes a reference to each element in the list as it loops through. Using modifiers such as:
item[2] = 5
will work because you are modifying the reference directly.
However, when you try to set the reference equal to another variable:
item = some_other_thing
you are not actually modifying the item, you are modifying the reference to the item, and have therefor never changed the original item, just lost your reference to it.

In all your cases, the loop variable is just an alias for each element in the thing being looped over.
The reason List in your second example works like you want it to, but coord in your third example doesn't, is that in the second example, you're changing an element of the list pointed to by List, and therefore actually updating the underlying data.
In the third example, you're trying to directly change coord, and thus are just updating the alias to point to a different list, instead of using the alias to access the underlying array and changing it. As a result, coord now just points to a different list. Consider this modified version of your second example:
for List in [listA,listB]:
List = [7,8,9]
print(listA) # prints [1,2,3]; underlying data not changed
print(List) # prints [7,8,9]; List now points to the new array

To modify elements of the list, you need to provide the index and then modify. In other words, this will not modify anything in the list:
arr=[1,2,3]
for x in arr:
x+=1 #not modifying the values in the list
print(x) #this will print 2,3,4
print(arr) #prints [1,2,3]
So even though you incremented the values but you did not replace the values in the list
What you need to do is, use the index to change the values
arr=[1,2,3]
for x in range(len(arr)): #len(arr) evaluates to 3 and range(3) makes an array of numbers from 0 up to but not including 3
arr[x]=arr[x]+1
print[arr] #Now prints [2,3,4]

The issue is that assignment never mutates. It merely creates a new reference, or reassigns a reference (which is what you are doing). You have to use a mutator method, so for example:
In [1]: x = [1.2345,0.543895,0.0]
...: y = [2,3,4]
...: z = [65.34,3.248578493,1.11111]
...: for coord in [x,y,z]:
...: coord[:] = [round(item,3) for item in coord]
...: print(x,y,z)
...:
[1.234, 0.544, 0.0] [2, 3, 4] [65.34, 3.249, 1.111]
So, even though coord[:] = [round(item,r) for item in cord] looks like a regular assignment, it is not. It is syntactic sugar for calling a mutator method, __setitem__ to be precise:
In [2]: coord
Out[2]: [65.34, 3.249, 1.111]
In [3]: coord.__setitem__(slice(None,None), ['foo','bar','baz'])
In [4]: coord
Out[4]: ['foo', 'bar', 'baz']
Similarly, List[0] = 100 is not assignment, it is syntactic sugar for:
In [6]: List
Out[6]: [1, 2, 3]
In [7]: List[0] = 99
In [8]: List
Out[8]: [99, 2, 3]
In [9]: List.__setitem__(0, 'foo')
In [10]: List
Out[10]: ['foo', 2, 3]
You can't modify immutable types because, by definition, they have no mutator methods, even though there are operations that look like mutator methods:
x = 10
x += 10 # looks like a mutator method but actually assigns a new object to x
Note, a str is similar to a list. You can slice it, but the corresponding mutator method doesn't exist:
In [17]: s = 'abcdefg'
In [18]: s[:3]
Out[18]: 'abc'
In [19]: s.__getitem__(slice(0,3))
Out[19]: 'abc'
In [20]: s.__setitem__
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-20-44ee26769121> in <module>()
----> 1 s.__setitem__
AttributeError: 'str' object has no attribute '__setitem__'

Your first example did not work because coord is only a reference to to the lists x, y, and z. When you reassign coord, you are overwriting the original reference and setting coord to reference a new list object.
In other words coord is not a pointer to each list. Rather, it is only a reference. And when you reassign coord, you lose the original reference.
You can remedy this problem by mutating the list object coord is referencing:
>>> x = [1.2345, 0.543895, 0.0]
>>> y = [2, 3, 4]
>>> z = [65.34, 3.248578493, 1.11111]
>>>
>>> for coord in [x, y, z]:
coord[:] = [round(item,3) for item in coord]
>>> x
[1.234, 0.544, 0.0]
>>> y
[2, 3, 4]
>>> z
[65.34, 3.249, 1.111]
>>>

Related

How to update list with function (with return value) in python

I am implementing few list methods manually like append(), insert(), etc. I was trying to add element at the end of list (like append method). This the working code i am using:
arr = [4,5,6]
def push(x, item):
x += [item]
return x
push(arr,7)
print(arr) #Output: [4,5,6,7]
But when I am implementing same code with little difference. I am getting different output.
arr = [4,5,6]
def push(x, item):
x = x + [item]
return x
push(arr,7)
print(arr) #Output: [4,5,6]
And I am facing same for insert method. Here is code for insert method:
arr = [4,5,7,8]
def insert(x, index, item):
x = x[:index] + [item] + x[index:]
return x
insert(arr,2,6)
print(arr) #Output: [4,5,7,8]
I know I can store return value to the list by arr=insert(arr,2,6) but I want an alternative solution, that list automatically gets update after calling function like in my first code sample.
Edit 1:
I think x[index:index] = [item] is better solution for the problem.
x += [item] and x = x + [item] are not a little difference. In the first case, you are asking to make a change to the list referenced by x; this is why the result reflects the change. In the second, you are asking to have x reference a new list, the one made by combining x's original value and [item]. Note that this does not change x, which is why your result is unchanged.
Also note that your return statements are irrelevant, since the values being returned are ignored.
In your first example you mutated(a.k.a changed) the list object referred to by x. When Python sees x += [item] it translates it to:
x.__iadd__([item])
As you can see, we are mutating the list object referred to by x by calling it's overloaded in-place operator function __iadd__. As already said, __iadd__() mutates the existing list object:
>>> lst = [1, 2]
>>> lst.__iadd__([3])
[1, 2, 3]
>>> lst
[1, 2, 3]
>>>
In your second example, you asked Python to assign x to a new reference. The referenced now referrer to a new list object made by combining (not mutating) the x and [item] lists. Thus, x was never changed.
When Python sees x = x + [item] it can be translated to:
x = x.__add__([item])
The __add__ function of lists does not mutate the existing list object. Rather, it returns a new-list made by combing the value of the existing list and the argument passed into __add__():
>>> lst = [1, 2]
>>> lst.__add__([3]) # lst is not changed. A new list is returned.
[1, 2, 3]
>>>
You need to return the the result of the version of push to the arr list. The same goes for insert.
You can assign to a slice of the list to implement your insert w/o using list.insert:
def insert(x, index, item):
x[:] = x[:index] + [item] + x[index:]
this replaces the contents of the object referenced by x with the new list. No need to then return it since it is performed in-place.
The problem is that you haven't captured the result you return. Some operations (such as +=) modify the original list in place; others (such as x = x + item) evaluate a new list and reassign the local variable x.
In particular, note that x is not bound to arr; x is merely a local variable. To get the returned value into arr, you have to assign it:
arr = push(arr, 7)
or
arr = insert(arr, 2, 6)
class DerivedList(list):
def insertAtLastLocation(self,obj):
self.__iadd__([obj])
parameter=[1,1,1]
lst=DerivedList(parameter)
print(lst) #output[1,1,1]
lst.insertAtLastLocation(5)
print(lst) #output[1,1,1,5]
lst.insertAtLastLocation(6)
print(lst) #output[1,1,1,5,6]
You can use this code to add one element at last position of list
class DerivedList(list):
def insertAtLastLocation(self,*obj):
self.__iadd__([*obj])
parameter=[1,1,1]
lst=DerivedList(parameter)
print(lst) #output[1,1,1]
lst.insertAtLastLocation(5)
print(lst) #output[1,1,1,5]
lst.insertAtLastLocation(6,7)
print(lst) #output[1,1,1,5,6,7]
lst.insertAtLastLocation(6,7,8,9,10)
print(lst) #output[1,1,1,5,6,7,8,9,10]
This code can add multiple items at last location

Understanding *x ,= lst

I'm going through some old code trying to understand what it does, and I came across this odd statement:
*x ,= p
p is a list in this context. I've been trying to figure out what this statement does. As far as I can tell, it just sets x to the value of p. For example:
p = [1,2]
*x ,= p
print(x)
Just gives
[1, 2]
So is this any different than x = p? Any idea what this syntax is doing?
*x ,= p is basically an obfuscated version of x = list(p) using extended iterable unpacking. The comma after x is required to make the assignment target a tuple (it could also be a list though).
*x, = p is different from x = p because the former creates a copy of p (i.e. a new list) while the latter creates a reference to the original list. To illustrate:
>>> p = [1, 2]
>>> *x, = p
>>> x == p
True
>>> x is p
False
>>> x = p
>>> x == p
True
>>> x is p
True
It's a feature that was introduced in Python 3.0 (PEP 3132). In Python 2, you could do something like this:
>>> p = [1, 2, 3]
>>> q, r, s = p
>>> q
1
>>> r
2
>>> s
3
Python 3 extended this so that one variable could hold multiple values:
>>> p = [1, 2, 3]
>>> q, *r = p
>>> q
1
>>> r
[2, 3]
This, therefore, is what is being used here. Instead of two variables to hold three values, however, it is just one variable that takes each value in the list. This is different from x = p because x = p just means that x is another name for p. In this case, however, it is a new list that just happens to have the same values in it. (You may be interested in "Least Astonishment" and the Mutable Default Argument)
Two other common ways of producing this effect are:
>>> x = list(p)
and
>>> x = p[:]
Since Python 3.3, the list object actually has a method intended for copying:
x = p.copy()
The slice is actually a very similar concept. As nneonneo pointed out, however, that works only with objects such as lists and tuples that support slices. The method you mention, however, works with any iterable: dictionaries, sets, generators, etc.
You should always throw these to dis and see what it throws back at you; you'll see how *x, = p is actually different from x = p:
dis('*x, = p')
1 0 LOAD_NAME 0 (p)
2 UNPACK_EX 0
4 STORE_NAME 1 (x)
While, the simple assignment statement:
dis('x = p')
1 0 LOAD_NAME 0 (p)
2 STORE_NAME 1 (x)
(Stripping off unrelated None returns)
As you can see UNPACK_EX is the different op-code between these; it's documented as:
Implements assignment with a starred target: Unpacks an iterable in TOS (top of stack) into individual values, where the total number of values can be smaller than the number of items in the iterable: one of the new values will be a list of all leftover items.
Which is why, as Eugene noted, you get a new object that's referred to by the name x and not a reference to an already existing object (as is the case with x = p).
*x, does seem very odd (the extra comma there and all) but it is required here. The left hand side must either be a tuple or a list and, due to the quirkiness of creating a single element tuple in Python, you need to use a trailing ,:
i = 1, # one element tuple
If you like confusing people, you can always use the list version of this:
[*x] = p
which does exactly the same thing but doesn't have that extra comma hanging around there.
You can clearly understand it from below example
L = [1, 2, 3, 4]
while L:
temp, *L = L
print(temp, L)
what it does is, the front variable will get the first item every time and the remaining list will be given to L.
The output will look shown below.
1 [2, 3, 4]
2 [3, 4]
3 [4]
4 []
Also look at below example
x, *y, z = "python"
print(x,y,z)
In this both x,z will get each one letter from the string meaning first letter is assigned to x and the last letter will be assigned to z and the remaining string will be assigned to variable y.
p ['y', 't', 'h', 'o'] n
One more example,
a, b, *c = [0,1,2,3]
print(a,b,c)
0 1 [2,3]
Boundary case: If there is nothing remaining for star variable then it will get an empty list.
Example:
a,b=[1]
print(a,b)
1 []

Copy values from one list to another without altering the reference in python

In python objects such as lists are passed by reference. Assignment with the = operator assigns by reference. So this function:
def modify_list(A):
A = [1,2,3,4]
Takes a reference to list and labels it A, but then sets the local variable A to a new reference; the list passed by the calling scope is not modified.
test = []
modify_list(test)
print(test)
prints []
However I could do this:
def modify_list(A):
A += [1,2,3,4]
test = []
modify_list(test)
print(test)
Prints [1,2,3,4]
How can I assign a list passed by reference to contain the values of another list? What I am looking for is something functionally equivelant to the following, but simpler:
def modify_list(A):
list_values = [1,2,3,4]
for i in range(min(len(A), len(list_values))):
A[i] = list_values[i]
for i in range(len(list_values), len(A)):
del A[i]
for i in range(len(A), len(list_values)):
A += [list_values[i]]
And yes, I know that this is not a good way to do <whatever I want to do>, I am just asking out of curiosity not necessity.
You can do a slice assignment:
>>> def mod_list(A, new_A):
... A[:]=new_A
...
>>> liA=[1,2,3]
>>> new=[3,4,5,6,7]
>>> mod_list(liA, new)
>>> liA
[3, 4, 5, 6, 7]
The simplest solution is to use:
def modify_list(A):
A[::] = [1, 2, 3, 4]
To overwrite the contents of a list with another list (or an arbitrary iterable), you can use the slice-assignment syntax:
A = B = [1,2,3]
A[:] = [4,5,6,7]
print(A) # [4,5,6,7]
print(A is B) # True
Slice assignment is implemented on most of the mutable built-in types. The above assignment is essentially the same the following:
A.__setitem__(slice(None, None, None), [4,5,6,7])
So the same magic function (__setitem__) is called when a regular item assignment happens, only that the item index is now a slice object, which represents the item range to be overwritten. Based on this example you can even support slice assignment in your own types.

How to work with one List on few Classes without effects on that list

I'm comparing a few algorithms.Each algorithms(class) received the same list. The problem is that first class affect on list and other classes cannot work on that list.
Is there any clever way to do this?
Here is a code:
Lista = []
start = 54
for i in range(25):
liczba = random.randint(1,179)
if liczba not in Lista and liczba != start:
Lista.append(liczba)
else:
i -= 1
print "Lista: ", Lista
x = SSTF(Lista)
x.Symulacja(91) #<----- OK!
y = FCFS(Lista)
y.Symulacja(25) #<----- FCFS received epty list.
z = SCAN()
z.Symulacja(start, Lista)
w = C_SCAN()
w.Symulacja(start, Lista)
results = Result()
results.Add(x)
results.Add(y)
print Results
SSTF is removing elements from list which received, FCFS the same. So after doing SSTF algorithm FCFS reveived empty list. I can't understand why this list is affected. I'm not working on List "Lista", but in init of SSTF i'm assigning "Lista" to other list.
Sorry if my problem is not a clear. I'm learning python and that problem hits me many times.
x = SSTF(Lista[:])
y = FCFS(Lista[:])
....
etc...
Your problem:
def SSTF(Lista):
Listb = Lista
means that Listb is still the same Lista, because this is a reference to the same object.
To avoid it, copy the list entirely, using [:] slice notation (Python dictionaries have the .copy() method, which is clearer).
Basically, any time you copy a reference to an object from one variable to another variable, you are ending up with two pointers to the same object so changes to that object will be seen by both variable names. Yeah, sorry about the terminology.
The pointer is shown by the id() code below. Notice how li == li2 != li3
>>>li = [1,2]
>>>li2 = li
>>>li3 = li[:]
>>>li2.append(3)
>>>print "li:", id(li), li
>>>print "li2:", id(li2), li2
>>>print "li3:", id(li3), li3
li: 4385880760 [1, 2, 3]
li2: 4385880760 [1, 2, 3]
li3: 4385924376 [1, 2]
This is not the case with things like numbers or strings. Check out also the concept of 'immutable'.
>>> a = "xxx"
>>> b = a
>>> b = b +"y"
>>> print a, b
xxx xxxy
>>> a = 1
>>> b = a
>>> b =b+1
>>> print a, b
1 2
If you need to copy your custom classes' instances, look into the copy module, but keep in mind that instance attributes are either shared or copied, depending on using copy.copy(x) or copy.deepcopy(x). In practice, this is rarely necessary, but I got bitten many times by Lista type issues on built-in collection classes before I learned my lesson.

Best method to clear list and dictionary

Code:
num_of_iterations_for_outer_loop = 2
num_of_iterations_for_inner_loop = 3
data = []
for j in range(num_of_iterations_for_outer_loop):
lst2 = []
for k in range(num_of_iterations_for_inner_loop):
lst1 = {}
lst1['1'] = "first_value" # these are not default, in my original code they are coming from some other values after calculation
lst1['2'] = "second_value"
lst1['3'] = "third_value"
lst2.append(lst1)
value = {'values':lst2}
data.append(value)
In the outer loop I have to clear lst2 list again and again for reusing it.
In the inner loop I have to clear lst1 dictionary again and again for reusing it.
I know 2 methods of clearing a list:
del lst2[:]
lst2 = []
and 1 method for clearing a dictionary:
lst1 = {}
But I don't know differences between these methods and which one I should use.
Is there any other method to clear the list and dictionary? And is that better method, and why?
Is there any better method to write the whole code?
Here is a brief idea of what each method does:
lst2 = [] does not actually clear the contents of the list. It just creates a new empty list and assign it to the name lst2. Then it is the task of GC to delete the actual contents based on the remaining references.
>>> a = [1,2,3]
>>> b = a
>>> a = []
>>> a
[]
>>> b
[1, 2, 3]
del lst2[:] clears the contents of the list in-place.
>>> a = [1,2,3]
>>> b = a
>>> del a[:]
>>> a
[]
>>> b
[]
Note that the other list b also gets cleared because the contents are deleted in-place.
lst1 = {} is same as point 1. It creates an empty dictionary and assigns the reference to the name lst1.
Another method to clear the list in-place is:
lst2[:] = []
The differences between your two methods of "clearing" the list is that the first operates in-place, deleting the contents of the original list object, while the second simply creates a new list object and assigns it to the same name. You must use the second version here, otherwise all of the lists in your dictionary will be copies of the same emptied list at the end:
>>> l = [1, 2, 3]
>>> d = {'a': l}
>>> del l[:]
>>> d
{'a': []}
Similarly, you can del all the key-value pairs from a dictionary, but you would have the same problem. As you are putting the original container objects into some other container, you should create a new container by assignment (e.g. lst1 = {}) in each loop.
Is there any better method to write the whole code ?
I don't know what else you're doing with the data, but the dictionary value may be unnecessary, and a three-tuple of the innermost data could be sufficient:
data = [[(1, 2, 3), (4, 5, 6)], [(7, 8, 9), ...], ...]
Assignments are pretty much good and efficient methods to clear list or dict.
my_dict = {}
and my_list = []
Nothing fancy needed.

Categories