如何在python中优雅地交错两个不均匀长度的列表?

时间:2013-10-10 10:37:58

标签: python list python-3.x

我想在python中合并两个列表,列表具有不同的长度,因此较短列表的元素在最终列表中的间隔尽可能相等。即我想要[1, 2, 3, 4]['a','b']并合并它们以获得类似于[1, 'a', 2, 3, 'b', 4]的列表。它需要能够使用不是精确倍数的列表,因此它可能需要[1, 2, 3, 4, 5]['a', 'b', 'c']并生成[1, 'a', 2, 'b', 3, 'c', 4, 5]或类似。它需要保留两个列表的顺序。

我可以通过冗长的强力方法看到如何做到这一点但是因为Python似乎有很多优秀的工具来做各种我不知道的聪明的事情(还)我想知道是否我可以使用哪种更优雅的东西?

注意:我正在使用Python 3.3。

8 个答案:

答案 0 :(得分:10)

借用Jon Clements的解决方案,您可以编写一个函数,该函数接受任意数量的序列并返回均匀间隔项的合并序列:

import itertools as IT

def evenly_spaced(*iterables):
    """
    >>> evenly_spaced(range(10), list('abc'))
    [0, 1, 'a', 2, 3, 4, 'b', 5, 6, 7, 'c', 8, 9]
    """
    return [item[1] for item in
            sorted(IT.chain.from_iterable(
            zip(IT.count(start=1.0 / (len(seq) + 1), 
                         step=1.0 / (len(seq) + 1)), seq)
            for seq in iterables))]

iterables = [
    ['X']*2,
    range(1, 11),
    ['a']*3
    ]

print(evenly_spaced(*iterables))

产量

[1, 2, 'a', 3, 'X', 4, 5, 'a', 6, 7, 'X', 8, 'a', 9, 10]

答案 1 :(得分:10)

这与Bresenham's line algorithm基本相同。您可以计算“像素”位置并将其用作列表中的索引。

您的任务不同之处在于您只希望每个元素出现一次。您需要修改算法或对索引进行后处理,仅在列表第一次出现时附加列表中的元素。但是有一点点含糊不清:当两个像素/列表索引同时发生变化时,您需要选择首先包含哪一个。这对应于交错问题中提到的列表和注释的两个不同选项。

答案 2 :(得分:7)

假设a是要插入的序列:

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]

答案 3 :(得分:5)

如果a是较长的列表,b是较短的

from itertools import groupby

len_ab = len(a) + len(b)
groups = groupby(((a[len(a)*i//len_ab], b[len(b)*i//len_ab]) for i in range(len_ab)),
                 key=lambda x:x[0])
[j[i] for k,g in groups for i,j in enumerate(g)]

例如

>>> a = range(8)
>>> b = list("abc")
>>> len_ab = len(a) + len(b)
>>> groups = groupby(((a[len(a)*i//len_ab], b[len(b)*i//len_ab]) for i in range(len_ab)), key=lambda x:x[0])
>>> [j[i] for k,g in groups for i,j in enumerate(g)]
[0, 'a', 1, 2, 'b', 3, 4, 5, 'c', 6, 7]

您可以使用此技巧确保a长于b

b, a = sorted((a, b), key=len)

答案 4 :(得分:4)

如果我们像这样修改@ Jon的答案

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))]

a / b中哪一个最长

无关紧要

答案 5 :(得分:1)

如果我们想在没有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)]
啊,错过了等间距的部分。出于某种原因,这被标记为重复,并且在存在不同列表长度的情况下不需要等间隔的问题。

答案 6 :(得分:1)

@Jon Clements的变体使用more_itertools.collate进行解释。

给出

import itertools as it

import more_itertools as mit


a, b = range(1, 5), ["a", "b"]

代码

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] 

详细信息

此答案已更新为可用于Python 3。

firstsecond是元组的可迭代项,每个元组包含一个位置元素对。

list(first)
# [(0, 1), (1, 2), (2, 3), (3, 4)]

list(second)
# [(0, 'a'), (2, 'b')]

more_itertools.collate() wraps heapq.merge(),它按顺序合并了预排序的firstsecond可迭代项。在最终列表理解中,key是排序函数,而每个元组中的最后一个元素都返回。

通过> pip install more_itertools安装此第三方软件包或直接使用heapq.merge()

答案 7 :(得分:1)

我喜欢unutbu's answer,但不喜欢嵌套样式,因此我重写了它。在那儿的时候,我注意到排序不稳定,因此我使用operator.itemgetter进行了修复。

我也将itertools.count替换为enumerate,因为它更直观。作为奖励,尽管我还没有测试过,但它对于大型输入也应该更加准确。

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

请注意,与第二个示例有一个不同之处,其中'b''c'下移:

>>> list(intersperse(range(1, 6), 'abc'))
[1, 'a', 2, 3, 'b', 4, 'c', 5]