Functions and Mutable lists in Python - python

sorry I'm new to python and still trying to wrap my head around few fundamentals. I know lists are mutable objects in python, but can't understand how the two functions below handle lists, and why one changes the list itself and the other doesn't
def f(x, y):
x.append(x.pop(0))
x.append(y[0])
return x
>>> a=[1,2,3]
>>> b=[1,2]
>>> f(a,b)
>>> [2, 3, 1, 1]
>>> a
>>> [2, 3, 1, 1]
def f(x, y):
y = y + [x]
return y
>>> a=[1,2,3]
>>> f(4,a)
>>> [1, 2, 3, 4]
>>> a
>>> [1, 2, 3]
Thank you

The + operation stores the result in a new list, while append operation appends the new value into the existing list
For example,
def f(x, y):
y = y + [x]
print(y)
y.append(x)
print(y)
a=[1,2,3]
f(4,a)
print(a)
gives
[1, 2, 3, 4]
[1, 2, 3, 4, 4]
[1, 2, 3]
y = y + [x] creates a new list y & hence the change is not reflected to the original list a & also the later append changes the new list y
but
def f(x, y):
y.append(x)
print(y)
y = y + [x]
print(y)
a=[1,2,3]
f(4,a)
print(a)
output
[1, 2, 3, 4]
[1, 2, 3, 4, 4]
[1, 2, 3, 4]
Here the append operation changes the original list a after that new list y is created

Simply put, the difference is in the function of append and +. append adds a new item to the list without creating a new list. + merges two lists together and creates a new list.
For more information, see DNS's answer here.

Related

How do I make a new array from values and arrays, without the new array containing an array

I have these variables:
x = 1
y = [2, 3, 4]
z = 5
I want to add them all to a new array (something like this):
a = [x, y, z]
Now a is [1, [2, 3, 4], 5]
However, I want a to be [1, 2, 3, 4, 5]
What's the most concise way to accomplish that?
You can convert x and z into lists and then chain them all together like this;
a = [x] + y + [z]
Or in Python 3.5+, you can unpack y as you build the list, like this:
a = [x, *y, z]

Why does this function return different results?

Can someone explain to me why this function returns different results:
def g(x, z):
x.append(z)
return x
y = [1, 2, 3]
g(y, 4).extend(g(y[:], 4))
y = [1, 2, 3]
g(y[:], 4).extend(g(y, 4))
The first returns
[1, 2, 3, 4, 1, 2, 3, 4, 4]
and the second
[1, 2, 3, 4]
In both cases, None is returned, because list.extend() extends the list in-place. So you must be looking at what y ends up as. And that's where the rub is; you didn't extend y itself in the second example.
In the first example, you essentially do this:
y.append(4) # y = [1, 2, 3, 4]
temp_copy = y[:] # temp_copy = [1, 2, 3, 4]
temp_copy.append(4) # temp_copy = [1, 2, 3, 4, 4]
y.extend(temp_copy) # y = [1, 2, 3, 4, 1, 2, 3, 4, 4]
del temp_copy
print(y)
The temp_copy name is never really created; the list is only available on the stack and briefly as x inside g(), which is why I delete temp_copy again at the end to make this clear.
So y is first appended to, then extended with another list (which happens to be a copy of y with another element added).
In your second example, you do this instead:
temp_copy = y[:] # temp_copy = [1, 2, 3]
temp_copy.append(4) # temp_copy = [1, 2, 3, 4]
y.append(4) # y = [1, 2, 3, 4]
temp_copy.extend(y) # temp_copy = [1, 2, 3, 4, 1, 2, 3, 4]
del temp_copy
print(y)
You appended one element to y, and all other manipulations apply to a copy. The copy is discarded again, because in your code there is no reference to it.
You made kind of a mess with assignments and copies there. Note that:
append() modifies the list in-place, without creating a new one
so does extend()
y[:] does create a new list
Your expressions return None. You only effect modifications to the lists, you don't save references to new ones.
Let me "unroll" your code, to show the difference:
# First snippet:
y = [1, 2, 3]
y.append(4)
y_copy = list(y)
y_copy.append(4)
y.extend(y_copy)
# Second snippet:
y = [1, 2, 3]
y_copy = list(y)
y_copy.append(4)
y.append(4)
y_copy.extend(y)
As you can see, in the second example, you apply most modifications to the copy, not the original. In the first, all changes go to the original.
On a subjective note, that code piece was very hard to understand. You wrote it yourself and couldn't follow it, and I have years of experience in Python and still had to pull the "unrolling" trick. Try to keep your code simpler, so that objects can be followed and reasoned about.
On the first call you pass the list by reference, on the second call you made a copy of the list(sublisting it).
explain :
>>> one = [1,2,3]
>>> ref = one
>>> copy = one[:]
>>> one
[3, 2, 3]
>>> ref
[3, 2, 3]
>>> copy
[1, 2, 3]

getting the difference between lists, keeping non-unique values

I am looking to get the difference between two lists.
I am looking for a solution like this only I want to consider non-unique list values.
x = [1, 1, 2, 2, 3]
y = [1, 2]
# i want
z = x - y
# z = [1, 2, 3]
The solution above my code turns the two lists into sets which allows the - operator to work on them but removes non unique values from the lists. Can you think of a simple 'one liner' solution that does what im looking for?
You could use collections.Counter to find the counts of elements in each list. Then you can take the difference and reconstruct a list from the results.
>>> from collections import Counter
>>> x = [1, 1, 2, 2, 3]
>>> y = [1, 2]
>>> [k for _ in range(v) for k,v in (Counter(x) - Counter(y)).iteritems()]
[1, 2, 3]
The drawback being, the order of the result has no real correlation with the order of the input lists. The fact that the result shown above looks sorted is implementation-dependent and possibly only a coincidence.
might not be fancy, first thing that came to mind..
x=[1,1,2,2,3]
y=[1,2]
_ = [ x.remove(n) for n in y if n in x ]
x
[1,2,3]
Here's my take:
x = [1, 1, 2, 2, 3]
y = [1, 2]
z = [n for n in y if (n in x) and x.remove(n)] + x
print(z) # -> [1, 2, 3]
x = [1, 1, 1, 2]
y = [1]
z = [n for n in y if (n in x) and x.remove(n)] + x
print(z) # -> [1, 1, 2]
You just described the union of two sets, so use set:
>>> list(set(x).union(set(y)))
[1, 2, 3]

Python lists Ids

Why is the id is of x changes in the following code while it still has the same value. I expect the id for x and z should be same in this case as the value remains the same at the end.
>>> x = [1, 2, 3]
>>> z = x
>>> id(z) == id(x)
True
>>> x = [1, 2, 3]
>>> id(z) == id(x)
False
>>> x
[1, 2, 3]
>>> z
[1, 2, 3]
>>>
What an object holds has nothing to do with its identity. id(x) == id(y) if and only if x and y both refer to the same object.
Maybe this example helps:
x = [1, 2, 3]
y = [1, 2, 3]
z = y
print x, y, z
y[0] = 1000
print x, y, z
which prints this:
[1, 2, 3] [1, 2, 3] [1, 2, 3]
[1, 2, 3] [1000, 2, 3] [1000, 2, 3]
y and z both refer to the same object, so modifying one variable modifies the value retrieved by the other, too. x remains the same because it's a separate object.
What you shouldn't forget is that initializing a variable with a literal list (like [1, 2, 3]) creates a new list object.
Whether or not the two lists contain the same elements does not imply that they should have the same id.
Two variables only have the same id if they refer to the same object, which the second z and x do not:
>>> x = [1, 2, 3]
>>> z = [1, 2, 3]
>>> x[0] = 999
>>> x
[999, 2, 3]
>>> z
[1, 2, 3]
This demonstrates that x and z are two distinct, unrelated lists.

List slicing in python every other value

Suppose I have a list x = [1,2,3] and I want to output every other value than the indexed one, is there a list operation I can use?, ie: x[0]=> [2,3], x[1] => [1,3], x[2] =>[1,2] ?
You could use this:
def exclude(l, e):
return [v for i, v in enumerate(l) if i != e]
>>> exclude(range(10), 3)
[0, 1, 2, 4, 5, 6, 7, 8, 9]
You could do it with a slice, but I'd be tempted to try:
a = [1, 2, 3]
b = a[:]
del b[1]
edit
The above does "technically" use a slice operation, which happens to be on list objects in effect a shallow-copy.
What's more flexible and shouldn't have any downsides is to use:
a = [1, 2, 3]
b = list(a)
del b[1]
The list builtin works on any iterable (while the slice operation [:] works on lists or those that are indexable) so that you could even extend it to constructs such as:
>>> a = '123'
>>> b = list(a)
>>> del b[0]
>>> ''.join(b)
'23'
You could use x[:i] + x[i+1:]:
In [8]: x = [1, 2, 3]
In [9]: i = 0
In [10]: x[:i] + x[i+1:]
Out[10]: [2, 3]
Depending on the context, x.pop(i) might also be useful. It modifies the list in place by removing and returning the i-th element. If you don't need the element, del x[i] is also an option.
>>> x = [1, 2, 3]
>>> x[:1] + x[2:]
[1, 3]
my_list = [1, 2, 3]
my_list.remove(my_list[_index_]) #_index_ is the index you want to remove
Such that: my_list.remove(my_list[0]) would yield [2, 3],
my_list.remove(my_list[1]) would yield [0, 3], and
my_list.remove(my_list[2]) would yield [1, 2].
So you want every value except the indexed value:
Where i is the index:
>>> list = [1,2,3,4,5,6,7,8]
>>> [x for x in list if not x == list[i]]
This will give you a list with no instances of the i't element, so e.g.:
>>> i = 2
>>> list = [1,2,3,4,5,6,7,1,2,3,4,5,6,7]
>>> [x for x in list if not x == list[i]]
[1, 2, 4, 5, 6, 7, 1, 2, 4, 5, 6, 7]
note how 3 is not in that list at all.
Every other is
x[::2], start at 0
x[1::2], start at 1

Categories