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

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个回答

1
使用 itertools.chain:
import itertools
from collections import Iterable

def list_flatten(lst):
    flat_lst = []
    for item in itertools.chain(lst):
        if isinstance(item, Iterable):
            item = list_flatten(item)
            flat_lst.extend(item)
        else:
            flat_lst.append(item)
    return flat_lst

或者不使用链式调用:

def flatten(q, final):
    if not q:
        return
    if isinstance(q, list):
        if not isinstance(q[0], list):
            final.append(q[0])
        else:
            flatten(q[0], final)
        flatten(q[1:], final)
    else:
        final.append(q)

1
我使用递归来解决任意深度的嵌套列表。
def combine_nlist(nlist,init=0,combiner=lambda x,y: x+y):
    '''
    apply function: combiner to a nested list element by element(treated as flatten list)
    '''
    current_value=init
    for each_item in nlist:
        if isinstance(each_item,list):
            current_value =combine_nlist(each_item,current_value,combiner)
        else:
            current_value = combiner(current_value,each_item)
    return current_value

所以在我定义了函数combine_nlist之后,使用这个函数来做列表扁平化就很容易了。或者你可以将它合并成一个函数。我喜欢我的解决方案,因为它可以应用于任何嵌套列表。

def flatten_nlist(nlist):
    return combine_nlist(nlist,[],lambda x,y:x+[y])

结果
In [379]: flatten_nlist([1,2,3,[4,5],[6],[[[7],8],9],10])
Out[379]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

嵌套列表深度不限制,这是真的。只要试一下就知道:current_value = combiner(current_value,each_item) RecursionError: 超过最大递归深度 - cglacet
你是不是试图展开超过1000层的列表? - Oldyoung
当然,这就是关于递归与迭代解决方案讨论的全部意义。如果您事先知道层数<1000,则最简单的解决方案将起作用。当您说“任何深度”时,这包括深度> 1000的列表。 - cglacet

1

不使用任何库:

def flat(l):
    def _flat(l, r):    
        if type(l) is not list:
            r.append(l)
        else:
            for i in l:
                r = r + flat(i)
        return r
    return _flat(l, [])



# example
test = [[1], [[2]], [3], [['a','b','c'] , [['z','x','y']], ['d','f','g']], 4]    
print flat(test) # prints [1, 2, 3, 'a', 'b', 'c', 'z', 'x', 'y', 'd', 'f', 'g', 4]

1
这是Python2中flatten的简单实现。
flatten=lambda l: reduce(lambda x,y:x+y,map(flatten,l),[]) if isinstance(l,list) else [l]

test=[[1,2,3,[3,4,5],[6,7,[8,9,[10,[11,[12,13,14]]]]]],]
print flatten(test)

#output [1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

1
这将使列表或字典(或列表的列表或字典的字典等)变平。它假设值是字符串,并创建一个字符串,将每个项与分隔符参数连接起来。如果您想要,可以使用分隔符将结果拆分为列表对象。如果下一个值是列表或字符串,则使用递归。使用key参数告诉您是否希望从字典对象中获取键或值(将key设置为false)。
def flatten_obj(n_obj, key=True, my_sep=''):
    my_string = ''
    if type(n_obj) == list:
        for val in n_obj:
            my_sep_setter = my_sep if my_string != '' else ''
            if type(val) == list or type(val) == dict:
                my_string += my_sep_setter + flatten_obj(val, key, my_sep)
            else:
                my_string += my_sep_setter + val
    elif type(n_obj) == dict:
        for k, v in n_obj.items():
            my_sep_setter = my_sep if my_string != '' else ''
            d_val = k if key else v
            if type(v) == list or type(v) == dict:
                my_string += my_sep_setter + flatten_obj(v, key, my_sep)
            else:
                my_string += my_sep_setter + d_val
    elif type(n_obj) == str:
        my_sep_setter = my_sep if my_string != '' else ''
        my_string += my_sep_setter + n_obj
        return my_string
    return my_string

print(flatten_obj(['just', 'a', ['test', 'to', 'try'], 'right', 'now', ['or', 'later', 'today'],
                [{'dictionary_test': 'test'}, {'dictionary_test_two': 'later_today'}, 'my power is 9000']], my_sep=', ')

产生:

just, a, test, to, try, right, now, or, later, today, dictionary_test, dictionary_test_two, my power is 9000

0
从我的先前的答案中可以看出,这个函数可以解决我能想到的大多数情况。我相信这个函数适用于Python 2.3及以上版本。
def flatten(item, keepcls=(), keepobj=()):
    if not hasattr(item, '__iter__') or isinstance(item, keepcls) or item in keepobj:
        yield item
    else:
        for i in item:
            for j in flatten(i, keepcls, keepobj + (item,)):
                yield j

循环链表

>>> list(flatten([1, 2, [...], 3]))
[1, 2, [1, 2, [...], 3], 3]

深度优先列表

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

嵌套重复列表

>>> list(flatten([[1,2],[1,[1,2]],[1,2]]))
[1, 2, 1, 1, 2, 1, 2]

带有字典的列表(或其他对象以避免扁平化)

>>> list(flatten([1,2, {'a':1, 'b':2}, 'text'], keepcls=(dict, str)))
[1, 2, {'a': 1, 'b': 2}, 'text']

任何可迭代对象
>>> list(flatten((x for x in [1,2, set([3,(4,5),6])])))
[1, 2, 4, 5, 3, 6]

您可能希望在keepcls中保留一些默认类,以使调用函数更加简洁。

0

使用Python 3的迭代解决方案

此解决方案适用于除str和bytes之外的所有对象。

from collections import Iterable
from collections import Iterator


def flat_iter(obj):
    stack = [obj]
    while stack:
        element = stack.pop()
        if element and isinstance(element, Iterator):
            stack.append(element)
            try:
                stack.append(next(element))
            except StopIteration:
                stack.pop()
        elif isinstance(element, Iterable) and not isinstance(element, (str, bytes)):
            stack.append(iter(element))
        else:
            yield element


tree_list = [[(1,2,3),(4,5,6, (7,8, 'next element is 5')), (5,6), [[[3,4,5],'foo1'],'foo2'],'foo3']]

not_iterable = 10

it1 = flat_iter(tree_list)
it2 = flat_iter(not_iterable)

print(list(it1))
print(list(it2))

[1, 2, 3, 4, 5, 6, 7, 8, '下一个元素是5', 5, 6, 3, 4, 5, 'foo1', 'foo2', 'foo3'] [10]

0
我们还可以使用Python的'type'函数。在迭代列表时,我们检查项目是否为列表。如果不是,我们将其'append',否则我们将其'extend'。以下是一个示例代码 -
l=[1,2,[3,4],5,[6,7,8]]
x=[]
for i in l:
    if type(i) is list:
        x.extend(i)
    else:
        x.append(i)
print x

输出:

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

如需了解更多关于append()和extend()的信息,请查看此网站: https://docs.python.org/2/tutorial/datastructures.html


0

不要脸地从另一个问题的我的答案中摘取。

这个函数

  • 不使用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]

0
这里有更高效的答案:https://dev59.com/THI95IYBdhLWcg3wyBCc#20495215 如果你能够控制列表的创建并且愿意对其进行修改,那么使用 deque(而不是 pop(0) 和列表拼接)会更加高效。
import collections

def flatten_and_consume(nested_deque: collections.deque):
    while nested_deque:
        elt = nested_deque.popleft()

        elt_is_sublist = isinstance(elt, collections.deque)
        if elt_is_sublist:
            nested_deque.extendleft(reversed(elt))
        else:
            yield elt

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