Вирівняти неправильний список списків


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
Той факт, що існує стільки відповідей і стільки дій з цього питання насправді говорить про те, що це має бути десь вбудованою функцією, правда? Особливо погано компілятор.ast був видалений з Python 3.0
Mittenchops

3
Я б сказав, що дійсно потрібно Python - це неперервна рекурсія, а не інший вбудований модуль.
глина

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

1
Якщо ви можете дозволити додати пакет до свого проекту - я думаю, що рішення more_itertools.collapse зробить це найкраще. З цієї відповіді: stackoverflow.com/a/40938883/3844376
viddik13

Відповіді:


382

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

Пітон 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.

Пітон 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?
Джош

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

6
Для Python 3.7 використання collections.Iterableзастаріло. Використовуйте collections.abc.Iterableзамість цього.
dawg

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 - це недолік, якого варто уникати.
abarnert

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

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

список (вирівняти ([['X'], 'Y'])) не вдається для варіанту 2.X
sten

@ user1019129 дивіться мій коментар вище вашого
dansalmo

так, це не вдається з циклом .. Я думаю, тому що рядок також є "масивом" -of-chars
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
Я думаю, що вам потрібно перевірити на рядки - наприклад, додати ", а не є речовина (l [0], basestring)", як у рішенні Крістіана. В іншому випадку ви отримаєте нескінченну петлю навколо l [0: 1] = l [0]
c-urchin

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

28

Ось моя функціональна версія рекурсивного вирівнювання, яка обробляє і кортежі, і списки, і дозволяє вам кидати будь-яку суміш позиційних аргументів. Повертає генератор, який виробляє всю послідовність у порядку, arg по 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дозволяє уникнути межі рекурсії пітона (і, таким чином, працює з довільно глибокими, вкладеними ітерабелями). Це генератор, який може обробляти рядки та довільні ітерабелі (навіть нескінченні).

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:.
Георгій

@Georgy це могло б бути виправлено шляхом інкапсуляції тіла плоскостопості в a 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 ', тому що вона не працює з посиланням, він просто приймає будь-які дужки список дужки.
abarnert

Га! У тому, як ваш коментар відформатувався на моєму комп’ютері, я навіть не зрозумів, що це повинен бути 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

Тестування генератора працює добре зі списком, який було надано. Однак новий код підніме a, 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 Чи можете ви, будь ласка, пояснити, що це означає трохи детальніше.
Xolve

1
Якщо ви переходите до списку, вам, ймовірно, потрібен список назад. Якщо ви переходите в кортеж, ви, мабуть, захочете кортеж назад. Якщо ви перейдете в мішанку з двох, ви отримаєте те, що було зовнішньою огороджувальною річчю.
Даш-Том-Банг

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

Це працює з двома списками: внутрішнім для циклу та зовнішнім циклом while.

Внутрішня для циклу проходить через список. Якщо він знаходить елемент списку, він (1) використовує list.extend () для вирівнювання цієї частини першого рівня вкладення та (2) перемикання KeepChecking на True. Проведення контрольної перевірки використовується для керування зовнішньою петлею. Якщо зовнішня петля встановлена ​​на істинну, вона запускає внутрішню петлю для іншого проходу.

Ці пропуски відбуваються до тих пір, поки не знайдеться більше вкладених списків. Коли пропуск нарешті виникає там, де його не знайдено, KeepChecking ніколи не стикається з істинним, а значить listIsNested залишається помилковим, а зовнішній, поки цикл виходить.

Потім сплющений список повертається.

Тестовий запуск

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
Я погоджуюся щодо рекурсії. Коли я написав свою відповідь, пітон все-таки пробив рекурсію на 1000 циклів. Вони змінили це? Щодо того, що я є професійним програмістом пітонів, я це не так. Більше того, я думаю, що багато людей, які програмують в 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/…
Jorge Orpinel

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.

Коли я кажу більшість кодів я маю на увазі всі коди, які використовують будь-яку форму рекурсії (або називають стандартну функцію бібліотеки, яка є рекурсивною). Усі ці коди виходять з ладу, оскільки для кожного здійсненого рекурсивного виклику стек (виклик) зростає на одну одиницю, а стек виклику (за замовчуванням) пітона має розмір 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 функцій, не повертаючись ніколи. Важливо також розуміти, що після повернення з функції стек виклику звільняється від використовуваної адреси (звідси і назва "стек", повертаюча адреса вводиться перед введенням функції та вилучається при поверненні). В окремому випадку простої рекурсії (функціяfщо дзвонить собі раз і знову -) ви будете входити fзнову і знову, поки обчислення не будуть закінчені (поки скарб не знайдеться) і повернетесь, fпоки не повернетесь туди, куди ви зателефонували fв першу чергу. Стек викликів ніколи не буде звільнений від нічого до кінця, коли він буде звільнений від усіх зворотних адрес одна за одною.

Як уникнути цього питання?

Це насправді досить просто: "не використовуйте рекурсії, якщо ви не знаєте, наскільки глибоко це може пройти". Це не завжди так, як в деяких випадках, рекурсія виклику хвоста може бути оптимізована (TCO) . Але в python це не так, і навіть «добре написана» рекурсивна функція не оптимізує використання стеку. З цього питання є цікавий пост від Гуйдо: Усунення рекурсії хвоста .

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

  1. натисніть на поточний список addressі indexв a 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)оскільки внутрішньо він використовуватиме рекурсивні дзвінки до __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

повністю хакі, але я думаю, що це буде працювати (залежно від вашого типу даних)

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


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, тому, хоча він є рекурсивним, AFAIK його не обмежує глибина рекурсії пітона, оскільки виклики функцій відбуваються в 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

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

Тож після того, як я визначу функцію комбінований список, легко скористатися цією функцією зробити вирівнювання. Або ви можете об'єднати його в одну функцію. Мені подобається моє рішення, оскільки його можна застосувати до будь-якого вкладеного списку.

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

Найпростіший спосіб - це використання бібліотеки морфів за допомогою 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: максимальна глибина рекурсії перевищена під час отримання репр об'єкта".
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

Це згладить список або словник (або список списків або словники словників тощо). Він передбачає, що значення - це рядки, і він створює рядок, який з'єднує кожен елемент аргументом роздільника. Якщо ви хочете, ви можете використовувати роздільник, щоб розділити результат на об'єкт списку. Він використовує рекурсію, якщо наступним значенням є список або рядок. Використовуйте ключовий аргумент, щоб сказати, чи потрібно клавіші або значення (встановити ключ на помилкове) від об'єкта словника.

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 і походжу з фонового малюнка. Ось що я придумав (перевірте назви вар для 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.