Сгладить неправильный список списков


440

Да, я знаю, что эта тема уже была рассмотрена ( здесь , здесь , здесь , здесь ), но, насколько я знаю, все решения, кроме одного, терпят неудачу в таком списке:

L = [[[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

flatten(L)

Это лучшая модель? Я что-то упустил? Любые проблемы?


16
Тот факт, что есть так много ответов и так много действий по этому вопросу, действительно предполагает, что это должно быть встроенной функцией где-то, верно? Особенно плохо, что compiler.ast был удален из Python 3.0
Mittenchops

3
Я бы сказал, что Python действительно нуждается в непрерывной рекурсии, а не в другой встроенной функции.
глина

2
@Mittenchops: совершенно не согласен, тот факт, что люди, работающие с явно плохими API-интерфейсами / чрезмерно сложными структурами данных (просто примечание, listпредназначенное для того, чтобы быть однородными), не означает, что это ошибка Python, и нам нужна встроенная
функция

1
Если вы можете позволить себе добавить пакет в ваш проект - я думаю, что решение more_itertools.collapse сделает это лучше всего. Из этого ответа: stackoverflow.com/a/40938883/3844376
viddik13

Ответы:


382

Использование функций генератора может немного облегчить чтение вашего примера и, вероятно, повысить производительность.

Python 2

def flatten(l):
    for el in l:
        if isinstance(el, collections.Iterable) and not isinstance(el, basestring):
            for sub in flatten(el):
                yield sub
        else:
            yield el

Я использовал Iterable ABC, добавленный в 2.6.

Python 3

В Python 3 этого basestringбольше нет, но вы можете использовать кортеж strи, bytesчтобы получить тот же эффект.

yield fromОператор возвращает элемент из генератора по одному. Этот синтаксис для делегирования субгенератору был добавлен в 3.3

def flatten(l):
    for el in l:
        if isinstance(el, collections.Iterable) and not isinstance(el, (str, bytes)):
            yield from flatten(el)
        else:
            yield el

6
Из всех предложений на этой странице, это единственный, который упростил этот список, l = ([[chr(i),chr(i-32)] for i in xrange(ord('a'), ord('z')+1)] + range(0,9))когда я сделал это list(flatten(l)). Все остальные, начинали работать и брали навсегда!
nemesisfixx

7
Это также сглаживает словари. Может быть, вы хотите использовать collections.Sequenceвместо collections.Iteratable?
josch

1
Это не работает с вещами, которые не являются списками изначально, например for i in flatten(42): print (i). Это можно исправить, переместив isinstance-test и условие else за пределами for el-loop. (Тогда вы могли бы бросить что-нибудь в это, и это сделало бы сплющенный список из этого)
RolKau

6
Для Python 3.7 использование collections.Iterableустарело. Используйте collections.abc.Iterableвместо этого.
августа

5
Действительно, рекурсия никогда не нужна. В данном конкретном случае использование рекурсии не является наилучшим решением, поскольку оно приводит к сбою в глубоко вложенных списках (глубина> 1000). Но если вы не нацелены на то, чтобы иметь что-то безопасное, то да, рекурсивные функции лучше, так как они намного проще для чтения / записи.
cglacet

50

Мое решение:

import collections


def flatten(x):
    if isinstance(x, collections.Iterable):
        return [a for i in x for a in flatten(i)]
    else:
        return [x]

Чуть более лаконично, но примерно так же.


5
Вы можете сделать это, ничего не импортируя, если просто try: iter(x)протестируете, является ли это итеративным ... Но я не думаю, что необходимость импортировать модуль stdlib - это недостаток, которого стоит избегать.
Абарнерт

8
Стоит отметить, что это решение работает, только если все элементы относятся к типуint
alfasin

1
Может сделать его более кратким, def flatten(x): return [a for i in x for a in flatten(i)] if isinstance(x, collections.Iterable) else [x]но читабельность здесь может быть субъективной.
Ноль

4
это не работает со строками, потому что строки тоже итерируемы. Заменить условие наif isinstance(x, collections.Iterable) and not isinstance(x, basestring)
aandis

заменить collections.Iterableнаlist
нообниня

36

Генератор, использующий рекурсию и утку (обновлен для Python 3):

def flatten(L):
    for item in L:
        try:
            yield from flatten(item)
        except TypeError:
            yield item

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

1
Спасибо, это хорошо работает для Python 3. Для 2.x необходимо предыдущее: for i in flatten(item): yield i
dansalmo

list (flatten ([['X'], 'Y'])) завершается с ошибкой в ​​варианте 2.X
sten

@ user1019129 см. мой комментарий выше вашего
dansalmo

да, это не с циклом .. я думаю, потому что строка также является "массивом" символов
sten

35

Генераторная версия нерекурсивного решения @ unutbu, запрошенная @Andrew в комментарии:

def genflat(l, ltypes=collections.Sequence):
    l = list(l)
    i = 0
    while i < len(l):
        while isinstance(l[i], ltypes):
            if not l[i]:
                l.pop(i)
                i -= 1
                break
            else:
                l[i:i + 1] = l[i]
        yield l[i]
        i += 1

Немного упрощенная версия этого генератора:

def genflat(l, ltypes=collections.Sequence):
    l = list(l)
    while l:
        while l and isinstance(l[0], ltypes):
            l[0:1] = l[0]
        if l: yield l.pop(0)

это предварительный обход дерева, сформированного вложенными списками. только листья возвращаются. Обратите внимание, что эта реализация будет использовать исходную структуру данных, в лучшую или в худшую сторону. Может быть интересно написать тот, который одновременно сохраняет оригинальное дерево, но также не должен копировать записи списка.
Эндрю Вагнер

6
Я думаю, вам нужно проверить строки - например, добавить «а не isinstance (l [0], basestring)», как в решении Кристиана. В противном случае вы получите бесконечный цикл вокруг l [0: 1] = l [0]
c-urchin

Это хороший пример создания генератора, но, как упоминает c-urchin, сам алгоритм завершается ошибкой, когда последовательность содержит строки.
Даниэль 'Данг' Гриффит

28

Вот моя функциональная версия рекурсивного сглаживания, которая обрабатывает как кортежи, так и списки, и позволяет вам добавлять любое сочетание позиционных аргументов. Возвращает генератор, который производит всю последовательность в порядке, arg by arg:

flatten = lambda *n: (e for a in n
    for e in (flatten(*a) if isinstance(a, (tuple, list)) else (a,)))

Применение:

l1 = ['a', ['b', ('c', 'd')]]
l2 = [0, 1, (2, 3), [[4, 5, (6, 7, (8,), [9]), 10]], (11,)]
print list(flatten(l1, -2, -1, l2))
['a', 'b', 'c', 'd', -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

1
отличное решение, однако было бы гораздо полезно , если вы добавили некоторые комментарии , чтобы описать то , что e, a, nсм
Кристофа Pal

2
@WolfgangKuehne: Try argsдля n, intermediate(или короче , midили вы можете предпочесть element) для aи resultдля e, так:flatten = lambda *args: (result for mid in args for result in (flatten(*mid) if isinstance(mid, (tuple, list)) else (mid,)))
Приостановлена до дальнейшего уведомления.

Это значительно быстрее, чем compiler.ast.flatten. Отличный, компактный код, работает для любого (я думаю) типа объекта.
bcdan

Ого, это должен быть самый одобренный и принятый ответ ... работает как шарм!
U10-Форвард

27

Эта версия flattenизбегает предела рекурсии Python (и, следовательно, работает с произвольно глубокими вложенными итерациями). Это генератор, который может обрабатывать строки и произвольные итерации (даже бесконечные).

import itertools as IT
import collections

def flatten(iterable, ltypes=collections.Iterable):
    remainder = iter(iterable)
    while True:
        first = next(remainder)
        if isinstance(first, ltypes) and not isinstance(first, (str, bytes)):
            remainder = IT.chain(first, remainder)
        else:
            yield first

Вот несколько примеров, демонстрирующих его использование:

print(list(IT.islice(flatten(IT.repeat(1)),10)))
# [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

print(list(IT.islice(flatten(IT.chain(IT.repeat(2,3),
                                       {10,20,30},
                                       'foo bar'.split(),
                                       IT.repeat(1),)),10)))
# [2, 2, 2, 10, 20, 30, 'foo', 'bar', 1, 1]

print(list(flatten([[1,2,[3,4]]])))
# [1, 2, 3, 4]

seq = ([[chr(i),chr(i-32)] for i in range(ord('a'), ord('z')+1)] + list(range(0,9)))
print(list(flatten(seq)))
# ['a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E', 'f', 'F', 'g', 'G', 'h', 'H',
# 'i', 'I', 'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N', 'o', 'O', 'p', 'P',
# 'q', 'Q', 'r', 'R', 's', 'S', 't', 'T', 'u', 'U', 'v', 'V', 'w', 'W', 'x', 'X',
# 'y', 'Y', 'z', 'Z', 0, 1, 2, 3, 4, 5, 6, 7, 8]

Хотя он flattenможет обрабатывать бесконечные генераторы, он не может обрабатывать бесконечное вложение:

def infinitely_nested():
    while True:
        yield IT.chain(infinitely_nested(), IT.repeat(1))

print(list(IT.islice(flatten(infinitely_nested()), 10)))
# hangs

1
какой-либо консенсус относительно того, использовать ли ABC Iterable или ABC Sequence?
Вим

sets, dicts, deques, listiterators, generators, Дескрипторы файлов, а также пользовательские классы с __iter__определены все случаи collections.Iterable, но не collections.Sequence. Результат сглаживания a dictнемного ненадежен, но в остальном, я думаю, collections.Iterableлучше, чем по умолчанию collections.Sequence. Это определенно более либерально.
unutbu

@wim: Одна проблема с использованием collections.Iterableзаключается в том, что это включает в себя бесконечные генераторы. Я изменил свой ответ справиться с этим делом.
unutbu

1
Это не работает для 3-го и 4-го примеров. Это бросает StopIteration. Кроме того, похоже, while True: first = next(remainder) может быть заменено for first in remainder:.
Георгий

@ Георгий это можно исправить с помощью инкапсуляции тела сглаживания в try-except StopIteration block.
Baduker

12

Вот еще один ответ, который еще интереснее ...

import re

def Flatten(TheList):
    a = str(TheList)
    b,crap = re.subn(r'[\[,\]]', ' ', a)
    c = b.split()
    d = [int(x) for x in c]

    return(d)

По сути, он преобразует вложенный список в строку, использует регулярное выражение для удаления вложенного синтаксиса, а затем преобразует результат обратно в (уплощенный) список.


Если вы попытаетесь обобщить это для чего-то другого, кроме значений int, с этим будет весело, например, [['C=64', 'APPLE ]['], ['Amiga', 'Mac', 'ST']]:). С другой стороны, учитывая список, который содержит себя, это будет немного лучше, чем другие ответы, поднимая исключение вместо того, чтобы просто зацикливаться до тех пор, пока у вас не закончатся память / рекурсивно, пока вы не исчерпаете стек ...
abarnert

Первоначальный запрос был о выравнивании списка целых чисел. Если вы просто измените понимание списка на d = [x для x в c], оно должно нормально работать для вашего образца.
глина

Во-первых, [x for x in c]это медленный и многословный способ сделать копию c, так почему бы вам это сделать? Во-вторых, ваш код явно будет преобразован 'APPLE ]['в 'APPLE ', потому что он не обрабатывает кавычки, он просто предполагает, что любые скобки являются скобками списка.
августа

Ха! То, как ваш комментарий был отформатирован на моем компьютере, я даже не осознавал, что это должен был быть Apple II, как он появился на старых компьютерах. В любом случае, мой ответ на оба ваших вопроса заключается в том, что это упражнение - для меня - просто эксперимент по поиску творческого решения для выравнивания списка. Я не уверен, что обобщил бы это, чтобы сгладить каждый список там.
глина

Тебе просто нужно, arr_str = str(arr)а потом [int(s) for s in re.findall(r'\d+', arr_str)]действительно. См. Github.com/jorgeorpinel/flatten_nested_lists/blob/master/…
Хорхе Орпинель

10
def flatten(xs):
    res = []
    def loop(ys):
        for i in ys:
            if isinstance(i, list):
                loop(i)
            else:
                res.append(i)
    loop(xs)
    return res

8

Вы можете использовать deepflattenиз стороннего пакета iteration_utilities:

>>> from iteration_utilities import deepflatten
>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> list(deepflatten(L))
[1, 2, 3, 4, 5, 6]

>>> list(deepflatten(L, types=list))  # only flatten "inner" lists
[1, 2, 3, 4, 5, 6]

Это итератор, поэтому вам нужно выполнить итерацию (например, обернуть его listили использовать в цикле). Внутренне он использует итеративный подход вместо рекурсивного подхода и написан как расширение C, поэтому он может быть быстрее, чем подходы чистого Python:

>>> %timeit list(deepflatten(L))
12.6 µs ± 298 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit list(deepflatten(L, types=list))
8.7 µs ± 139 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

>>> %timeit list(flatten(L))   # Cristian - Python 3.x approach from https://stackoverflow.com/a/2158532/5393381
86.4 µs ± 4.42 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> %timeit list(flatten(L))   # Josh Lee - https://stackoverflow.com/a/2158522/5393381
107 µs ± 2.99 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> %timeit list(genflat(L, list))  # Alex Martelli - https://stackoverflow.com/a/2159079/5393381
23.1 µs ± 710 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Я автор iteration_utilitiesбиблиотеки.


7

Было забавно пытаться создать функцию, которая могла бы сгладить нерегулярный список в Python, но, конечно, для этого и нужен Python (чтобы программирование было увлекательным). Следующий генератор работает довольно хорошо с некоторыми оговорками:

def flatten(iterable):
    try:
        for item in iterable:
            yield from flatten(item)
    except TypeError:
        yield iterable

Это будет выравниваться типы данных , которые вы можете оставить в покое (как bytearray, bytesи strобъекты). Кроме того, код опирается на тот факт, что запрос итератора из неповторяемого вызывает a TypeError.

>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> def flatten(iterable):
    try:
        for item in iterable:
            yield from flatten(item)
    except TypeError:
        yield iterable


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

Редактировать:

Я не согласен с предыдущей реализацией. Проблема в том, что вы не должны быть в состоянии сгладить то, что не повторяется. Это сбивает с толку и дает неправильное впечатление от аргумента.

>>> list(flatten(123))
[123]
>>>

Следующий генератор почти такой же, как первый, но у него нет проблемы с попыткой выравнивания не повторяемого объекта. Он терпит неудачу, как и следовало ожидать, когда ему дается неуместный аргумент.

def flatten(iterable):
    for item in iterable:
        try:
            yield from flatten(item)
        except TypeError:
            yield item

Тестирование генератора прекрасно работает с предоставленным списком. Тем не менее, новый код будет вызывать, TypeErrorкогда ему дается не повторяемый объект. Ниже приведен пример нового поведения.

>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> list(flatten(L))
[1, 2, 3, 4, 5, 6]
>>> list(flatten(123))
Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    list(flatten(123))
  File "<pyshell#27>", line 2, in flatten
    for item in iterable:
TypeError: 'int' object is not iterable
>>>

5

Хотя был выбран элегантный и очень питонический ответ, я бы представил свое решение только для обзора:

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

Скажите, пожалуйста, насколько хорош или плох этот код?


1
Использование isinstance(i, (tuple, list)). Инициализация пустых переменных - это флаг для меня, чтобы посмотреть на альтернативные структуры кода, как правило, на понимание, генераторы, рекурсию и т. Д.
dansalmo

3
return type(l)(ret)вернет вам тот же тип контейнера, который был передан в. :)
dash-tom-bang

@ dash-tom-bang Не могли бы вы объяснить, что это значит, в деталях.
Ксольв

1
Если вы передадите список, вы, вероятно, захотите вернуть его обратно. Если вы передадите кортеж, вы, вероятно, захотите вернуть кортеж. Если вы пройдете путаницу из этих двух, вы получите то, что было внешней оболочкой.
dash-tom-bang

4

Я предпочитаю простые ответы. Нет генераторов. Нет рекурсии или рекурсивных ограничений. Просто итерация:

def flatten(TheList):
    listIsNested = True

    while listIsNested:                 #outer loop
        keepChecking = False
        Temp = []

        for element in TheList:         #inner loop
            if isinstance(element,list):
                Temp.extend(element)
                keepChecking = True
            else:
                Temp.append(element)

        listIsNested = keepChecking     #determine if outer loop exits
        TheList = Temp[:]

    return TheList

Это работает с двумя списками: внутренний цикл for и внешний цикл while.

Внутренний цикл for просматривает список. Если он находит элемент списка, он (1) использует list.extend (), чтобы сгладить эту часть первого уровня вложенности, и (2) переключает keepChecking в True. keepchecking используется для управления внешним циклом while. Если внешний цикл установлен в значение true, он запускает внутренний цикл для другого прохода.

Эти проходы продолжаются до тех пор, пока не будут найдены вложенные списки. Когда, наконец, происходит проход, где ничего не найдено, keepChecking никогда не получает значение true, что означает, что listIsNested остается false, а внешний цикл while завершается.

Выровненный список затем возвращается.

Тестовый забег

flatten([1,2,3,4,[100,200,300,[1000,2000,3000]]])

[1, 2, 3, 4, 100, 200, 300, 1000, 2000, 3000]


Мне тоже нравится простой. Однако в этом случае вы перебираете список столько раз, сколько существует вложений или уровней. Может стать дорогим.
Telliott99

@ telliott99: Вы правы, если ваши списки действительно большие и / или вложены в большую глубину. Однако, если это не так, то более простое решение работает так же хорошо, и без глубокой магии некоторых других ответов. Есть место для многоступенчатого рекурсивного понимания генератора, но я не уверен, что это должно быть именно там, где вы смотрите в первую очередь. (Полагаю, вы знаете, куда я попал в дебатах «Хуже лучше».)
глина

@ telliott99: Или, иначе говоря, вам не придется «пытаться обмануть» мое решение. Если производительность не является узким местом, что для вас как для программиста имеет наибольшее значение?
глина

У более простых решений меньше логики. Рекурсия - довольно фундаментальная программная конструкция, с которой любой, кто считает себя программистом, должен быть полностью доволен. Генераторы очень похожи на Python Way и (наряду с пониманием) - это то, что любой профессиональный программист на Python должен немедленно получить.
тир-том- взрывы

1
Я согласен с рекурсией. Когда я написал свой ответ, Python все еще ломал рекурсию на 1000 циклов. Они изменили это? Что касается того, чтобы быть профессиональным программистом Python, я не. Более того, я представляю, что многие люди, программирующие на python, не занимаются этим постоянно.
глина

4

Вот простая функция, которая выравнивает списки произвольной глубины. Нет рекурсии, чтобы избежать переполнения стека.

from copy import deepcopy

def flatten_list(nested_list):
    """Flatten an arbitrarily nested list, without recursion (to avoid
    stack overflows). Returns a new list, the original list is unchanged.

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

    """
    nested_list = deepcopy(nested_list)

    while nested_list:
        sublist = nested_list.pop(0)

        if isinstance(sublist, list):
            nested_list = sublist + nested_list
        else:
            yield sublist

Да! Очень похоже на мой код на github.com/jorgeorpinel/flatten_nested_lists/blob/master/…
Хорхе Орпинел

3

Я удивлен, что никто не подумал об этом. Чертова рекурсия Я не получаю рекурсивных ответов, которые сделали продвинутые люди здесь. в любом случае, вот моя попытка на это. предостережение это очень специфично для варианта использования ОП

import re

L = [[[1, 2, 3], [4, 5]], 6]
flattened_list = re.sub("[\[\]]", "", str(L)).replace(" ", "").split(",")
new_list = list(map(int, flattened_list))
print(new_list)

вывод:

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

3

Я не просмотрел все уже имеющиеся ответы здесь, но вот один вкладыш, который я придумал, заимствуя способ lisp обработки первого и остальных списков

def flatten(l): return flatten(l[0]) + (flatten(l[1:]) if len(l) > 1 else []) if type(l) is list else [l]

Вот один простой и один не очень простой случай -

>>> flatten([1,[2,3],4])
[1, 2, 3, 4]

>>> flatten([1, [2, 3], 4, [5, [6, {'name': 'some_name', 'age':30}, 7]], [8, 9, [10, [11, [12, [13, {'some', 'set'}, 14, [15, 'some_string'], 16], 17, 18], 19], 20], 21, 22, [23, 24], 25], 26, 27, 28, 29, 30])
[1, 2, 3, 4, 5, 6, {'age': 30, 'name': 'some_name'}, 7, 8, 9, 10, 11, 12, 13, set(['set', 'some']), 14, 15, 'some_string', 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
>>> 

Это не один лайнер. Независимо от того, сколько вы пытаетесь вписать его в одну, def foo():это отдельная линия. Кроме того, это очень нечитаемо.
cs95

Я удалил код в одну строку и произвел дальнейший рефакторинг. (редактирование находится на рассмотрении, пока я пишу это) Этот конкретный метод показался мне очень читабельным, хотя исходный код действительно нуждался в рефакторинге.
Эмилио М Бумачар

3

При попытке ответить на такой вопрос вам действительно нужно указать ограничения кода, который вы предлагаете в качестве решения. Если бы речь шла только о производительности, я бы не возражал, но большинство кодов, предложенных в качестве решения (включая принятый ответ), не могут сгладить ни один список, глубина которого превышает 1000.

Когда я говорю большинство кодов я имею в виду все коды, которые используют любую форму рекурсии (или вызывают стандартную библиотечную функцию, которая является рекурсивной). Все эти коды не выполняются, потому что для каждого сделанного рекурсивного вызова стек (вызова) увеличивается на одну единицу, а стек вызовов (по умолчанию) python имеет размер 1000.

Если вы не слишком знакомы со стеком вызовов, возможно, вам поможет следующее (в противном случае вы можете просто прокрутить до реализации ).

Размер стека вызовов и рекурсивное программирование (аналогия подземелий)

Найти клад и выйти

Представьте, что вы входите в огромное подземелье с пронумерованными комнатами в поисках сокровищ. Вы не знаете место, но у вас есть некоторые указания о том, как найти клад. Каждое указание - загадка (сложность варьируется, но вы не можете предсказать, насколько сложными они будут). Вы решаете немного подумать о стратегии экономии времени, вы делаете два замечания:

  1. Трудно (долго) найти сокровище, так как вам придется разгадывать (потенциально сложные) загадки, чтобы добраться туда.
  2. Как только сокровище найдено, возвращение ко входу может быть легким, вам просто нужно использовать тот же путь в другом направлении (хотя это требует немного памяти, чтобы вспомнить ваш путь).

При входе в темницу вы видите небольшую тетрадь здесь. Вы решаете использовать его, чтобы записать каждую комнату, в которую вы выходите после решения загадки (при входе в новую комнату), таким образом, вы сможете вернуться обратно ко входу. Это гениальная идея, вы даже не будете тратить ни цента реализацию своей стратегии.

Вы входите в темницу, с большим успехом разгадывая первые 1001 загадку, но тут появляется то, что вы не планировали, у вас нет места в заимствованной вами записной книжке. Вы решаете отказаться от своего квеста, так как вы предпочитаете не иметь сокровища, а быть потерянным навсегда в подземелье (это выглядит действительно умно).

Выполнение рекурсивной программы

По сути, это то же самое, что найти клад. Подземелье - это память компьютера. Ваша цель теперь не в том, чтобы найти сокровище, а в том, чтобы вычислить некоторую функцию (найти f (x) для заданного x ). Показания просто подпрограммы, которые помогут вам решить f (x) . Ваша стратегия аналогична стратегии стека вызовов , ноутбук - это стек, комнаты - адреса возврата функций:

x = ["over here", "am", "I"]
y = sorted(x) # You're about to enter a room named `sorted`, note down the current room address here so you can return back: 0x4004f4 (that room address looks weird)
# Seems like you went back from your quest using the return address 0x4004f4
# Let's see what you've collected 
print(' '.join(y))

Проблема, с которой вы столкнулись в подземелье, будет такой же: стек вызовов имеет конечный размер (здесь 1000), и поэтому, если вы введете слишком много функций без возврата назад, вы заполняете стек вызовов и получаете сообщение об ошибке например, «Дорогой искатель приключений, мне очень жаль, но ваша записная книжка заполнена», : которая вызывает себя один раз - снова и снова - вы будете входить снова и снова, пока не закончится вычисление (пока не будет найдено сокровище) и вернетесь с до Вы возвращаетесь к тому месту, где вы в первую очередь звонили . Стек вызовов никогда не будет освобожден ни от чего до конца, где он будет освобожден от всех адресов возврата один за другим.RecursionError: maximum recursion depth exceeded . Обратите внимание, что вам не нужна рекурсия для заполнения стека вызовов, но очень маловероятно, чтобы нерекурсивная программа вызывала 1000 функций без какого-либо возврата. Также важно понимать, что после того, как вы вернулись из функции, стек вызовов освобождается от используемого адреса (следовательно, имя «стек», адрес возврата вставляется перед входом в функцию и извлекается при возврате). В частном случае простой рекурсии (функцияffff

Как избежать этой проблемы?

Это на самом деле довольно просто: «не используйте рекурсию, если вы не знаете, как глубоко она может зайти». Это не всегда так, поскольку в некоторых случаях рекурсия Tail Call может быть оптимизирована (TCO) . Но в python это не так, и даже «хорошо написанная» рекурсивная функция не оптимизирует использование стека. Есть интересный пост от Гвидо по этому вопросу: устранение рекурсии хвоста .

Есть методика, которую вы можете использовать, чтобы сделать любую рекурсивную функцию итеративной, этот метод, который мы могли бы назвать, принесет ваш собственный блокнот . Например, в нашем конкретном случае мы просто изучаем список, вход в комнату эквивалентен вводу подсписка, вопрос, который вы должны задать себе, - как я могу вернуться из списка в его родительский список? Ответ не так уж сложен, повторяйте следующее до тех пор, пока поле не stackстанет пустым:

  1. выдвигать текущий список addressи indexв stackпри входе в новый подсписок (обратите внимание, что адрес списка + индекс также является адресом, поэтому мы просто используем точно такой же метод, который используется стеком вызовов);
  2. каждый раз, когда элемент найден, yieldон (или добавить их в список);
  3. как только список полностью изучен, вернитесь к родительскому списку, используя stack return addressindex) .

Также обратите внимание, что это эквивалентно DFS в дереве, где некоторые узлы являются подсписками, A = [1, 2]а некоторые - простыми элементами: 0, 1, 2, 3, 4(для L = [0, [1,2], 3, 4]). Дерево выглядит так:

                    L
                    |
           -------------------
           |     |     |     |
           0   --A--   3     4
               |   |
               1   2

Предварительный порядок обхода DFS: L, 0, A, 1, 2, 3, 4. Помните, что для реализации итеративной DFS вам также «нужен» стек. Реализация, которую я предложил ранее, привела к следующим состояниям (для stackи flat_list):

init.:  stack=[(L, 0)]
**0**:  stack=[(L, 0)],         flat_list=[0]
**A**:  stack=[(L, 1), (A, 0)], flat_list=[0]
**1**:  stack=[(L, 1), (A, 0)], flat_list=[0, 1]
**2**:  stack=[(L, 1), (A, 1)], flat_list=[0, 1, 2]
**3**:  stack=[(L, 2)],         flat_list=[0, 1, 2, 3]
**3**:  stack=[(L, 3)],         flat_list=[0, 1, 2, 3, 4]
return: stack=[],               flat_list=[0, 1, 2, 3, 4]

В этом примере максимальный размер стека равен 2, поскольку входной список (и, следовательно, дерево) имеют глубину 2.

Реализация

Для реализации в Python вы можете немного упростить использование итераторов вместо простых списков. Ссылки на (под) итераторы будут использоваться для хранения адресов возврата подсписков (вместо того, чтобы иметь адрес списка и индекс). Это не большая разница, но я чувствую, что это более читабельно (а также немного быстрее):

def flatten(iterable):
    return list(items_from(iterable))

def items_from(iterable):
    cursor_stack = [iter(iterable)]
    while cursor_stack:
        sub_iterable = cursor_stack[-1]
        try:
            item = next(sub_iterable)
        except StopIteration:   # post-order
            cursor_stack.pop()
            continue
        if is_list_like(item):  # pre-order
            cursor_stack.append(iter(item))
        elif item is not None:
            yield item          # in-order

def is_list_like(item):
    return isinstance(item, list)

Кроме того, обратите внимание, что у is_list_likeменя есть isinstance(item, list), который может быть изменен для обработки большего количества типов ввода, здесь я просто хотел иметь простейшую версию, где (итерируемый) это просто список. Но вы также можете сделать это:

def is_list_like(item):
    try:
        iter(item)
        return not isinstance(item, str)  # strings are not lists (hmm...) 
    except TypeError:
        return False

Это рассматривает строки как "простые элементы" и поэтому flatten_iter([["test", "a"], "b])будет возвращаться, ["test", "a", "b"]а не возвращаться ["t", "e", "s", "t", "a", "b"]. Обратите внимание, что в этом случае iter(item)вызывается дважды для каждого элемента, давайте представим, что это упражнение для читателя, чтобы сделать это чище.

Тестирование и замечания по другим реализациям

В конце помните, что вы не можете распечатать бесконечно вложенный список, Lиспользуя, print(L)потому что внутренне он будет использовать рекурсивные вызовы to __repr__( RecursionError: maximum recursion depth exceeded while getting the repr of an object). По той же причине, решения для flattenвовлечения strпотерпят неудачу с тем же сообщением об ошибке.

Если вам нужно протестировать свое решение, вы можете использовать эту функцию для генерации простого вложенного списка:

def build_deep_list(depth):
    """Returns a list of the form $l_{depth} = [depth-1, l_{depth-1}]$
    with $depth > 1$ and $l_0 = [0]$.
    """
    sub_list = [0]
    for d in range(1, depth):
        sub_list = [d, sub_list]
    return sub_list

Который дает: build_deep_list(5)>>> [4, [3, [2, [1, [0]]]]].


2

Вот compiler.ast.flattenреализация в 2.7.5:

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

Есть лучшие, более быстрые методы (если вы достигли здесь, вы уже видели их)

Также обратите внимание:

Устаревший с версии 2.6: пакет компилятора был удален в Python 3.


2

полностью взломанный, но я думаю, что это будет работать (в зависимости от вашего data_type)

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

2

Просто используйте funcyбиблиотеку: pip install funcy

import funcy


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

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

Он может игнорировать любой конкретный (или производный) тип, который вам нужен, он возвращает итератор, поэтому вы можете преобразовать его в любой конкретный контейнер, такой как 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) против 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

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

Использование 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

Так что после того, как я определил функцию comb_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: maximum recursion depth exceeded
cglacet

хммм я пытаюсь разжать список с более чем 1000 слоями?
Oldyoung

Конечно, в этом весь смысл обсуждения рекурсивных и итеративных решений. Если вы заранее знаете, что количество слоев меньше 1000, тогда подойдет самое простое решение. Когда вы говорите «любая глубина», это включает список с глубиной> 1000.
cglacet

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

Я знаю, что уже есть много удивительных ответов, но я хотел бы добавить ответ, который использует метод функционального программирования для решения вопроса. В этом ответе я использую двойную рекурсию:

def flatten_list(seq):
    if not seq:
        return []
    elif isinstance(seq[0],list):
        return (flatten_list(seq[0])+flatten_list(seq[1:]))
    else:
        return [seq[0]]+flatten_list(seq[1:])

print(flatten_list([1,2,[3,[4],5],[6,7]]))

вывод:

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

1

Я не уверен, если это обязательно быстрее или эффективнее, но это то, что я делаю:

def flatten(lst):
    return eval('[' + str(lst).replace('[', '').replace(']', '') + ']')

L = [[[1, 2, 3], [4, 5]], 6]
print(flatten(L))

flattenФункция здесь превращает список в строку, вынимает все квадратные скобки, придает квадратные скобки назад на концах, и превращает его обратно в список.

Хотя, если бы вы знали, что у вас в списке будут квадратные скобки, например [[1, 2], "[3, 4] and [5]"], вам придется заняться чем-то другим.


Это не имеет никакого преимущества перед простым решением, так как не обрабатывает глубокие списки, то есть «RecursionError: максимальная глубина рекурсии превышена при получении repr объекта».
cglacet

1

Это простая реализация flatten на python2

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, чтобы указать, хотите ли вы получить ключи или значения (установите для ключа значение 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

Если вам нравится рекурсия, это может быть интересным для вас решением:

def f(E):
    if E==[]: 
        return []
    elif type(E) != list: 
        return [E]
    else:
        a = f(E[0])
        b = f(E[1:])
        a.extend(b)
        return a

Я на самом деле адаптировал это из некоторого практического кода Схемы, который я написал некоторое время назад.

Наслаждайтесь!


0

Я новичок в Python и пришел с фоном LISP. Вот что я придумал (посмотрите названия вар для lulz):

def flatten(lst):
    if lst:
        car,*cdr=lst
        if isinstance(car,(list,tuple)):
            if cdr: return flatten(car) + flatten(cdr)
            return flatten(car)
        if cdr: return [car] + flatten(cdr)
        return [car]

Кажется, работает. Тестовое задание:

flatten((1,2,3,(4,5,6,(7,8,(((1,2)))))))

возвращает:

[1, 2, 3, 4, 5, 6, 7, 8, 1, 2]
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.