불규칙한 목록 목록을 병합


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
이 질문에 대한 많은 답변과 조치가 있다는 사실은 이것이 실제로 어딘가에 내장 된 기능이어야 함을 시사합니다. 특히 컴파일러가 너무 나쁩니다. Python 3.0에서 제거되었습니다.
Mittenchops

3
파이썬이 실제로 필요한 것은 다른 내장 함수가 아닌 끊임없는 재귀라고 말합니다.
클레이

2
@Mittenchops : 완전히 잘못된 의견, 명백히 나쁜 API / 과도하게 복잡한 데이터 구조를 사용하는 사람들 (단지 참고 : list균질하도록 의도 된 것)은 이것이 파이썬의 결함을 의미하지 않으며 그러한 작업을위한 내장이 필요합니다
Azat Ibrakov

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

2.6에 추가 된 Iterable ABC를 사용했습니다 .

파이썬 3

파이썬 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 2016 년

7
이것은 또한 사전을 평평하게합니다. 어쩌면 당신은 collections.Sequence대신에 사용하고 싶 collections.Iteratable습니까?
josch 2016 년

1
처음에 나열되지 않은 것 (예 :)에서는 작동하지 않습니다 for i in flatten(42): print (i). isinstance-test 및 else-clause를 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)iterable 여부를 테스트하기 위해 아무것도 가져 오지 않고도이 작업을 수행 할 수 있습니다 ...하지만 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]있지만 여기서는 가독성이 주관적 일 수 있습니다.
Zero

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
고마워, 그것은 파이썬 3에서 잘 작동합니다. 2.x의 경우 이전 버전이 필요합니다. for i in flatten(item): yield i
dansalmo

list (flatten ([[ 'X'], 'Y'])) 실패 2.X 변형
sten

@ user1019129 당신의 위의 내 의견보기
dansalmo

예, 사이클에 실패합니다. 문자열도 "어레이"-문자이므로
sten

35

@Andrew의 요청에 따라 @unutbu의 비 재귀 솔루션의 생성기 버전은 다음과 같습니다.

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)

중첩 된 목록에 의해 형성된 트리의 선주문 순서입니다. 잎만 반환됩니다. 이 구현은 원본 데이터 구조를 더 좋게 또는 더 많이 소비합니다. 원래 트리를 유지하면서 목록 항목을 복사 할 필요가없는 것을 작성하는 것이 재미있을 수 있습니다.
Andrew Wagner

6
문자열을 테스트해야한다고 생각합니다. 예를 들어 Cristian의 솔루션에서와 같이 "and isinstance (l [0], basestring)"을 추가하십시오. 그렇지 않으면 당신은 l [0 : 1] = l [0] 주위에 무한 루프를 얻는다
c-urchin

이것은 생성기를 만드는 좋은 예이지만 c-urchin이 언급했듯이 시퀀스에 문자열이 있으면 알고리즘 자체가 실패합니다.
Daniel 'Dang'Griffith

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를 참조
크리스토프 래

2
@WolfgangKuehne : 시도 args에 대한 n, intermediate(또는 짧은 mid또는 당신이 선호하는 수도 element)에 대한 aresult를 위해 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-Forward

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 사용 여부에 대한 합의가 있습니까?
wim

sets, dicts, deques, listiterators, generators,와 파일 핸들 및 사용자 정의 클래스 __iter__정의는 모든 인스턴스 collections.Iterable가 아니라이 collections.Sequence. a를 평평하게 한 결과는 dict약간 iffy이지만 그렇지 않은 경우 collections.Iterable보다 더 나은 기본값 이라고 생각 collections.Sequence합니다. 확실히 더 자유 롭습니다.
unutbu

@ wim : 사용의 한 가지 문제 collections.Iterable는 무한 생성기가 포함된다는 것입니다. 이 경우 내 답변 처리를 변경했습니다.
unutbu

1
이것은 세 번째와 네 번째 예제에서는 작동하지 않는 것 같습니다. 던졌습니다 StopIteration. 또한 while True: first = next(remainder) 로 대체 될 수있는 것 같습니다 for first in remainder:.
Georgy

@Georgy 이것은 평평한 몸을에 캡슐화하여 고정시킬 수 있습니다 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 in x in 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/…를
Jorge Orpinel

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 확장으로 작성되어 순수한 파이썬 접근 방식보다 빠릅니다.

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

파이썬에서 불규칙한 목록을 평평하게 만들 수있는 함수를 만드는 것은 재미 있었지만, 물론 그것은 파이썬이 프로그래밍을 즐겁게하기위한 것입니다. 다음과 같은 생성기는 일부 경고에 상당히 잘 작동합니다.

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

혼자 남겨두고 싶은 데이터 유형 (예 bytearray: bytes, 및 str객체) 을 평평하게 합니다. 또한 코드는 반복 불가능한 반복자를 요청하면 사실이 발생한다는 사실에 의존합니다.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

우아하고 매우 pythonic 답변이 선택되었지만 검토를 위해 내 솔루션을 제시합니다.

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
목록을 전달하면 목록을 다시 원할 것입니다. 튜플을 전달하면 튜플을 다시 원할 것입니다. 두 가지의 엉망을지나 치면 바깥 쪽이 무엇이든 얻을 수 있습니다.
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 : 당신의 목록이 정말로 크고 깊이있는 경우에 옳습니다. 그러나 그렇지 않은 경우 더 간단한 솔루션은 다른 답변의 깊은 마술 없이도 잘 작동합니다. 다단계 재귀 생성기 이해를위한 장소가 있지만, 내가 먼저보아야 할 곳이 확실하지 않습니다. (내가 "Worse is Better"토론에 빠진 곳을 아는 것 같다.)
clay

@ telliott99 : 또는 다른 방법으로, 내 솔루션을 "Grok"할 필요가 없습니다. 성능이 병목 현상이 아닌 경우 프로그래머로서 가장 중요한 것은 무엇입니까?
clay

간단한 솔루션은 로직이 적습니다. 재귀는 프로그래머라고 생각하는 사람이라면 누구나 쉽게 이해할 수있는 매우 기본적인 프로그래밍 구조입니다. 제너레이터는 파이썬 방식과 매우 비슷하며 (전문가와 함께) 전문 파이썬 프로그래머가 즉시 이해해야 할 부분입니다.
dash-tom-bang

1
재귀에 동의합니다. 내 대답을 쓸 때 파이썬은 여전히 ​​1000주기에서 재귀를 끊었습니다. 그들은 이것을 바 꾸었습니까? 전문적인 파이썬 프로그래머가되는 것은 아닙니다. 또한 파이썬으로 프로그래밍하는 많은 사람들이 풀 타임으로 그렇게하지 않는다고 생각합니다.
clay

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


3

아무도 이것에 대해 생각하지 않은 것에 놀랐습니다. 재귀 끔찍한 나는 여기 고급 사람들이 한 재귀 답변을 얻지 못합니다. 어쨌든 여기에 내 시도가 있습니다. 주의 사항은 OP의 사용 사례에 매우 구체적입니다.

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

코드를 한 줄로 분리하고 추가 리팩토링을 수행했습니다. (이 글을 쓰는 동안 편집은 동료 검토 대기 중입니다.)이 특정 방법은 나에게 매우 읽기 쉬운 것처럼 보였지만 원래 코드에는 약간의 리팩토링이 필요했습니다.
Emilio M Bumachar

3

그러한 질문에 대답하려고 할 때 실제로 솔루션으로 제안하는 코드의 한계를 제시해야합니다. 성능에만 관심이 있다면별로 신경 쓰지 않지만 솔루션으로 제안 된 대부분의 코드 (응답 포함)는 깊이가 1000보다 큰 목록을 평평하게하지 못합니다.

내가 대부분의 코드를 말할 때 모든 형태의 재귀를 사용하는 모든 코드를 의미합니다 (또는 재귀적인 표준 라이브러리 함수를 호출합니다). 모든 재귀 호출에 대해 (호출) 스택이 한 단위 씩 증가하고 (기본) 파이썬 호출 스택의 크기가 1000이기 때문에 이러한 코드는 모두 실패합니다.

호출 스택에 익숙하지 않은 경우 다음이 도움이 될 것입니다 (그렇지 않으면 구현으로 스크롤 할 수 있습니다 ).

콜 스택 크기 및 재귀 프로그래밍 (던전 유추)

보물 찾기 및 종료

번호가 매겨진 방 으로 거대한 지하 감옥에 들어가 보물을 찾고 있다고 상상해보십시오 . 당신은 장소를 모르지만 보물을 찾는 방법에 대한 표시 가 있습니다. 각 표시는 수수께끼입니다 (난이도는 다르지만 얼마나 힘들지는 예측할 수 없습니다). 시간을 절약하기위한 전략에 대해 약간 생각하고 두 가지 관찰을합니다.

  1. 보물을 찾기가 어렵 기 때문에 (잠재적으로) 수수께끼를 풀어야합니다.
  2. 보물이 발견되면 입구로 돌아가는 것이 쉬울 수 있지만 다른 방향으로 동일한 경로를 사용해야합니다 (경로를 기억하려면 약간의 메모리가 필요하지만).

지하 감옥에 들어가면 여기에 작은 공책이 있습니다. 수수께끼를 풀고 (새 방에 들어올 때) 나가는 모든 방을 적어두기로 결정하면 입구로 돌아갈 수 있습니다. 그것은 천재적인 아이디어입니다, 당신 은 심지어 센트를 소비하지 않을 것입니다 입니다. 전략을 구현 .

당신은 지하 감옥에 들어가서 첫 번째 1001 수수께끼를 성공적으로 해결했지만 여기에 계획하지 않은 것이 나오고, 빌린 노트북에 남은 공간이 없습니다. 던전 안에서 영원히 잃어버린 것보다 보물을 좋아하지 않기 때문에 퀘스트 를 포기 하기로 결정합니다 (실제로는 똑똑해 보입니다).

재귀 프로그램 실행

기본적으로 보물을 찾는 것과 똑같습니다. 던전은 컴퓨터의 메모리입니다 . 이제 목표는 보물을 찾는 것이 아니라 일부 함수계산하는 것입니다 ( 주어진 x에 대한 f (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을 호출 할 가능성은 거의 없습니다. 함수에서 리턴 한 후에는 호출 스택이 사용 된 주소에서 해제됩니다 (따라서 이름 "stack", 리턴 주소는 함수에 들어가기 전에 푸시되고 리턴 할 때 꺼내집니다). 간단한 재귀의 특별한 경우 (함수ff계산이 끝날 때까지 (보물이 발견 될 때까지) 반복해서 입력 하고 처음부터 f전화했던 곳으로 돌아갈 때까지 돌아갑니다 f. 호출 스택은 모든 리턴 주소에서 차례로 해제 될 때까지 어떤 것도 해제되지 않습니다.

이 문제를 피하는 방법?

실제로는 매우 간단합니다. "얼마나 깊이 갈 수 있는지 모르는 경우 재귀를 사용하지 마십시오". 경우에 따라 Tail Call 재귀를 최적화 할 수있는 것처럼 항상 사실은 아닙니다 (TCO) . 그러나 파이썬에서는 그렇지 않습니다. 심지어 "잘 작성된"재귀 함수조차도 스택 사용을 최적화 하지 않습니다 . 이 질문에 대한 귀도의 흥미로운 게시물이 있습니다 : Tail Recursion Elimination .

재귀 함수를 반복적으로 만드는 데 사용할 수있는 기술이 있습니다.이 기술을 사용 하여 자신의 노트북을 가져올 수 있습니다 . 예를 들어, 특별한 경우에 우리는 단순히 목록을 탐색하고 방을 입력하는 것은 하위 목록을 입력하는 것과 같습니다. 스스로 질문해야 할 것은 목록에서 부모 목록으로 어떻게 돌아갈 수 있습니까? 대답은 그렇게 복잡하지 않습니다. stack이 비어 있을 때까지 다음을 반복하십시오 .

  1. 현재리스트를 푸시 addressindexA의 stack새로운 하위 목록 입력 (주리스트 어드레스 + 인덱스 따라서 우리는 단지 호출 스택에서 사용되는 동일한 기술을 사용하여, 또한 어드레스 있음);
  2. 항목을 찾을 때마다 yield(또는 목록에 추가)
  3. 목록이 완전히 탐색되면 stack return address(및 index)을 사용하여 상위 목록으로 돌아갑니다 .

또한 이것은 일부 노드가 하위 목록 A = [1, 2]이고 일부는 간단한 항목 인 0, 1, 2, 3, 4(for L = [0, [1,2], 3, 4]) 트리의 DFS와 같습니다 . 나무는 다음과 같습니다

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

DFS 순회 예약 주문은 L, 0, A, 1, 2, 3, 4입니다. 반복적 인 DFS를 구현하려면 스택이 "필요"합니다. 구현 I합니다 (상태가 다음 필요에 따라서 이전에 제안 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입니다.

이행

구현을 위해 파이썬에서는 간단한 목록 대신 반복자를 사용하여 조금 단순화 할 수 있습니다. (반복자) 반복자에 대한 참조는 ( 목록 주소와 색인이 아닌) 하위 목록 리턴 주소 를 저장하는 데 사용됩니다 . 이것은 큰 차이는 아니지만 이것이 더 읽기 쉽다고 느낍니다.

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 objectflattenstr

솔루션을 테스트해야하는 경우이 함수를 사용하여 간단한 중첩 목록을 생성 할 수 있습니다.

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.flatten2.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
참고 : 그것은 재귀 솔루션을 사용합니다 : 소스에 링크
Georgy

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

원하는 특정 (또는 파생 된) 유형을 무시하고 반복자를 반환하므로 목록, 튜플, dict와 같은 특정 컨테이너로 변환하거나 메모리 사용 공간을 줄이기 위해 간단히 소비 할 수 있습니다. int와 같은 반복 불가능한 초기 객체를 처리 할 수 ​​있습니다 ...

itertools가 구현되는 방식을 알고 있기 때문에 대부분의 무거운 리프팅은 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 (number of types)가 필요합니다. 물론 언급 된 무시 된 유형의 파생 유형이있는 값은 실패합니다. 그것의 사용 이유이다 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

따라서 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: 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]"]다른 작업을 수행해야합니다.


이것은 딥리스트를 처리하지 못하기 때문에 간단한 솔루션에 비해 이점이 없습니다.
cglacet

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

이렇게하면 목록 또는 사전 (또는 사전 목록 또는 사전 사전 등)이 병합됩니다. 값이 문자열이라고 가정하고 구분자 인수로 각 항목을 연결하는 문자열을 만듭니다. 원하는 경우 나중에 구분 기호를 사용하여 결과를 목록 개체로 분할 할 수 있습니다. 다음 값이 목록 또는 문자열이면 재귀를 사용합니다. 키 인수를 사용하여 사전 오브젝트에서 키 또는 값 (키를 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

나는 파이썬을 처음 사용하고 lisp 배경에서 왔습니다. 이것은 내가 생각해 낸 것입니다 (lulz의 var 이름을 확인하십시오).

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]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.