假设我有两个列表,一个比另一个长,x = [1,2,3,4,5,6,7,8]
并且y = [a,b,c]
我想将 y 中的每个元素合并到 x 中的每个第三个索引,因此结果列表 z 看起来像:z = [1,2,a,3,4,b,5,6,c,7,8]
在 python 中解决这个问题的最佳方法是什么?
假设我有两个列表,一个比另一个长,x = [1,2,3,4,5,6,7,8]
并且y = [a,b,c]
我想将 y 中的每个元素合并到 x 中的每个第三个索引,因此结果列表 z 看起来像:z = [1,2,a,3,4,b,5,6,c,7,8]
在 python 中解决这个问题的最佳方法是什么?
这是来自itertools 文档的轮询配方的改编版本,应该可以满足您的要求:
from itertools import cycle, islice
def merge(a, b, pos):
"merge('ABCDEF', [1,2,3], 3) --> A B 1 C D 2 E F 3"
iterables = [iter(a)]*(pos-1) + [iter(b)]
pending = len(iterables)
nexts = cycle(iter(it).next for it in iterables)
while pending:
try:
for next in nexts:
yield next()
except StopIteration:
pending -= 1
nexts = cycle(islice(nexts, pending))
例子:
>>> list(merge(xrange(1, 9), 'abc', 3)) # note that this works for any iterable!
[1, 2, 'a', 3, 4, 'b', 5, 6, 'c', 7, 8]
或者,您可以roundrobin()
在不进行任何修改的情况下按原样使用:
>>> x = [1,2,3,4,5,6,7,8]
>>> y = ['a','b','c']
>>> list(roundrobin(*([iter(x)]*2 + [y])))
[1, 2, 'a', 3, 4, 'b', 5, 6, 'c', 7, 8]
或等效但更具可读性的版本:
>>> xiter = iter(x)
>>> list(roundrobin(xiter, xiter, y))
[1, 2, 'a', 3, 4, 'b', 5, 6, 'c', 7, 8]
请注意,这两种方法都适用于任何可迭代的,而不仅仅是序列。
这是原始roundrobin()
实现:
from itertools import cycle, islice
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
# Recipe credited to George Sakkis
pending = len(iterables)
nexts = cycle(iter(it).next for it in iterables)
while pending:
try:
for next in nexts:
yield next()
except StopIteration:
pending -= 1
nexts = cycle(islice(nexts, pending))
这是另一种方式:
x = range(1, 9)
y = list('abc')
from itertools import count, izip
from operator import itemgetter
from heapq import merge
print map(itemgetter(1), merge(enumerate(x), izip(count(1, 2), y)))
# [1, 2, 'a', 3, 4, 'b', 5, 6, 'c', 7, 8]
这使它在构建新列表之前保持懒惰,并让merge
自然地合并序列......有点装饰/不装饰......虽然它确实需要 Python 2.7count
有一个step
参数。
所以,稍微介绍一下:
a = list(enumerate(x))
# [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8)]
b = zip(count(1, 2), y)
# [(1, 'a'), (3, 'b'), (5, 'c')]
print list(merge(a, b))
# [(0, 1), (1, 2), (1, 'a'), (2, 3), (3, 4), (3, 'b'), (4, 5), (5, 6), (5, 'c'), (6, 7), (7, 8)]
然后itemgetter(1)
只取删除索引的实际值......
这种方法x
就地修改。x
或者,如果您不想更改原件,您可以复制并返回修改后的副本。
def merge(x, y, offset):
for i, element in enumerate(y, 1):
x.insert(i * offset - 1, element)
>>> x = [1,2,3,4,5,6,7,8]
>>> y = ['a','b','c']
>>> merge(x, y, 3)
>>> x
[1, 2, 'a', 3, 4, 'b', 5, 6, 'c', 7, 8]
y
结束后的所有额外元素都x
将附加到末尾。
>>> from itertools import chain
def solve(x,y):
it = iter(y)
for i in xrange(0, len(x), 2):
try:
yield x[i:i+2] + [next(it)]
except StopIteration:
yield x[i:]
...
>>> x = [1,2,3,4,5,6,7,8]
>>> y = ['a','b','c']
>>> list(chain.from_iterable(solve(x,y)))
[1, 2, 'a', 3, 4, 'b', 5, 6, 'c', 7, 8]
使用itertools.izip_longest
:
>>> from itertools import izip_longest, chain
>>> y = ['a','b','c']
>>> x = [1,2,3,4,5,6,7,8]
>>> lis = (x[i:i+2] for i in xrange(0, len(x) ,2)) # generator expression
>>> list(chain.from_iterable([ (a + [b]) if b else a
for a, b in izip_longest(lis, y)]))
[1, 2, 'a', 3, 4, 'b', 5, 6, 'c', 7, 8]
sep, lst = 2, []
for i in range(len(y)+1):
lst += x[i*sep:(i+1)*sep] + y[i:i+1]
插入元素之前的元素sep
数量在哪里。x
y
表现:
>>> timeit.timeit(stmt="for i in range(len(y)+1): lst += x[i*sep:(i+1)*sep] + y[i:i+1]", setup="lst = [];x = [1,2,3,4,5,6,7,8];y = ['a','b','c'];sep = 2", number=1000000)
2.8565280437469482
相当不错。我无法stmt
开始,let = []
所以我认为它一直在附加lst
(除非我误解了timeit
),但仍然......一百万次相当不错。
上面的解决方案真的很酷。这是一个不涉及循环或迭代工具的替代方案。
def merge(x, y):
result = []
while y:
for i in range(0, 2): result.append(x.pop(0))
for i in range(0, 1): result.append(y.pop(0))
result.extend(x)
return result
其中 2 和 1 是任意的,并且假定列表 y 比列表 x 短。
def merge(xs, ys):
ys = iter(ys)
for i, x in enumerate(xs, 1):
yield x
if i % 2 == 0:
yield next(ys)
''.join(merge('12345678', 'abc')) # => '12a34b56c78'