How can itertools.chain be used with a generator function? - python

In what follows, I am trying but failing in getting a flat list:
>>> from itertools import chain
>>>
>>> def foo():
... for i in range(3):
... yield range(5)
...
>>>
>>> chain(foo)
<itertools.chain object at 0x7fce499934d0>
>>>
>>> list(chain(foo()))
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]
>>>
>>> list(chain([foo()]))
[<generator object foo at 0x7fce49994aa0>]
>>>
>>> list(chain(list(foo())))
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]
What am I doing wrong? How can I get a flat list [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4] using itertools.chain?

You're close, you can use chain.from_iterable from itertools
>>> list(itertools.chain.from_iterable(foo()))
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4]

Related

Appending to lists within loops in Python3 (again)

I'm having difficulty adding to a list iteratively.
Here's a MWE:
# Given a nested list of values, or sets
sets = [[1, 2, 3], [1, 2, 4], [1, 2, 5]]
# add a value to each sublist giving the number of that set in the list.
n_sets = len(sets)
for s in range(n_sets):
(sets[s]).insert(0, s)
# Now repeat those sets reps times
reps = 4
expanded_sets = [item for item in sets for i in range(reps)]
# then assign a repetition number to each occurance of a set.
rep_list = list(range(reps)) * n_sets
for i in range(n_sets * reps):
(expanded_sets[i]).insert(0, rep_list[i])
expanded_sets
which returns
[[3, 2, 1, 0, 0, 1, 2, 3],
[3, 2, 1, 0, 0, 1, 2, 3],
[3, 2, 1, 0, 0, 1, 2, 3],
[3, 2, 1, 0, 0, 1, 2, 3],
[3, 2, 1, 0, 1, 1, 2, 4],
[3, 2, 1, 0, 1, 1, 2, 4],
[3, 2, 1, 0, 1, 1, 2, 4],
[3, 2, 1, 0, 1, 1, 2, 4],
[3, 2, 1, 0, 2, 1, 2, 5],
[3, 2, 1, 0, 2, 1, 2, 5],
[3, 2, 1, 0, 2, 1, 2, 5],
[3, 2, 1, 0, 2, 1, 2, 5]]
instead of the desired
[[0, 0, 1, 2, 3],
[1, 0, 1, 2, 3],
[2, 0, 1, 2, 3],
[3, 0, 1, 2, 3],
[0, 1, 1, 2, 4],
[1, 1, 1, 2, 4],
[2, 1, 1, 2, 4],
[3, 1, 1, 2, 4],
[0, 2, 1, 2, 5],
[1, 2, 1, 2, 5],
[2, 2, 1, 2, 5],
[3, 2, 1, 2, 5]]
Just for fun, the first loop returns an expected value of sets
[[0, 1, 2, 3], [1, 1, 2, 4], [2, 1, 2, 5]]
but after the second loop sets changed to
[[3, 2, 1, 0, 0, 1, 2, 3], [3, 2, 1, 0, 1, 1, 2, 4], [3, 2, 1, 0, 2, 1, 2, 5]]
I suspect the issue has something to do with copies and references. I've tried adding .copy() and slices in various places, but with the indexed sublists I haven't come across a combo that works. I'm running Python 3.10.6.
Thanks for looking!
Per suggested solution, [list(range(reps)) for _ in range(n_sets)] doesn't correctly replace the list(range(reps)) * n_sets, since it gives [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]] instead of
the desired [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]. Do I need to flatten, or is there a syntax with the _ notation that gives me a single list?
Further update . . .
replacing
rep_list = list(range(reps)) * n_sets
with
rep_list_nest = [list(range(reps)) for _ in range(n_sets)]
rep_list = [i for sublist in rep_list_nest for i in sublist]
gives the same undesired result for expanded_sets.
The problem is here:
expanded_sets = [item
for item in sets
for i in range(reps)]
This list now contains the same element of sets four times in a row, followed by the next element repeated four times, and so on.
Creating copies of item fixes the issue:
expanded_sets = [item.copy()
for item in sets
for i in range(reps)]
Try it online
If you want a more pythonic approach, then recognize that the result is a product of two ranges, and your original sets all concatenated together:
from itertools import product
sets = [[1, 2, 3], [1, 2, 4], [1, 2, 5]]
expanded_sets = [[inner_counter, outer_counter] + sets_elem
for sets_elem, outer_counter, inner_counter in product(sets, range(len(sets)), range(4))]
Try it online

How can I select a specific number in a list with duplicates without removing the previous same values?

I have the following list in python3:
a = [1, 1, 1, 2, 2, 3, 3, 3, 1, 1, 1, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3]
I want to have something like this as an output:
b = [1, 2, 3, 1, 2, 4, 3]
How can I do that? I have tried a = set(a) and other methods for getting rid of duplicates. But they remove all duplicates.
If you are willing to use a module, you can use itertools.groupby:
from itertools import groupby
a = [1, 1, 1, 2, 2, 3, 3, 3, 1, 1, 1, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3]
output = [k for k, _ in groupby(a)]
print(output) # [1, 2, 3, 1, 2, 4, 3]
This would work:
a = [1, 1, 1, 2, 2, 3, 3, 3, 1, 1, 1, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3]
b = [a[0]]
for k in a:
if k!=b[-1]:
b.append(k)
print(b)
If you comfortable with C, you may like this style:
a = [1, 1, 1, 2, 2, 3, 3, 3, 1, 1, 1, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3]
b=len(a)
i=0
last_one=None
while i<b:
if a[i]==last_one:
del a[i]
b-=1
continue
else:
last_one=a[i]
i+=1
print(a)

Creating a dictionary given values from a list and dictionary

So, I am given a list
a =[[[0, 0, 3, 3, 3, 3], [0, 0, 1, 3, 3, 3, 3]], [[0, 1]], [[2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3]], [[0, 0, 2, 2, 2, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3, 3]]]
and a dictionary d.
d = {0:2,1:1,2:3,3:4}
For the output, I want a dictionary:
output = {0:[0,3],1;[1],2:[2,3],3:[0,2]}
This output is formed by passing through each sublist of a and checking the number of times each element appears in d.
Let's look at index 0 of a. Now we look at a[0][0]and
a[0][1] and since 0 appears twice in both and 3 appears 4 times (comparing it to d), [0,3] are added to index 0. Similarly, at index 1, 0 appears just once and is not added to the dictionary at index 1.
What I tried so far:
def example(a,d):
for i in range(len(a)):
count = 0
for j in range(len(a[i])):
if j in (a[i][j]):
count+=1
if count == d[i]:
print(i,j)
Edit: A version that work
from collections import Counter
a = [[[0, 0, 3, 3, 3, 3], [0, 0, 1, 3, 3, 3, 3]], [[0, 1]],
[[2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3]],
[[0, 0, 2, 2, 2, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3, 3]]]
d = {0: 2, 1: 1, 2: 3, 3: 4}
output = {i: [] for i in range(len(a))}
for j, sublist in enumerate(a):
counts = [Counter(i) for i in sublist]
for k,v in d.items():
try:
if all(counts[i][k] == v for i in range(len(counts))):
output[j].append(k)
except: continue
print(output)
output:
{0: [0, 3], 1: [1], 2: [2, 3], 3: [0, 2]}
The try except block is merely for convenience, If you insist you can if your way around this by checking if a key is in all counters (which is a requirement for it to be add)

How do I make copies of an array in python based on conditions in the array?

I have an
n = np.array([[1, 12, 1, 3],
[1, 1, 12, 0]])
and would like to duplicate it such that if I have a double-digit number in the array, it breaks the array into two identical arrays where the first array has the first digit and the second array has the second digit. In the above example, I would have 4 copies of the matrix. The assumptions are that there are either single digit or double digit numbers in the array.
n1 = [1, 1, 1, 3], [1, 1, 1, 0]
n2 = [1, 1, 1, 3], [1, 1, 2, 0]
n3 = [1, 2, 1, 3], [1, 1, 1, 0]
n4 = [1, 2, 1, 3], [1, 1, 2, 0]
Approach 1: itertools.product
>>> import numpy as np
>>> from itertools import product
>>> from pprint import pprint
>>>
>>> n = np.array([[1, 12, 1, 3],
... [1, 1, 12, 0]])
>>>
>>> pprint([np.reshape(nn, n.shape).astype(int) for nn in product(*map(str, n.ravel()))])
[array([[1, 1, 1, 3],
[1, 1, 1, 0]]),
array([[1, 1, 1, 3],
[1, 1, 2, 0]]),
array([[1, 2, 1, 3],
[1, 1, 1, 0]]),
array([[1, 2, 1, 3],
[1, 1, 2, 0]])]
Note that this happens to work also for longer numbers.
>>> n = np.array([462, 3, 15, 1, 0])
>>> pprint([np.reshape(nn, n.shape).astype(int) for nn in product(*map(str, n.ravel()))])
[array([4, 3, 1, 1, 0]),
array([4, 3, 5, 1, 0]),
array([6, 3, 1, 1, 0]),
array([6, 3, 5, 1, 0]),
array([2, 3, 1, 1, 0]),
array([2, 3, 5, 1, 0])]
Approach 2: np.meshgrid
>>> import numpy as np
>>>
>>> n = np.array([[1, 12, 1, 3],
... [1, 1, 12, 0]])
>>>
>>> te = np.where(n>=10)
>>> dims = tuple(np.log10(n[te]).astype(int) + 1)
>>>
>>> out = np.empty(dims + n.shape, dtype=n.dtype)
>>> out[...] = n
>>> out[(Ellipsis,) + te] = np.moveaxis(np.meshgrid(*(s//10**np.arange(i)[::-1]%10 for i, s in zip(dims, n[te])), indexing='ij'), 0, -1)
>>>
>>> out
array([[[[1, 1, 1, 3],
[1, 1, 1, 0]],
[[1, 1, 1, 3],
[1, 1, 2, 0]]],
[[[1, 2, 1, 3],
[1, 1, 1, 0]],
[[1, 2, 1, 3],
[1, 1, 2, 0]]]])

Applying a method with no return value to each element of a list [duplicate]

This question already has answers here:
Is it Pythonic to use list comprehensions for just side effects?
(7 answers)
Closed 4 months ago.
Is there a way to use methods with no return value such as random.shuffle in a list comprehension?
>>> import pprint
>>> import random
>>>
>>> L = [ random.shuffle(range(5)) for x in range(5)]
>>>
>>> print L
[None, None, None, None, None]
This is the for loop that applies the random.shuffle method to each item of my list:
>>> L = [ range(5) for x in range(5) ]
>>> pprint.pprint(L)
[[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]]
>>> for element in L:
... random.shuffle(element)
...
>>> pprint.pprint(L)
[[2, 0, 3, 1, 4],
[2, 0, 1, 4, 3],
[4, 1, 3, 0, 2],
[1, 2, 4, 3, 0],
[1, 3, 0, 2, 4]]
I can use map, which as a side effect shuffles the original list but returns a list of None
>>> L = [ range(5) for x in range(5) ]
>>> pprint.pprint(L)
[[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]]
>>> map(random.shuffle, L)
[None, None, None, None, None]
>>> pprint.pprint(L)
[[3, 0, 4, 1, 2],
[2, 3, 0, 1, 4],
[2, 3, 1, 4, 0],
[4, 2, 0, 3, 1],
[1, 3, 0, 2, 4]]
as does using the list comprehension with shuffle:
>>> L = [ range(5) for x in range(5) ]
>>> pprint.pprint(L)
[[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]]
>>> L1 = [ random.shuffle(x) for x in L ]
>>> pprint.pprint(L1)
[None, None, None, None, None]
>>> pprint.pprint(L)
[[1, 4, 0, 2, 3],
[0, 4, 1, 3, 2],
[2, 3, 4, 0, 1],
[4, 1, 0, 2, 3],
[2, 0, 4, 3, 1]]
Many questions and answers on stack overflow already point out that using map or a lc for the side effect is bad practice. I was wondering if there's any correct way to use a method with no return value in a list comprehension.
Is writing a method to wrap the non-returning method the only way:
>>> def shuffled(L):
... ret_val = L[:]
... random.shuffle(ret_val)
... return ret_val
...
>>> L = [ shuffled(range(5)) for x in range(5)]
>>> pprint.pprint(L)
[[2, 1, 0, 4, 3],
[4, 0, 3, 1, 2],
[4, 2, 3, 0, 1],
[1, 0, 4, 2, 3],
[2, 4, 3, 0, 1]]
>>>
No - list comprehensions are meant to be use with functions having return values. It's how their semantics are defined:
List comprehensions provide a concise
way to create lists without resorting
to use of map(), filter() and/or
lambda. The resulting list definition
tends often to be clearer than lists
built using those constructs. Each
list comprehension consists of an
expression followed by a for clause,
then zero or more for or if clauses.
The result will be a list resulting
from evaluating the expression in the
context of the for and if clauses
which follow it.
Having read this, it should be clear that "a list comprehension from a function having no return value" is an oxymoron.
Just use a for loop for something "one off":
import random
L = []
for x in range(5):
l = range(5)
random.shuffle(l)
L.append(l)
Clean and simple. Your shuffled function is also just fine, and can be used in a list comprehension.
Eli is quite right. But I'd go for something more concise:
import random
L = [range(5) for each in xrange(5)]
for each in L:
random.shuffle(each)
[edit]
OTOH you could use random.sample():
from random import sample
xr = xrange(5)
L = [sample(xr, 5) for each in xr]

Categories

Resources