Is there a middle ground between `zip` and `zip_longest`

前端 未结 5 1017
面向向阳花
面向向阳花 2021-01-12 11:54

Say I have these three lists:

a = [1, 2, 3, 4]
b = [5, 6, 7, 8, 9]
c = [10, 11, 12]

Is there a builtin function such that:

         


        
5条回答
  •  北海茫月
    2021-01-12 12:29

    Your output seems to be restricted by the output of the first iterator it1. So we could use it1 as is, and pad it2 with infinite None-yielding iterator, and zip them.

    >>> from itertools import repeat,izip,chain
    >>> somezip = lambda it1,it2: izip(it1,chain(it2,repeat(None)))
    
    >>> list(somezip(a,b))
    [(1, 5), (2, 6), (3, 7), (4, 8)]
    >>> list(somezip(a,c))
    [(1, 10), (2, 11), (3, 12), (4, None)]
    

    repeat(None) creates iterator yielding None infinitely.

    chain glues it2 and repeat(None).

    izip will stop yielding as soon as it1 is exhausted.

    The other solutions have some flaws (I've left remarks in the comments). They may work well, but with some input they may fail unexpectedly.


    As glglgl suggested in the comments, this function would rather accept variable number of iterators in parameters.

    So I updated the code to work this:

    from itertools import repeat,izip,chain,imap
    somezip = lambda it1,*its: izip(it1,*imap(chain,its,repeat(repeat(None))))
    

    Test:

    >>> print(list(somezip(a,b)))
        print(list(somezip(a,c)))
        print(list(somezip(b,a,c)))
    
    [(1, 5), (2, 6), (3, 7), (4, 8)]
    [(1, 10), (2, 11), (3, 12), (4, None)]
    [(5, 1, 10), (6, 2, 11), (7, 3, 12), (8, 4, None), (9, None, None)]
    

    I had to use imap here though there were no need to do so (as parameter are later unpacked, so plain map would do). The reason was that map don't accept iterators of different length, while imap stop while the smallest iterator is consumed.

    So, imap applies chain to all the iterators except for the first one and chains each of them with repeat(None). To serve every iterator of its I used another repeat above repeat(None) (note, it may be very dangerous in the other projects, as all the objects which the outer repeat produces are the same object repeat(None), so in the end they all the chained iterators share it). Then I unpacked imap object to produce the parameters to izip, which returns the values until it1 is consumed (as chained its are now produce infinite sequence of values each).

    Note that all the operations work in pure C, so there is no interpreter overhead involved.

    To clarify how it works, I'm adding this elaboration:

    def somezip(it1,*its): #from 0 to infinite iterators its
        # it1 -> a1,a2,a3,...,an
        # its -> (b1,b2,b3,...,bn),(c1,c2,c3,...,cn),...
        infinite_None = repeat(None) # None,None,None,...
        infinite_Nones = repeat(infinite_None) # infinite_None,infinite_None,... (share the same infinite_None)
        chained = imap(chain,its,infinite_Nones) # [(b1,b2,b3,...,bn,None,None,...),(c1,c2,c3,...,cn,None,None,...),...]
        return izip(it1,*chained)
    

    And one-liner for it is just:

    somezip = lambda it1,*its: izip(it1,*imap(chain,its,repeat(repeat(None))))
    

提交回复
热议问题