outputs of different list comprehensions in python 3.x - python

I was studying about list comprehension in Python and I am confused why this two codes are producing different outputs.
CODE:
print([(letter,num) for letter in 'abc' for num in range(2)])
print([((letter,num) for letter in 'abc') for num in range(2)])
OUTPUT:
[('a', 0), ('a', 1), ('a', 2), ('a', 3), ('b', 0), ('b', 1), ('b', 2), ('b', 3), ('c', 0), ('c', 1), ('c', 2), ('c', 3), ('d', 0), ('d', 1), ('d', 2), ('d', 3)]
[<generator object <listcomp>.<genexpr> at 0x000002919E020F20>, <generator object <listcomp>.<genexpr> at 0x000002919E148C10>, <generator object <listcomp>.<genexpr> at 0x000002919E1489E0>, <generator object <listcomp>.<genexpr> at 0x000002919E148C80>]

The first example:
print([(letter,num) for letter in 'abc' for num in range(2)])
Prints a list (because of the outer [] brackets) which contains all the tuples (because of the parentheses around letter, num) of letter and num for each value of letter looping over 'abc' and each value of num looping over every value of the generator returned by range(2) (which will be 0 and 1).
Since Python takes the first for as the outer loop, you see ('a', 0), ('a', 1), etc. instead of ('a', 0), ('b', 0), etc.
However, when you add parentheses around a for expression like (letter,num) for letter in 'abc', you're no longer executing the loop in the comprehension, but you're capturing the generators (ready to start yielding their values, but not actually yielding the values into the comprehension).
So:
print([((letter,num) for letter in 'abc') for num in range(2)])
Here, ((letter,num) for letter in 'abc') is just a generator that will yield values as soon as you start asking for them.
Note: because the value of num is not enclosed in the generators separately, if you do something with them, you may see a surprising result:
x = [((letter,num) for letter in 'abc') for num in range(2)]
print(next(x[0]))
print(next(x[0]))
print(next(x[0]))
print(next(x[1]))
print(next(x[1]))
print(next(x[1]))
Result:
('a', 1)
('b', 1)
('c', 1)
('a', 1)
('b', 1)
('c', 1)

The first list comprehension is equivalent to nested loops:
result = []
for num in range(2):
for letter in 'abc':
result.append((letter, num))
print(result)
Each iteration of the nested loop produces in an element of the resulting list.
The second is equivalent to a single loop:
result = []
for num in range(2):
result.append((letter, num) for letter in 'abc')
print(result)
Each iteration of the loop appends a generator object to the resulting list.
You could use a nested list comprehension, but then the result will be nested lists, not a flat list as in the first version.
print([list((letter,num) for letter in 'abc') for num in range(2)])
# output: [[('a', 0), ('b', 0), ('c', 0)], [('a', 1), ('b', 1), ('c', 1)]]

Related

Convert list of tuples such that [(a,b,c)] converts to [(a,b),(a,c)]

Thoughts on how I would do this? I want the first value in the tuple to pair with each successive value. This way each resulting tuple would be a pair starting with the first value.
I need to do this: [(a,b,c)] --> [(a,b),(a,c)]
You can try this.
(t,)=[('a','b','c')]
[(t[0],i) for i in t[1:]]
# [('a', 'b'), ('a', 'c')]
Using itertools.product
it=iter(('a','b','c'))
list(itertools.product(next(it),it))
# [('a', 'b'), ('a', 'c')]
Using itertools.repeat
it=iter(('a','b','c'))
list(zip(itertools.repeat(next(it)),it))
# [('a', 'b'), ('a', 'c')]
a = [('a','b','c')]
a = a[0]
a = [tuple([a[0], a[index]]) for index in range(1, len(a))]
Try this !
A solution that uses itertools's combinations module.
from itertools import combinations
arr = (['a','b','c'])
for i in list(combinations(arr, 2)):
if(i[0]==arr[0]):
print(i ,end = " ")
This would give a solution ('a', 'b') ('a', 'c')
You can just append pairs of tuples to a list:
original = [(1,2,3)]
def makePairs(lis):
ret = []
for t in lis:
ret.append((t[0],t[1]))
ret.append((t[0],t[2]))
return ret
print(makePairs(original))
Output:
[(1, 2), (1, 3)]
If your tuples are arbitrary length you can write a simple generator:
def make_pairs(iterable):
iterator = iter(iterable)
first = next(iterator)
for item in iterator:
yield first, item
example result:
my_tuple = ('a', 'b', 'c', 'd')
list(make_pairs(my_tuple))
Out[170]: [('a', 'b'), ('a', 'c'), ('a', 'd')]
This is a memory-efficient solution.

How to combine two lists together into a list of tuples using recursion?

I know that this doesn't have to be done by doing recursion. I want to combine two lists such as x = [0, 1] and y = ['a', 'b', 'c'] and create a function that puts them in a list of tuples such as: [(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), (1, 'b'), (1, 'c')].
I know only how to do this for the first index of list x and struggling to figure out how to move on to further indexes. The output for the function below is: [(0, 'a'), (0, 'b'), (0, 'c')].
def combine(x, y, idx=0):
if idx < len(y):
return [(x[idx], y[idx])] + combine(x, y[1:])
else:
return []
I don't know how to get to get the list x to move onto further indexes. I think I may need to call all_pairs(list1, list2, index+1 instead of slicing the list. Do I need to have the function call itself twice? This may seem elementary but for whatever reason I can't figure it out.
You can do as below
from itertools import product
a = list(product(x,y))
a
or just list comprehension as below
[(a,b) for a in x for b in y]
Output
[(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), (1, 'b'), (1, 'c')]

Create a nested list based on tree relationship

I have a datatype that consists of multiple tuples in a list. It represents the relationship of parent-child.
For example, [('A', 1), ('A', 2, 1), ('A', 2, 2) ('A', 3), ('B', 1), ('B', 1, 1), ('B', 1, 2), ('C',)] where the tuples can either have 1, 2, or three items with the format of (letter, number, number). In the above example, ('B', 1) is the parent of ('B', 1, 1) and ('B', 1, 2), and so on until we reach just a letter.
My question is, how can I create a function that will receive something like the example above and create a nested list where the similar orders and letters/numbers will be grouped together.
For instance, how do I create a function that will take something like:
[('A', 1), ('A', 2, 1), ('A', 2, 2), ('A', 3), ('B', 1), ('B', 1, 1), ('B', 1, 2), ('B', 2), ('B', 3), ('C',)]
and turn it into:
[[('A', 1), [('A', 2, 1), ('A', 2, 2)] ('A', 3)], [[('B', 1, 1), ('B', 1, 2)], ('B', 2), ('B', 3)], ('C',)]
Also note that the list will come presorted already in alphabetical and numerical order. Only the lowest order tuples are in the input list as well. (Parental tuples will not appear in the input list if their children are present)
Thanks!
We basically can iterate over the tuples, and for each tuple recursively "dive" into the data structure, and add that element. I think however that a list is, at least for an intermediate structure, not appropriate. A dictionary allows fast retrieval, hence it will boost updating.
def to_nested_list(tuples):
data = {}
for tup in tuples:
elem = data
for ti in tup:
elem = elem.setdefault(ti, {})
stck = []
def to_list(source, dest):
for k, v in source.items():
stck.append(k)
if v:
dest.append(to_list(v, []))
else:
dest.append(tuple(stck))
stck.pop()
return dest
return to_list(data, [])
For the given sample data, we thus first construct a dictionary that looks, before the stck = [] line, like:
{'A': {1: {}, 2: {1: {}, 2: {}}, 3: {}}, 'B': {1: {1: {}, 2: {}}}, 'C': {}}
next we "harvest" the tuples of this structure, by iterating over the dictionary recursively, and each time if the corresponding value is not empty, adding a tuple we construct based on the "call path" to the corresponding sublist.
For example:
>>> to_nested_list([('A', 1), ('A', 2, 1), ('A', 2, 2), ('A', 3), ('B', 1), ('B', 1, 1), ('B', 1, 2), ('C',)])
[[('A', 1), [('A', 2, 1), ('A', 2, 2)], ('A', 3)], [[('B', 1, 1), ('B', 1, 2)]], ('C',)]
This works for tuples of arbitrary length, as long as the elements of these tuples are all hashable (strings and integers are hashable, so we are safe here if the tuples contain only letters and numbers).
That being said, I'm not sure that using a nested list is a good idea anyway. Such list will result in the fact that it can take a lot of time to verify that the list contains a certain tuple, since the elements of the list do not "hint" about the prefix of that tuple. I think the data dictionary is probable a better representation.
Set
a = [('A', 1), ('A', 2, 1), ('A', 2, 2), ('A', 3), ('B', 1), ('B', 1, 1), ('B', 1, 2), ('B', 2), ('B', 3), ('C',)]
The following solution works for trees with any depth:
First, a helper function that wraps each node with excess brackets if needed
def self_wrap(x, n):
output = x
for _ in range(n):
output = [output]
return output
Now, the main loop:
out_list = []
for i in range(len(a)):
# add 0th element to out_list
if i == 0:
out_list.append(self_wrap(a[i], len(a[i])-1))
continue
# determine the appropriate bracket level to add a[i]
prev_list = curr_list = out_list
j = 0
while min(len(a[i-1]), len(a[i])) > j and a[i-1][j] == a[i][j]:
prev_list, curr_list = curr_list, curr_list[-1]
print(curr_list, i, j)
j += 1
left_over_len = len(a[i]) - j - 1
# override if last item was parent
if j == len(a[i-1]):
prev_list[-1] = self_wrap(a[i], left_over_len + 1)
continue
# append a[i] to appropriate level and wrap with additional brackets if needed
curr_list.append(self_wrap(a[i], left_over_len) if left_over_len > 0 else a[i])
print(out_list)
This prints
[[('A', 1), [('A', 2, 1), ('A', 2, 2)], ('A', 3)], [[('B', 1, 1), ('B', 1, 2)], ('B', 2), ('B', 3)], ('C',)]
as expected.
As people have pointed out, this structure is not very efficient. There are 2 reasons:
redundant information
lists are hard to manipulate/lookup
That being said, the is probably the only way to represent paths.

In python, how should I implement a min heap on a list of tuple?

I'm trying to implement a min heap on a list of tuple.
For example:
A=[('a',2),('b',1)]
how can I heapify A based on the second element of these tuple, so that A will be heapified to [('b',1),('a',2)] ? (I must maintain a min heap.)
As per #JimMischel's comment, place your tuples in a tuple with the priority as the first element. Then use heapq:
import heapq
list = [('a', 2), ('b', 1), ('c', 0), ('d', 1)]
heap_elts = [(item[1], item) for item in list]
heapq.heapify(heap_elts) # you specifically asked about heapify, here it is!
while len(heap_elts) > 0:
print(heapq.heappop(heap_elts)[1]) # element 1 is the original tuple
produces:
('c', 0)
('b', 1)
('d', 1)
('a', 2)
import heapq
A=[('a',2),('b',1), ('d', 0), ('c', 2), ('a', 2)]
h = []
for el in A:
heapq.heappush(h, (el[1], el[0]))
print(h)
result:
[(0, 'd'), (2, 'a'), (1, 'b'), (2, 'c'), (2, 'a')]

How to separate elements of tuples into occurrences of pairs in Python?

I have a tuple that looks like:
t=(('a','b'),('a','c','d','e'),('c','d','e'))
I need to rearrange it so I have a new tuple that will look like:
t2=(('a','b'),('a','c'),('c','d'),('d','e'),('c','d'),('d','e'))
Basically the new tuple takes pairs (of 2) from each element of the old tuple. But I am not sure how to get started. Thanks for your help.
Use a generator expression with zip to pair and convert to a tuple at the end:
>>> t = (('a','b'),('a','c','d','e'),('c','d','e'))
>>> tuple((x) for tupl in t for x in zip(tupl, tupl[1:]))
(('a', 'b'), ('a', 'c'), ('c', 'd'), ('d', 'e'), ('c', 'd'), ('d', 'e'))
Try this out :
tuple([(t[i][j],t[i][j+1]) for i in range(len(t)) for j in range(len(t[i])-1)])
#[('a', 'b'), ('a', 'c'), ('c', 'd'), ('d', 'e'), ('c', 'd'), ('d', 'e')]
You can also try another way. If the problem is reduced to do this for one tuple alone :
def pairs(my_tuple):
return [(my_tuple[i],my_tuple[i+1]) for i in range(len(my_tuple)-1)]
Then this can be mapped for all the tuples
tuple(sum(list(map(pairs,t)),[]))
#(('a', 'b'), ('a', 'c'), ('c', 'd'), ('d', 'e'), ('c', 'd'), ('d', 'e'))
Explanation :
map(pairs,t) : maps the function pairs for every element in tuple t
list(map(pairs,t)) : output of the above
But as a nested list
[[[('a', 'b')], [('a', 'c'), ('c', 'd'), ('d', 'e')],...]
sum(list(...),[]) : Flattens out this nested list for the desired output
Here's what I came up with really quick
def transform(t):
out = []
for tup in t:
for i in range(0, len(tup) - 1):
out.append((tup[i], tup[i+1]))
return tuple(out)
You can use this easy to understand code:
t = (('a','b'),('a','c','d','e'),('c','d','e'))
t2 = []
for i in t:
for j in range(len(i)-1):
t2.append((i[j], i[j+1]))
t2 = tuple(t2)
Obviously it isn't very optimized like other answers but for an easy understanding it will be perfect.
That is something equivalent to:
t2 = tuple((i[j], i[j+1]) for i in t for j in range(len(i)-1))
That is a generator expression, something quite similar to list comprehension (it use brackets instead of square brackets) and they basically do similar things, or at least in basic codes like this one. I still don't understand very well their differences but the generators are one-time fasters while the list comprehension are slower but reusable...
Nevermind: the generator means:
t2 = tuple(...) # Make with the result array a tuple, otherwise it will be a list.
for i in t # Iterate over each item of t, they will by called i.
for i in t for j in range(len(i)) # Iterate over each item of t --called--> i and then iterate over the range(len(i)) --called--> j.
(i[j], i[j+1]) for i in t for j in range(len(i)) # The same as before but each time we get a new j (each time the second loop iterate) do --> (i[j], i[j+1])
I know, make two generator/list expression/comprehension on the same line is strange. I always look at an answer like this one to remember how to do that.
My old answer was:
t = (('a','b'),('a','c','d','e'),('c','d','e'))
t2 = []
for i in t:
for j in range(len(i)):
if j < len(i) - 1:
t2.append((i[j], i[j+1]))
t2 = tuple(t2)
But I notice that adding a -1 to the len() of the loop I can avoid that line, because I won't never get an out of index error.

Categories

Resources