Python Box and Pointers in lists - python-3.x

I'm studying for my final semester written paper. I was taught to trace the mutations of lists using box and pointer diagrams. However there was one question I came across where my method didn't work.
#Main code 1
a = [1, 2]
b = [a, a]
c = a.copy()
c[0], a[1] = b[1], c[0] #replace this
#code A
##c[0]=b[1]
##a[1]=c[0]
#code B
##a[1]=c[0]
##c[0]=b[1]
print(a)
print(b)
print(c)
##Normal Output/Code B
##[1, 1]
##[[1, 1], [1, 1]]
##[[1, 1], 2]
##Code A output
##[1, [...]]
##[[1, [...]], [1, [...]]]
##[[1, [...]], 2]
The main code is written as it is, and the given answer is shown in Normal Output(proven by Python3 IDLE). When I did the tracing on paper, I thought that the code would produce Code A output instead.
Is there any knowledge that I am missing out here?
Here are some other similar mutation questions that my method of tracing has worked in, but I can't draw any differences to why this behaviour is only seen in the first code.
#Code2 where switching line 3 like code 1 doesn't matter
a = [['a', 'b'], ['c'], 'd']
b = a[:-1]
a[1], b[0][1] = b[0], a[2] #switch this
print(a)
print(b)
##[['a', 'd'], ['a', 'd'], 'd']
##[['a', 'd'], ['c']]
#Code3 that has the same output even with a 1 liner replacement
a = [1,2,3]
b = [a,3,4]
a[2] = b
b[0][0] = b
a[0][1] = 99
a[2][0] = 5
#a[2],b[0][0],a[0][1],a[2][0]=b,b,99,5 #1 liner replacement
print(a)
#[[5, 99, 4], 2, [5, 99, 4]]

An assignment statement has two parts, a left side and a right side. The right side is calculated first, then the left side is calculated, then the right side is assigned to the left side, through tuple unpacking if necessary.
So c[0], a[1] = b[1], c[0] becomes c[0], a[1] = (a, 1). Then c[0] = a, then a[1] = 1
So you end up with the final result c == [[1, 1], 2] and a == [1, 1], with b == [a, a] still.
A key thing to realize here is that 1, 2, 3 is the syntax for a tuple.
a, b = 1, 2 and a, b = (1, 2) are exactly the same, it's just that in most contexts there are operators around that require you to parenthesize the tuple elements.
It's easier to follow that example if we give a[0] it's own variable name. So
x = ['a', 'b']
a = [x, ['c'], 'd']
b = a[:-1]
Then a[1], b[0][1] = b[0], a[2] -> a[1], b[0][1] = x, 'd' -> a[1] = x and x[1] = 'd'.
So we end up with x = ['a', 'd'], a = [x, x, 'd'] and b = [x, ['c']]. The ['c'] isn't an x because the second list in a wasn't mutated, it was replaced in a but not in b.

Related

What is the role of [:] in overwriting a list in a for loop?

I came across a weird syntactical approach at work today that I couldn't wrap my head around. Let's say I have the following list:
my_list = [[1, 2, 3], [4, 5, 6]]
My objective is to filter each nested list according to some criteria and overwrite the elements of the list in place. So, let's say I want to remove odd numbers from each nested list such that my_list contains lists of even numbers, where the end result would look like this:
[[2], [4, 6]]
If I try to do this using a simple assignment operator, it doesn't work.
my_list = [[1, 2, 3], [4, 5, 6]]
for l in my_list:
l = [num for num in l if num % 2 == 0]
print(my_list)
Output: [[1, 2, 3], [4, 5, 6]]
However, if I "slice" the list, it provides the expected output.
my_list = [[1, 2, 3], [4, 5, 6]]
for l in my_list:
l[:] = [num for num in l if num % 2 == 0]
print(my_list)
Output: [[2], [4, 6]]
My original hypothesis was that l was a newly created object that didn't actually point to the corresponding object in the list, but comparing the outputs of id(x[i]), id(l), and id(l[:]) (where i is the index of l in x), I realized that l[:] was the one with the differing id. So, if Python is creating a new object when I assign to l[:] then how does Python know to overwrite the existing object of l? Why does this work? And why doesn't the simple assignment operator l = ... work?
It's subtle.
Snippet one:
my_list = [[1, 2, 3], [4, 5, 6]]
for l in my_list:
l = [num for num in l if num % 2 == 0]
Why doesn't this work? Because when you do l = , you're only reassigning the variable l, not making any change to its value.
If we write the loop out "manually", it hopefully will become more clear why this strategy fails:
my_list = [[1, 2, 3], [4, 5, 6]]
# iteration 1
l = my_list[0]
l = [num for num in l if num % 2 == 0]
# iteration 2
l = my_list[1]
l = [num for num in l if num % 2 == 0]
Snippet two:
my_list = [[1, 2, 3], [4, 5, 6]]
for l in my_list:
l[:] = [num for num in l if num % 2 == 0]
Why does this work? Because by using l[:] = , you're actually modifying the value that l references, not just the variable l. Let me elaborate.
Generally speaking, using [:] notation (slice notation) on lists allows one to work with a section of the list.
The simplest use is for getting values out of a list; we can write a[n:k] to get the nth, item n+1st item, etc, up to k-1. For instance:
>>> a = ["a", "very", "fancy", "list"]
>>> print(a[1:3])
['very', 'fancy']
Python also allows use of slice notation on the left-side of a =. In this case, it interprets the notation to mean that we want to update only part of a list. For instance, we can replace "very", "fancy" with "not", "so", "fancy" like so:
>>> print(a)
['a', 'very', 'fancy', 'list']
>>> a[1:3] = ["not", "so", "fancy"]
>>> print(a)
['a', 'not', 'so', 'fancy', 'list']
When using slice syntax, Python also provides some convenient shorthand. Instead of writing [n:k], we can omit n or k or both.
If we omit n, then our slice looks like [:k], and Python understands it to mean "up to k", i.e., the same as [0:k].
If we omit k, then our slice looks like a[n:], and Python understands it to mean "n and after", i.e., the same as a[n:len(a)].
If we omit both, then both rules take place, so a[:] is the same as a[0:len(a)], which is a slice over the entire list.
Examples:
>>> print(a)
['a', 'not', 'so', 'fancy', 'list']
>>> print(a[2:4])
['so', 'fancy']
>>> print(a[:4])
['a', 'not', 'so', 'fancy']
>>> print(a[2:])
['so', 'fancy', 'list']
>>> print(a[:])
['a', 'not', 'so', 'fancy', 'list']
Crucially, this all still applies if we are using our slice on the left-hand side of a =:
>>> print(a)
['a', 'not', 'so', 'fancy', 'list']
>>> a[:4] = ["the", "fanciest"]
>>> print(a)
['the', 'fanciest', 'list']
And using [:] means to replace every item in the list:
>>> print(a)
['the', 'fanciest', 'list']
>>> a[:] = ["something", "completely", "different"]
>>> print(a)
['something', 'completely', 'different']
Okay, so far so good.
They key thing to note is that using slice notation on the left-hand side of a list updates the list in-place. In other words, when I do a[1:3] =, the variable a is never updated; the list that it references is.
We can see this with id(), as you were doing:
>>> print(a)
['something', 'completely', 'different']
>>> print(id(a))
139848671387072
>>> a[1:] = ["truly", "amazing"]
>>> print(a)
['something', 'truly', 'amazing']
>>> print(id(a))
139848671387072
Perhaps more pertinently, this means that if a were a reference to a list within some other object, then using a[:] = will update the list within that object. Like so:
>>> list_of_lists = [ [1, 2], [3, 4], [5, 6] ]
>>> second_list = list_of_lists[1]
>>> print(second_list)
[3, 4]
>>> second_list[1:] = [2, 1, 'boom!']
>>> print(second_list)
[3, 2, 1, 'boom!']
>>> print(list_of_lists)
[[1, 2], [3, 2, 1, 'boom!'], [5, 6]]

get list from index of other list python

I have 2 lists:
a=[0,2,0,5]
b=[3,4,5,6]
I want to find remove all the 0 from list a and remove corresponding values(with same index) in list b.
My result should be:
a=[2,5]
b=[4,6]
until now I did:
a = [idx for idx, val in enumerate(a) if val == 0]
and get a=[1,3]
but I don't manage to get the corresponding list in b
a=[0,2,0,5]
b=[3,4,5,6]
a, b = map(list, zip(*[[i, j] for i, j in zip(a, b) if i != 0]))
print(a)
print(b)
Prints:
[2, 5]
[4, 6]
You got a list indexes correctly, to get valid elements from b list the easy way is to do
[b[idx] for idx, val in enumerate(a) if val != 0]
and to get a values
[val for val in a if val != 0]
to do it in one iteration:
x = [(val, b[idx]) for idx, val in enumerate(a) if val != 0]
or
x = [(val_a, val_b) for val_a, val_b in zip(a, b) if val_a != 0]
but it gives you list of tuples, but you can use some python magic to turn it into two lists
a, b = map(list, zip(*x))

(Python) Finding all possible partitions of a list of lists subject to a size limit for a partition

Suppose that I have a list k = [[1,1,1],[2,2],[3],[4]], with size limit c = 4.
Then I will like to find all possible partitions of k subject ot c. Ideally, the result should be:
[ {[[1,1,1],[3]], [[2,2], [4]]}, {[[1,1,1],[4]], [[2,2], [3]]}, {[[1,1,1]], [[2,2], [3], [4]]}, ..., {[[1,1,1]], [[2,2]], [[3]], [[4]]} ]
where I used set notation { } in the above example (actual case its [ ]) to make it clearer as to what a partition is, where each partition contains groups of lists grouped together.
I implemented the following algorithm but my results do not tally:
def num_item(l):
flat_l = [item for sublist in l for item in sublist]
return len(flat_l)
def get_all_possible_partitions(lst, c):
p_opt = []
for l in lst:
p_temp = [l]
lst_copy = lst.copy()
lst_copy.remove(l)
iterations = 0
while num_item(p_temp) <= c and iterations <= len(lst_copy):
for l_ in lst_copy:
iterations += 1
if num_item(p_temp + [l_]) <= c:
p_temp += [l_]
p_opt += [p_temp]
return p_opt
Running get_all_possible_partitions(k, 4), I obtain:
[[[1, 1, 1], [3]], [[2, 2], [3], [4]], [[3], [1, 1, 1]], [[4], [1, 1, 1]]]
I understand that it does not remove duplicates and exhaust the possible combinations, which I am stuck on.
Some insight will be great! P.S. I did not manage to find similar questions :/
I think this does what you want (explanations in comments):
# Main function
def get_all_possible_partitions(lst, c):
yield from _get_all_possible_partitions_rec(lst, c, [False] * len(lst), [])
# Produces partitions recursively
def _get_all_possible_partitions_rec(lst, c, picked, partition):
# If all elements have been picked it is a complete partition
if all(picked):
yield tuple(partition)
else:
# Get all possible subsets of unpicked elements
for subset in _get_all_possible_subsets_rec(lst, c, picked, [], 0):
# Add the subset to the partition
partition.append(subset)
# Generate all partitions that complete the current one
yield from _get_all_possible_partitions_rec(lst, c, picked, partition)
# Remove the subset from the partition
partition.pop()
# Produces all possible subsets of unpicked elements
def _get_all_possible_subsets_rec(lst, c, picked, current, idx):
# If we have gone over all elements finish
if idx >= len(lst): return
# If the current element is available and fits in the subset
if not picked[idx] and len(lst[idx]) <= c:
# Mark it as picked
picked[idx] = True
# Add it to the subset
current.append(lst[idx])
# Generate the subset
yield tuple(current)
# Generate all possible subsets extending this one
yield from _get_all_possible_subsets_rec(lst, c - len(lst[idx]), picked, current, idx + 1)
# Remove current element
current.pop()
# Unmark as picked
picked[idx] = False
# Only allow skip if it is not the first available element
if len(current) > 0 or picked[idx]:
# Get all subsets resulting from skipping current element
yield from _get_all_possible_subsets_rec(lst, c, picked, current, idx + 1)
# Test
k = [[1, 1, 1], [2, 2], [3], [4]]
c = 4
partitions = list(get_all_possible_partitions(k, c))
print(*partitions, sep='\n')
Output:
(([1, 1, 1],), ([2, 2],), ([3],), ([4],))
(([1, 1, 1],), ([2, 2],), ([3], [4]))
(([1, 1, 1],), ([2, 2], [3]), ([4],))
(([1, 1, 1],), ([2, 2], [3], [4]))
(([1, 1, 1],), ([2, 2], [4]), ([3],))
(([1, 1, 1], [3]), ([2, 2],), ([4],))
(([1, 1, 1], [3]), ([2, 2], [4]))
(([1, 1, 1], [4]), ([2, 2],), ([3],))
(([1, 1, 1], [4]), ([2, 2], [3]))
If all elements in the list are unique, then you can use bit.
Assume k = [a,b,c], which length is 3, then there are 2^3 - 1 = 7 partions:
if you use bit to compresent a, b, c, there will be
001 -> [c]
010 -> [b]
011 -> [b, c]
100 -> [a]
101 -> [a,c]
110 -> [a,b]
111 -> [a,b,c]
so, the key to solving this question is obvious now.
Note: This answer is actually for a closed linked question.
If you only want to return the bipartitions of the list you can utilize more_iterools.set_partions:
>>> from more_itertools import set_partitions
>>>
>>> def get_bipartions(lst):
... half_list_len = len(lst) // 2
... if len(lst) % 2 == 0:
... return list(
... map(tuple, [
... p
... for p in set_partitions(lst, k=2)
... if half_list_len == len(p[0])
... ]))
... else:
... return list(
... map(tuple, [
... p
... for p in set_partitions(lst, k=2)
... if abs(half_list_len - len(p[0])) < 1
... ]))
...
>>> get_bipartions(['A', 'B', 'C'])
[(['A'], ['B', 'C']), (['B'], ['A', 'C'])]
>>> get_bipartions(['A', 'B', 'C', 'D'])
[(['A', 'B'], ['C', 'D']), (['B', 'C'], ['A', 'D']), (['A', 'C'], ['B', 'D'])]
>>> get_bipartions(['A', 'B', 'C', 'D', 'E'])
[(['A', 'B'], ['C', 'D', 'E']), (['B', 'C'], ['A', 'D', 'E']), (['A', 'C'], ['B', 'D', 'E']), (['C', 'D'], ['A', 'B', 'E']), (['B', 'D'], ['A', 'C', 'E']), (['A', 'D'], ['B', 'C', 'E'])]

How to retrieve lists of keys and values of a dictionary through a list comprehension?

This is a MWE that shows what I want to obtain but using a for loop:
a = {'a':1, 'b':2, 'c':3, 'd':4}
b = []
c = []
for key, value in a.items():
b.append(key)
c.append(value)
print(b) # ['a', 'b', 'c', 'd']
print(c) # [1, 2, 3, 4]
I want to obtain the same result in one line using list comprehension.
b,c = [(key, value) for key, value in a.items()] results in an unpack error because it assign to b and c, respectively, the first and second item of a and then it doesn't know where unpack the other items. b,c = [key, value for key, value in a.items()] results again in an error, a syntax one.
b, c = map(list, zip(*a.items()))
print(b)
print(c)
This outputs:
['a', 'b', 'c', 'd']
[1, 2, 3, 4]

Merge contents of two lists alternately into one list in groovy

I want to merge the contents of two lists alternately into a new list. The length of the lists are undefined. I am using the below code to achieve this. I want to know, if there is any groovy way to achieve this without using all the conditions and loop. The objective is to shorten the code as much as possible using groovy features.
def combineList(ArrayList list1, ArrayList list2){
def list = [];
int j = k = 0;
def size = (list1.size() + list2.size());
for (int i = 0; i < size; i++) {
if(j < list1.size())
list.add(list1.get(j++));
if(k < list2.size())
list.add(list2.get(k++));
}
println list;
}
Input:
case 1:
combineList([1,2,3,4,5,6,7,8,9,0], ['a','b','c','d','e','f'])
case 2:
combineList([1,2,3,4], ['a','b','c','d','e','f'])
Output:
case 1:
[1, a, 2, b, 3, c, 4, d, 5, e, 6, f, 7, 8, 9, 0]
case 2:
[1, a, 2, b, 3, c, 4, d, e, f]
One of many ways:
List combineList(List one, List two) {
def result = [one, two].transpose()
( result += (one - result*.get(0)) ?: (two - result*.get(1)) ).flatten()
}
assert combineList([1,2,3,4], ['a','b','c','d','e','f']) == [1, 'a', 2, 'b', 3, 'c', 4, 'd', 'e', 'f']
assert combineList([1,2,3,4,5,6,7,8,9,0], ['a','b','c','d','e','f']) == [1, 'a', 2, 'b', 3, 'c', 4, 'd', 5, 'e', 6, 'f', 7, 8, 9, 0]
assert combineList([1,2,3,4], ['a','b','c','d']) == [1, 'a', 2, 'b', 3, 'c', 4, 'd']
Explanation:
Transposing the lists results a list like [[1, a], [2, b], [3, c]].
Next check if there is any residual elements left in either of the list.
This is done by checking if a minus operation results with any elements. Take all first elements from result list to check against first list parameter, similarly take second elements of result list to check against second list passed as parameter to method.
If present, then add them to the result
Finally, flatten the result
It goes also simpler and slightly more performant:
def combineList( List o, List t ){
def res = [ o, t ].transpose().flatten()
int resSize = res.size() >> 1
if( o.size() > resSize ) res += o[ resSize..-1 ]
else if( t.size() > resSize ) res += t[ resSize..-1 ]
res
}
Java and Groovy does not differ a lot , one quick and easy to read solution can be like this, for existing List l1 and l2
List list3 = new ArrayList<>();
Iterator it = l1.iterator();
Iterator it = l2.iterator();
while(itA.hasNext() || itB.hasNext())
{
if(itA.hasNext())
list3.add(itA.next());
if(itB.hasNext())
list3.add(itB.next());
}

Resources