[Inspired by this question]
Suppose I have two lists:
list1 = ['tom', 'mary', 'frank', 'joe', 'john', 'barry']
list2 = [1, 2, 3, 4]
I want to match each name in list1
with a number in list2
. Since there are four numbers in list2
, I would like to pair each of the first four names to the corresponding number in list2
, and assign random numbers to the remaining names in list1
.
I know that I can solve this problem using a for-loop with enumerate
and random.choice
. Indeed, I've provided such a solution to the original problem.
I would like to know however, if it is possible to do something like this:
for name, number in itertools.izip_longest(list1, list2, fillvalue=MAGIC):
print name, number
Originally, I thought of using something like this:
MAGIC = random.choice(list1)
However, that performs random.choice(list1)
first, and then uses the answer as the fillvalue
for the zipping operation. This is unattractive, as it does not choose a new random value for each pair of values that it zips. It is therefore clear, that itertools.izip_longest
requires for its fillvalue
, something that has a value by itself, which it doesn't call. Indeed, if I were to provide it a function, it would yield a pair consisting of a name, and a callable function, which is also undersired. For this reason, lambda
functions are not feasible solutions.
How would I go about creating a variable that calls some function when it is called upon? How does itertools.izip_longest
use the fillvalue
variable? Is the __repr__
of that variable called? If so, can I make a class with a __repr__
that calls a function inside it?
Seems the simplest approach here would be to create a generator that yields all the filler values for eternity, then chain this with yielding the values in list2:
def inf_gen(f):
while True:
yield f()
Note that inf_gen(f)
is actually equivalent to iter(f, None)
, given that f
never returns. iter
with two arguments calls the first argument until it returns the second argument.
Then use it as such:
>>> from itertools import izip, chain
>>> for name, number in izip(list1,chain(list2, iter(lambda: random.choice(list2), None))):
print name, number
tom 1
mary 2
frank 3
joe 4
john 1
barry 4
You can also do this just with itertools stuff without defining a separate function by using a generator comprehension with itertools.count
:
>>> from itertools import izip, chain, count
>>> for name, number in izip(list1, chain(list2, (random.choice(list2) for _ in count()))):
print name, number
If you want it to "scale" to multiple iterators, you could do it with a slight modification to the code given in http://docs.python.org/2.7/library/itertools.html?highlight=izip_longest#itertools.izip_longest :
from itertools import repeat, chain, count
class ZipExhausted(Exception):
pass
def izip_longest2(*args, **kwds):
fillvalue = kwds.get('fillvalue')
fillgen = kwds.get('fillgen', repeat(fillvalue)) # added
counter = [len(args) - 1]
def sentinel():
if not counter[0]:
raise ZipExhausted
counter[0] -= 1
yield next(fillgen) # modified
iterators = [chain(it, sentinel(), fillgen) for it in args] # modified
try:
while iterators:
yield tuple(map(next, iterators))
except ZipExhausted:
pass
a = ['bob', 'mary', 'paul']
b = ['eggs', 'spam']
c = ['a', 'b', 'c', 'd']
for x in izip_longest2(a, b, c, fillgen=count()):
print '\t'.join(map(str, x))
# output:
# bob eggs a
# mary spam b
# paul 0 c
# 1 2 d
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With