我想在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。
答案 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。
first
和second
是元组的可迭代项,每个元组包含一个位置元素对。
list(first)
# [(0, 1), (1, 2), (2, 3), (3, 4)]
list(second)
# [(0, 'a'), (2, 'b')]
more_itertools.collate()
wraps heapq.merge()
,它按顺序合并了预排序的first
和second
可迭代项。在最终列表理解中,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]