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

Python成语链(扁平化)有限迭代对象的无限迭代? 在Python中扁平化一个浅列表 理解平展一个序列的序列吗? 我如何从列表的列表中创建一个平面列表?

但据我所知,所有的解决方案,除了一个,在像[[[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

这是最好的方法吗?我是不是忽略了什么?任何问题吗?


当前回答

无耻地从我自己对另一个问题的回答中提取。

这个函数

不使用isinstance,因为它是邪恶的,破坏了鸭子的输入。 递归使用reduce。肯定有一个用reduce的答案。 适用于任意嵌套列表,其元素要么是嵌套列表,要么是非嵌套原子列表,要么是原子(受递归限制)。 不是LBYL。 但对于包含字符串作为原子的嵌套列表则不是这样。

下面的代码:

def flattener(left, right):
    try:
        res = reduce(flattener, right, left)
    except TypeError:
        left.append(right)
        res = left
    return res


def flatten(seq):
    return reduce(flattener, seq, [])


>>> nested_list = [0, [1], [[[[2]]]],
                   [3, [], [4, 5]],
                   [6, [7, 8],
                    9, [[[]], 10,
                        []]],
                   11, [], [],
                   [12]]
>>> flatten(nested_list)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

其他回答

这是另一种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))

它可以忽略你想要的任何特定(或派生)类型,它返回一个迭代器,所以你可以将它转换为任何特定的容器,如list, tuple, dict或简单地消耗它,以减少内存占用,无论是好是坏,它可以处理初始的不可迭代对象,如int…

注意,大部分繁重的工作都是在C中完成的,因为据我所知,itertools就是这样实现的,所以虽然它是递归的,但AFAIK它不受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) vs O(类型数量)来检查是否应该忽略一个元素,当然,任何带有指定的被忽略类型的派生类型的值都会失败,这就是为什么它使用str, unicode,所以要小心使用它…

测试:

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

我是一个愚蠢的人,所以我会给出一个“愚蠢”的解决方案。所有的递归都伤了我的大脑。

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]

我知道这是一个副作用但这是我对递归的最好理解

我在这里没有看到类似的帖子,只是从一个关于同一主题的封闭问题中得到的,但为什么不做这样的事情呢(如果你知道你想拆分的列表类型):

>>> 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()]

你需要知道元素的类型,但我认为这是可以推广的,就速度而言,我认为它会更快。

虽然已经选择了一个优雅和非常python的答案,但我将提出我的解决方案,只是为了回顾:

def flat(l):
    ret = []
    for i in l:
        if isinstance(i, list) or isinstance(i, tuple):
            ret.extend(flat(i))
        else:
            ret.append(i)
    return ret

请告诉我这个代码是好是坏?

只需使用函数库: PIP安装功能

import funcy


funcy.flatten([[[[1, 1], 1], 2], 3]) # returns generator
funcy.lflatten([[[[1, 1], 1], 2], 3]) # returns list