[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