How to elegantly interleave two lists of uneven length in python?

前端 未结 8 1380
自闭症患者
自闭症患者 2021-01-04 06:13

I want to merge two lists in python, with the lists being of different lengths, so that the elements of the shorter list are as equally spaced within the final list as possi

相关标签:
8条回答
  • 2021-01-04 06:42

    This is basically the same as Bresenham's line algorithm. You can calculate "pixel" positions and use them as the indices into the lists.

    Where your task differs is that you only want each element to show up once. You'd need to either modify the algorithm or post-process the indices, appending the elements from the lists only the first time they appear. There is a slight ambiguity, though: when both pixel/list indices change at the same time, you'll need to pick which one to include first. This corresponds to the two different options for interleaving the lists that are mentioned in the question and a comment.

    0 讨论(0)
  • 2021-01-04 06:50

    A variant of @Jon Clements answer using more_itertools.collate with explanation.

    Given

    import itertools as it
    
    import more_itertools as mit
    
    
    a, b = range(1, 5), ["a", "b"]
    

    Code

    first = enumerate(a)
    second = zip(it.count(0, len(a) // len(b)), b)
    [x for i, x in mit.collate(first, second, key=lambda x: x[0])]
    # [1, 'a', 2, 3, 'b', 4] 
    

    Details

    This answer is updated to use with Python 3.

    first and second are iterables of tuples, each tuple comprising a position-element pair.

    list(first)
    # [(0, 1), (1, 2), (2, 3), (3, 4)]
    
    list(second)
    # [(0, 'a'), (2, 'b')]
    

    more_itertools.collate() wraps heapq.merge(), which merges the pre-sorted first and second iterables in order. In the final list comprehension, the key is the sorting function while the last element in each tuple in returned.

    Install this third-party package via > pip install more_itertools or use heapq.merge() directly.

    0 讨论(0)
  • 2021-01-04 06:51

    If we modify @Jon's answer like this

    from itertools import count
    import heapq
    
    [x[1] for x in heapq.merge(izip(count(0, len(b)), a), izip(count(0, len(a)), b))]
    

    It doesn't matter which of a/b is longest

    0 讨论(0)
  • 2021-01-04 06:56

    If we want to do this without itertools:

    def interleave(l1, l2, default=None):  
        max_l = max(len(l1), len(l2))
        data  = map(lambda x: x + [default] * (max_l - len(x)), [l1,l2])
        return [data[i%2][i/2] for i in xrange(2*max_l)]
    

    Ahh, missed the equally spaced part. This was for some reason marked as duplicate with a question that didn't require equally spaced in the presence of differing list lengths.

    0 讨论(0)
  • With the assumption that a is the sequence to be inserted into:

    from itertools import izip, count
    from operator import itemgetter
    import heapq
    
    a = [1, 2, 3, 4]
    b = ['a', 'b']
    
    fst = enumerate(a)
    snd = izip(count(0, len(a) // len(b)), b)
    print map(itemgetter(1), heapq.merge(fst, snd))
    # [1, 'a', 2, 3, 'b', 4]
    
    0 讨论(0)
  • 2021-01-04 07:00

    I like unutbu's answer but not the nested style, so I rewrote it. While I was there I noticed the sorting wasn't stable, so I fixed it using operator.itemgetter.

    I also replaced itertools.count with enumerate because it's more intuitive. As a bonus it should also be more accurate for large inputs, though I haven't tested it.

    import itertools
    import operator
    
    def distribute(sequence):
        """
        Enumerate the sequence evenly over the interval (0, 1).
    
        >>> list(distribute('abc'))
        [(0.25, 'a'), (0.5, 'b'), (0.75, 'c')]
        """
        m = len(sequence) + 1
        for i, x in enumerate(sequence, 1):
            yield i/m, x
    
    def intersperse(*sequences):
        """
        Evenly intersperse the sequences.
    
        Based on https://stackoverflow.com/a/19293603/4518341
    
        >>> list(intersperse(range(10), 'abc'))
        [0, 1, 'a', 2, 3, 4, 'b', 5, 6, 7, 'c', 8, 9]
        >>> list(intersperse('XY', range(10), 'abc'))
        [0, 1, 'a', 2, 'X', 3, 4, 'b', 5, 6, 'Y', 7, 'c', 8, 9]
        >>> ''.join(intersperse('hlwl', 'eood', 'l r!'))
        'hello world!'
        """
        distributions = map(distribute, sequences)
        get0 = operator.itemgetter(0)
        for _, x in sorted(itertools.chain(*distributions), key=get0):
            yield x
    

    Note that there's one difference from your second example, where 'b' and 'c' get moved down:

    >>> list(intersperse(range(1, 6), 'abc'))
    [1, 'a', 2, 3, 'b', 4, 'c', 5]
    
    0 讨论(0)
提交回复
热议问题