在Python中的一个函数中连接任意数量的列表

12

我希望编写join_lists函数,以接受任意数量的列表并将它们连接起来。例如,如果输入为

m = [1, 2, 3]
n = [4, 5, 6]
o = [7, 8, 9]

当我调用print join_lists(m, n, o)时,它将返回[1, 2, 3, 4, 5, 6, 7, 8, 9]。我意识到应该在join_lists中使用*args作为参数,但不确定如何连接任意数量的列表。谢谢。


不需要编写此函数,只需使用 from itertools import chain - Derek Veit
6个回答

18

虽然你可以按顺序使用调用__add__的方法,但那是非常错误的做法(首先,你会创建与输入列表数量相同的新列表,这将导致二次复杂度)。

标准工具是itertools.chain

def concatenate(*lists):
    return itertools.chain(*lists)
或者
def concatenate(*lists):
    return itertools.chain.from_iterable(lists)

这将返回一个生成器,按顺序产生列表中的每个元素。如果您需要将其作为列表使用,请使用list: list(itertools.chain.from_iterable(lists))

如果您坚持手动完成,则使用extend

def concatenate(*lists):
    newlist = []
    for l in lists: newlist.extend(l)
    return newlist

实际上,不要像那样使用extend——这仍然是低效的,因为它必须不断地扩展原始列表。"正确"的方法(其实还是错误的方法):

def concatenate(*lists):
    lengths = map(len,lists)
    newlen = sum(lengths)
    newlist = [None]*newlen
    start = 0
    end = 0
    for l,n in zip(lists,lengths):
        end+=n
        newlist[start:end] = list
        start+=n
    return newlist

http://ideone.com/Mi3UyL

请注意,这仍然会执行与列表中总插槽数相同的复制操作。因此,这不比使用 list(chain.from_iterable(lists)) 更好,并且可能更糟,因为list可以在C级别上利用优化。


最后,这是使用 extend 的版本(次优)单行代码,使用 reduce:

concatenate = lambda *lists: reduce((lambda a,b: a.extend(b) or a),lists,[])

PEP8并没有规定我们不能在一行上编写循环,只是建议不要这样做。我只是有点吹毛求疵。 - SethMMorton
@SethMMorton它还指出,愚蠢的一致性是小心灵的妖怪,我记得。对于一个单一的表达式,我肯定会发现这更易读。 - Marcin
哈哈。这也是真的。 - SethMMorton
@AshwiniChaudhary 谢谢。我认为如果回答的数量超过被采纳答案的票数,就会获得一个徽章。 - Marcin
将长度相加并执行 [None]*newlen,就像您的示例一样,然后执行 newlist[:] = itertools.chain.from_iterable(lists) 如何?C级别的优化不能使你摆脱这个事实:在迭代所有内容之前,列表构造函数无法知道最终的长度。 - Random832
@Random832 啊,但是通过在 C 级别上工作,可以使内存分配更加高效。特别是,它可以从 Python 级别的角度原子性地分配内存,而不需要分配 Python 对象并重新分配。关于你提供的形式 - 是的,那会更好,但重点是展示如何“手动”完成工作。如果您要使用 itertools,那么干脆就直接调用 list - Marcin

16

这种方法可能是使用reduce,因为目前我感觉比较函数式:

import operator
from functools import reduce
def concatenate(*lists):
    return reduce(operator.add, lists)

然而,更好的功能方法在马尔钦的回答中给出:

from itertools import chain
def concatenate(*lists):
    return chain(*lists)
尽管您可以直接使用 itertools.chain(*iterable_of_lists),但以下是一种过程式的方法:
def concatenate(*lists):
    new_list = []
    for i in lists:
        new_list.extend(i)
    return new_list

一种压缩的版本:j=lambda*x:sum(x,[])(实际上不要使用这个版本)。


谢谢!不导入任何模块,只使用基本工具怎么样? - alittleboy
3
第一种形式的复杂度为二次,并创建n个中间列表。 - Marcin
第二种形式仍然效率低下,因为它会将列表扩展n次。 - Marcin
最后,我想提醒一下,可以使用extend来实现一行代码的版本。 - Marcin
@Marcin 哦,我以为那是不同的,所以你才会给它。itertools.chain.from_iterable(lists) 高效吗? - rlms
@user2387370 这个程序非常高效,因为它只需按顺序遍历列表一次。 - Marcin

2
你可以使用一个空列表作为start参数,来使用sum()函数:
def join_lists(*lists):
    return sum(lists, [])

例如:

>>> join_lists([1, 2, 3], [4, 5, 6])
[1, 2, 3, 4, 5, 6]

1
为什么?我认为这看起来很好。 - rlms
7
这个解决方案只适用于非常短的列表,它具有二次复杂度。 - Frerich Raabe
有趣的问题。sum() 函数在内部使用 + 还是 += 来累加结果?如果使用其中一种,这将具有二次复杂度,而使用另一种则是线性的。 - Duncan
2
回答我的问题,@FrerichRaabe是正确的。如果sum()使用就地加法,它将改变起始值,这可能会破坏事物,因此它必须使用具有二次复杂度的普通加法。 - Duncan

0
另一种方法:
>>> m = [1, 2, 3]
>>> n = [4, 5, 6]
>>> o = [7, 8, 9]
>>> p = []
>>> for (i, j, k) in (m, n, o):
...     p.append(i)
...     p.append(j)
...     p.append(k)
... 
>>> p
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 

0
这似乎完全正常运行:
def join_lists(*args):
    output = []
    for lst in args:
        output += lst
    return output

它返回一个包含所有先前列表项的新列表。对于这种列表处理,使用+符号是否不合适?


-1
或者你可以更加合乎逻辑,创建一个变量(这里是'z'),将其赋值为传递给'join_lists'函数的第一个列表,然后将列表中的项目(而不是列表本身)分配给一个新列表,然后您就能够添加其他列表的元素了。
m = [1, 2, 3]
n = [4, 5, 6]
o = [7, 8, 9]

def join_lists(*x):
    z = [x[0]]
    for i in range(len(z)):
        new_list = z[i]
    for item in x:
        if item != z:
            new_list += (item)
    return new_list

然后

print(join_lists(m, n, o))

将输出:

[1, 2, 3, 4, 5, 6, 7, 8, 9]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接