Using zip_longest on unequal lists but repeat the last entry instead of returning None
Asked Answered
B

3

-2

There is an existing thread about this Zipping unequal lists in python in to a list which does not drop any element from longer list being zipped

But it's not quite I'm after. Instead of returning None, I need it to copy the previous entry on the list.

Is this possible?

a = ["bottle","water","sky"]
b = ["red", "blue"]
for i in itertools.izip_longest(a,b):
    print i

#result
# ('bottle', 'red')
# ('water', 'blue')
# ('sky', None) 

# What I want on the third line is
# ('sky', 'blue')
Buddhi answered 25/4, 2019 at 10:9 Comment(1)
should it return 'blue' for any subsequent element?Consistent
C
1

itertools.izip_longest takes an optional fillvalue argument that provides the value that is used after the shorter list has been exhausted. fillvalue defaults to None, giving the behaviour you show in your question, but you can specify a different value to get the behaviour you want:

    fill = a[-1] if (len(a) < len(b)) else b[-1]
    for i in itertools.izip_longest(a, b, fillvalue=fill):
        print i

(Obviously if the same list is always the shorter one then choosing the fill character is even easier.)

Calchas answered 25/4, 2019 at 21:12 Comment(0)
N
0

You can chain the shorter list with a repeat of its last value. Then using regular izip, the result will be the length of the longer list:

from itertools import izip, repeat, chain

def izip_longest_repeating(seq1, seq2):
    if len(seq1) < len(seq2):
        repeating = seq1[-1]
        seq1 = chain(seq1, repeat(repeating))
    else:
        repeating = seq2[-1]
        seq2 = chain(seq2, repeat(repeating))
    return izip(seq1, seq2)   

print(list(izip_longest_repeating(a, b)))
#  [('bottle', 'red'), ('water', 'blue'), ('sky', 'blue')]    

And here's a version that should work for any iterables:

from itertools import izip as zip # Python2 only

def zip_longest_repeating(*iterables):
    iters = [iter(i) for i in iterables]
    sentinel = object() 
    vals = tuple(next(it, sentinel) for it in iters)
    if any(val is sentinel for val in vals):
        return
    yield vals
    while True:
        cache = vals
        vals = tuple(next(it, sentinel) for it in iters)
        if all(val is sentinel for val in vals):
            return
        vals = tuple(old if new is sentinel else new for old, new in zip(cache, vals))
        yield vals

list(zip_longest_repeating(['a'], ['b', 'c'], ['d', 'r', 'f']))
#  [('a', 'b', 'd'), ('a', 'c', 'r'), ('a', 'c', 'f')]
Nucleolar answered 25/4, 2019 at 21:1 Comment(0)
P
0

You basically want the last item of the shortest list.

lists = (a, b)  # Could be any number of lists
last_value = min(lists, key=len)[-1]  # Takes the last value of the min len list
for i in itertools.izip_longest(*lists, fillvalue=last_value):
    print i

How it works

For finding the smallest anything in python we use the min function which conveniently takes a key argument to know how to sort the items.

In this case we want to sort on len so we pass that. At this point we have the shortest list so we can simply extract the last item [-1].

Puzzle answered 8/8 at 16:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.