14

我想在 python 中合并两个列表,列表的长度不同,以便较短列表的元素在最终列表中尽可能等距。即我想把它们[1, 2, 3, 4]合并['a','b']起来得到一个类似的列表[1, 'a', 2, 3, 'b', 4]。它还需要能够处理不是精确倍数的列表,因此它可以采用[1, 2, 3, 4, 5]and['a', 'b', 'c']和产生[1, 'a', 2, 'b', 3, 'c', 4, 5]或类似的方式。它需要保留两个列表的顺序。

我可以通过冗长的蛮力方法看到如何做到这一点,但由于 Python 似乎有大量优秀的工具可以做各种我不知道的聪明的事情(还)我想知道是否还有更多优雅的我可以用吗?

注意:我使用的是 Python 3.3。

4

8 回答 8

12

大量借鉴 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]
于 2013-10-10T10:43:44.647 回答
10

这与Bresenham 的线算法基本相同。您可以计算“像素”位置并将它们用作列表中的索引。

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

于 2013-10-10T10:54:29.493 回答
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]
于 2013-10-10T11:56:44.637 回答
6

ifa是较长的列表并且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]

你可以使用这个技巧来确保ab

b, a = sorted((a, b), key=len)
于 2013-10-10T11:00:17.160 回答
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最长无关紧要

于 2013-10-10T12:59:50.603 回答
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)]

啊,错过了等距的部分。由于某种原因,这被标记为重复的问题,在存在不同列表长度的情况下不需要等间距。

于 2015-06-17T22:08:24.157 回答
1

@Jon Clements 的一个变体回答使用more_itertools.collatewith解释。

给定

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() wrapsheapq.merge(),它按顺序合并预排序firstsecond迭代。在最终的列表推导中,key是排序函数,返回每个元组中的最后一个元素。

也可以看看

通过安装此第三方软件包> pip install more_itertools

于 2018-10-11T17:20:46.683 回答
1

我喜欢unutbu 的答案,但不喜欢嵌套风格,所以我重写了它。当我在那里时,我注意到排序不稳定,所以我使用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]
于 2020-01-04T20:26:00.263 回答