将一个不规则(任意嵌套)的列表展开为一维列表

553

是的,我知道这个主题以前已经被讨论过:

但据我所知,除了一个解决方案外,所有的解决方案都不能处理像[[[1, 2, 3], [4, 5]], 6]这样的列表,期望的输出是[1, 2, 3, 4, 5, 6](或者更好的方式是迭代器)。

我看到的唯一一个可以处理任意嵌套的解决方案在这个问题中找到:

def flatten(x):
    result = []
    for el in x:
        if hasattr(el, "__iter__") and not isinstance(el, basestring):
            result.extend(flatten(el))
        else:
            result.append(el)
    return result

这是最好的方法吗?我有遗漏了什么吗?有什么问题吗?


32
这么多答案和讨论表明这个问题应该被内置为一个函数,是吧?尤其遗憾的是,在Python 3.0中移除了compiler.ast。 - Mittenchops
3
我认为Python真正需要的是不受限制的递归,而不是另一个内置函数。 - clay
4
@Mittenchops: 完全不同意,事实上,与明显糟糕的API或过于复杂的数据结构一起工作(仅说明一下:list 应该是同质的)并不意味着这是Python的问题,我们需要为这样的任务建立一个内置功能。 - Azat Ibrakov
7
如果您的项目可以添加软件包,我建议使用more_itertools.collapse解决方案。这个答案来自这里:https://dev59.com/qnNA5IYBdhLWcg3wdtld#40938883 - viddik13
@viddik13:请考虑将其作为此问题的答案,这样我一定会点赞。 (我同意Mittenchops的观点。)事实上,它不是一个内置函数没关系(根据Azat Ibrakov),但是有(并且显然有)一个库例程来完成这个操作。(因为:不是所有的不规则性都是“糟糕的”/“过于复杂的”。有时,它只是...不“规则”,这没关系。在我看来,只要它是明确定义的,并且它可以被定义为不规则的(例如,“整数的任意嵌套列表(列表(列表...))”是明确定义的)。) - lindes
使用递归函数遍历列表树的列表 https://stackabuse.com/python-how-to-flatten-list-of-lists/ - Golden Lion
52个回答

2
这是2.7.5版本中compiler.ast.flatten的实现代码:

def flatten(seq):
    l = []
    for elt in seq:
        t = type(elt)
        if t is tuple or t is list:
            for elt2 in flatten(elt):
                l.append(elt2)
        else:
            l.append(elt)
    return l

有更好、更快的方法(如果你已经到这里,你已经看到它们了)

另外注意:

自Python 3版本起,编译器包已被删除。自2.6版本起已废弃。


2
无递归或嵌套循环。仅需几行。格式良好,易于阅读:
def flatten_deep(arr: list):
    """ Flattens arbitrarily-nested list `arr` into single-dimensional. """

    while arr:
        if isinstance(arr[0], list):  # Checks whether first element is a list
            arr = arr[0] + arr[1:]  # If so, flattens that first element one level
        else:
            yield arr.pop(0)  # Otherwise yield as part of the flat array

flatten_deep(L)

从我的代码中,https://github.com/jorgeorpinel/flatten_nested_lists/blob/master/flatten.py

1
谢谢,这对我来说是最容易理解的,并且对我很有效;干得好。另外,只想说一下你的flatten_trick,太棒了! - Nathaniel Hoyt

2
我经常使用more_itertools.collapse
# pip install more-itertools
from more_itertools import collapse

out = list(collapse([[[1, 2, 3], [4, 5]], 6]))
# [1, 2, 3, 4, 5, 6]

它可以直接处理字符串/字节,而不会将它们拆分为单个字符。
list(collapse([[[1, 2, 3, 'abc'], [4, 5]], 6, 'def']))
# [1, 2, 3, 'abc', 4, 5, 6, 'def']

它还可以在给定的嵌套级别停止:
list(collapse([[[1, 2, 3], [4, 5]], 6], levels=1))
# [[1, 2, 3], [4, 5], 6]

完整的代码相对简单,它还使用了递归的方法:
def collapse(iterable, base_type=None, levels=None):
    def walk(node, level):
        if (
            ((levels is not None) and (level > levels))
            or isinstance(node, (str, bytes))
            or ((base_type is not None) and isinstance(node, base_type))
        ):
            yield node
            return

        try:
            tree = iter(node)
        except TypeError:
            yield node
            return
        else:
            for child in tree:
                yield from walk(child, level + 1)

    yield from walk(iterable, 0)

2
我非常惊讶,65个现有的答案中竟然没有提到more_itertools.collapse,尽管那个评论提到了! - undefined
@Pranav 我也是,我检查了所有的东西两次,确保没有遗漏!more_itertools 是一个非常好的库,如果它不在那里,那就太可惜了。 - undefined

1
最简单的方法是使用 morph 库,使用 pip install morph 命令安装即可。
代码如下:
import morph

list = [[[1, 2, 3], [4, 5]], 6]
flattened_list = morph.flatten(list)  # returns [1, 2, 3, 4, 5, 6]

1

我在这里没有看到类似的帖子,刚从一个关闭的有关相同主题的问题中来到这里,但为什么不像这样做(如果您知道要拆分的列表类型):

>>> a = [1, 2, 3, 5, 10, [1, 25, 11, [1, 0]]]    
>>> g = str(a).replace('[', '').replace(']', '')    
>>> b = [int(x) for x in g.split(',') if x.strip()]

你需要知道元素的类型,但我认为这可以泛化,并且在速度方面会更快。


这很聪明(而且可能很快)……但它并不是很Pythonic。 - DanB
8
“你为什么不做类似这样的事情呢?”你问道?因为这种方式很容易出错!非常糟糕。例如,如果你的项目是字符串而不是整数,那么如果一个字符串包含了一个“[”,那么你就注定会出错。而且,如果你的项目没有好的(或者非常长的)字符串表示形式,又该怎么办呢? - gb.
@gb。如果这正是提问者所需要的,而且示例明显是一个ints列表,那么“假设”在这里并不适用,如果提问者另有说明,但他没有,所以根据所给出的信息,这是最简单和最有效的答案之一。 - Zion
2
很抱歉,“假设情况”是必须考虑的,对所有“假设情况”的仔细考虑是编程的核心。 - gb.

1

我是一个蠢人,所以我会提供一个“蠢”的解决方案。所有这些递归让我的大脑疼痛。

flattened_list = []
nested_list = [[[1, 2, 3], [4, 5]], 6]

def flatten(nested_list, container):
    for item in nested_list:
        if isintance(item, list):
            flatten(item, container)
        else:
            container.append(item)

>>> flatten(nested_list, flattened_list)
>>> flattened_list
[1, 2, 3, 4, 5, 6]

我知道它使用了副作用,但这是我对递归理解得最好的程度。

1

没有花哨,只有刺激。

recursive_list_of_lists = [1,2,3,[1,2,[[3,4,[5]],7,0,1,10],100,[101,[101,[[101]],2]],0]]

k = []

def flatten(subl):
    for i in subl:
        if type(i) != type([1]):
            k.append(i)
        else:
            flatten(i)

flatten(recursive_list_of_lists)

print(k)

[1,2,3,1,2,3,4,5,7,0,1,10,100,101,101,101,2,0]


1

这是我使用递归解决的方法:

def flatten(x):
    if not any(isinstance(e, list) for e in x):
        return x
    while type(x[-1]) == int:
        x = [x[-1]] + [x[:-1]]
    return flatten(x = x + x.pop(-1))

甚至更好:

def flatten(x):
    if not any(isinstance(e, list) for e in x):
        return x
    return flatten(x = x + x.pop([isinstance(e, list) for e in x].index(1)))

1

这里是另一种基于py2的方法,我不确定它是否最快、最优雅或最安全...

from collections import Iterable
from itertools import imap, repeat, chain


def flat(seqs, ignore=(int, long, float, basestring)):
    return repeat(seqs, 1) if any(imap(isinstance, repeat(seqs), ignore)) or not isinstance(seqs, Iterable) else chain.from_iterable(imap(flat, seqs))

它可以忽略任何您想要的特定(或派生)类型,它返回一个迭代器,因此您可以将其转换为任何特定容器,例如列表、元组、字典或仅消耗它以减少内存占用,无论好坏它都可以处理最初的非可迭代对象,如int ...
请注意,大部分重活都是在C中完成的,因为据我所知这就是itertools的实现方式,因此虽然它是递归的,但据我所知它不受Python递归深度的限制,因为函数调用发生在C中,尽管这并不意味着您受到内存的限制,特别是在OS X中,由于今天(OS X Mavericks)其堆栈大小有一个硬限制...
有一种稍微更快的方法,但是不太便携,只有在您可以假定输入的基本元素可以被明确确定时才使用它,否则您将得到无限递归,并且具有其有限堆栈大小的OS X将很快抛出分段错误...
def flat(seqs, ignore={int, long, float, str, unicode}):
    return repeat(seqs, 1) if type(seqs) in ignore or not isinstance(seqs, Iterable) else chain.from_iterable(imap(flat, seqs))

在这里,我们使用集合来检查类型,因此检查元素是否应被忽略仅需O(1)而不是O(类型数量),但当然,任何具有所述被忽略类型的派生类型的值都将失败,这就是为什么它使用strunicode,因此请谨慎使用...

测试:

import random

def test_flat(test_size=2000):
    def increase_depth(value, depth=1):
        for func in xrange(depth):
            value = repeat(value, 1)
        return value

    def random_sub_chaining(nested_values):
        for values in nested_values:
            yield chain((values,), chain.from_iterable(imap(next, repeat(nested_values, random.randint(1, 10)))))

    expected_values = zip(xrange(test_size), imap(str, xrange(test_size)))
    nested_values = random_sub_chaining((increase_depth(value, depth) for depth, value in enumerate(expected_values)))
    assert not any(imap(cmp, chain.from_iterable(expected_values), flat(chain(((),), nested_values, ((),)))))

>>> test_flat()
>>> list(flat([[[1, 2, 3], [4, 5]], 6]))
[1, 2, 3, 4, 5, 6]
>>>  

$ uname -a
Darwin Samys-MacBook-Pro.local 13.3.0 Darwin Kernel Version 13.3.0: Tue Jun  3 21:27:35 PDT 2014; root:xnu-2422.110.17~1/RELEASE_X86_64 x86_64
$ python --version
Python 2.7.5

1

虽然很粗糙,但我认为它可以工作(取决于数据类型)

flat_list = ast.literal_eval("[%s]"%re.sub("[\[\]]","",str(the_list)))

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