リストの不規則なリストをフラット化する


440

はい、私はこの主題が以前にカバーされたことを知っていますが(ここここここここここ)、私が知る限り、1つを除くすべてのソリューションは次のようなリストで失敗します:

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

3
Pythonが本当に必要とするのは、別の組み込みではなく、途切れのない再帰だと私は言うでしょう。
粘土

2
@Mittenchops:まったく同意できない、明らかに悪いAPI /過度に複雑なデータ構造で作業しているという事実(単なるメモ:listsは均質であることを意図している)は、それがPythonの障害であり、そのようなタスクに組み込みが必要であることを意味しない
Azat Ibrakov

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

2.6で追加されたIterable ABCを使用しました。

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-clauseを-loopの外に移動することで修正できますfor el。(それから、あなたはそれに何でも投げることができ、そしてそれはそれから平らなリストを作るでしょう)
RolKau

6
Python 3.7の場合、使用collections.Iterableは非推奨です。collections.abc.Iterable代わりに使用してください。
dawg '30 / 09/30

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 2013

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
:2.xの以前のが必要になるためのPython 3のための素晴らしい作品をありがとう、 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が言及しているように、シーケンスに文字列が含まれていると、アルゴリズム自体が失敗します。
ダニエル 'ダン'グリフィス

28

これは、タプルとリストの両方を処理し、位置引数の任意の組み合わせを投入できる、再帰的なフラット化の私の機能バージョンです。シーケンス全体を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
あなたは何を記述するためにいくつかのコメントを追加した場合優れたソリューションは、しかし、非常に参考になるeanを参照してください
クリストフパル

2
@WolfgangKuehne:お試しargsのためにnintermediate(またはより短い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、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のどちらを使用するかについてコンセンサスはありますか?
2013

setsdictsdequeslistiteratorsgenerators、ファイルハンドル、およびカスタムクラスで__iter__定義されたすべてのインスタンスであるcollections.Iterable、ではありませんcollections.Sequence。aを平坦化した結果はdict少し不明瞭ですが、それ以外の場合collections.Iterableは、のデフォルトよりも良いと思いますcollections.Sequence。それは間違いなくよりリベラルです。
unutbu 2013

@wim:使用に関する1つの問題collections.Iterableは、これに無限ジェネレーターが含まれることです。このケースの処理方法を変更しました。
unutbu 2013

1
これは3番目と4番目の例では機能しないようです。投げStopIterationます。また、while True: first = next(remainder) に置き換えることもできますfor first in remainder:
ジョージー

@Georgyこれは、flattenの本体をでカプセル化することで修正できます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 for x in c]に変更するだけで、サンプルで問題なく機能するはずです。
クレイ

まず、の[x for x in c]コピーを作成するのは遅くて冗長な方法cですが、なぜそれを行うのですか?次に、コードは明らかにに変換'APPLE ]['され'APPLE 'ます。引用符は処理されないため、角かっこがリスト角かっこであると見なされるだけです。
abarnert 2014

ハ!あなたのコメントが私のコンピューターでフォーマットされた方法、私はそれが古いコンピューターで現れたので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拡張として記述されているため、純粋な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

それはあなたが(のように放置される可能性のあるデータ型平らになりbytearraybytesそして、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

エレガントで非常にパイソンのような答えが選択されていますが、レビューのためだけに私のソリューションを提示します:

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意味を少し詳しく説明してください。
Xolve 2014

1
リストを渡す場合は、おそらくリストを戻す必要があります。タプルを渡す場合は、タプルを戻す必要があります。2つのミッシュマッシュを渡すと、外側を囲んでいるものは何でも取得できます。
ダッシュトムバン

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ループの2つのリストで機能します。

内部のforループはリストを反復処理します。リスト要素が見つかると、(1)list.extend()を使用して、その部分を1レベルのネスト化し、(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 '13年

@ telliott99:リストが本当に大きいか、ネストされたリストが非常に深い場合は、そのとおりです。ただし、そうでない場合は、より簡単なソリューションも同様に機能し、他のいくつかの回答の深い魔法はありません。多段階の再帰的なジェネレータの理解のための場所がありますが、私はあなたが最初に見るべき場所であると確信していません。(私が「ワースイズベター」の議論にどこに陥るか知っていると思います。)
クレイ

@ telliott99:別の言い方をすれば、私の解決策を「グロクに試す」必要はありません。パフォーマンスがボトルネックでない場合、プログラマーにとって最も重要なことは何ですか。
クレイ

シンプルなソリューションほどロジックが少なくなります。再帰はかなり基本的なプログラミング構造であり、プログラマーであると自分で考える人は誰でも完全に快適である必要があります。ジェネレーターは非常にPythonの方法であり、(理解とともに)プロの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


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の最初のリストと残りのリストの処理方法を借りて思いついた1つのライナーを示します

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

ワンライナーではありません。どれだけ1つに収めようとしてdef foo():も、別の行になります。また、これは非常に判読できません。
cs95 2018年

コードを1行で修正し、さらにリファクタリングを行いました。(私がこれを書いている間、編集はピアレビュー待ちです)この特定の方法は私には非常に読みやすいように見えましたが、元のコードにはいくつかのリファクタリングが必要でした。
エミリオMブマチャー

3

そのような質問に答えようとするとき、あなたは本当にあなたが解決策として提案するコードの制限を与える必要があります。パフォーマンスのみであれば、あまり気にしませんが、解決策として提案されたコード(承認された回答を含む)のほとんどは、深さが1000を超えるリストを平坦化できません。

私が言うときのコードのほとんどを私は再帰のいずれかの形式を使用するすべてのコードを意味する(または再帰的である標準ライブラリ関数を呼び出します)。行われた再帰呼び出しごとに、(呼び出し)スタックが1単位大きくなり、(デフォルト)Python呼び出しスタックのサイズが1000になるため、これらのコードはすべて失敗します。

コールスタックに精通していない場合は、次の方法が役立つ場合があります(それ以外の場合は、実装までスクロールできます)。

呼び出しスタックのサイズと再帰プログラミング(ダンジョンの類推)

宝物を見つけて終了する

宝物を探して、番号の付いた部屋のある巨大なダンジョンに入ると想像してみてください。あなたは場所を知りませんが、宝物を見つける方法についていくつかの目安があります。それぞれの兆候はなぞなぞです(難易度はさまざまですが、どれほど難しいかは予測できません)。あなたは時間を節約するための戦略について少し考えることに決め、あなたは2つの観察をします:

  1. 宝物を見つけるのは難しい(長い)ので、そこにたどり着くには(潜在的に難しい)なぞなぞを解く必要があります。
  2. 宝物が見つかったら、入口に戻るのは簡単かもしれませんが、反対方向に同じパスを使用するだけです(ただし、パスを呼び出すには少しメモリが必要です)。

ダンジョンに入ると、ここに小さなノートがあります。(新しい部屋に入るとき)なぞなぞを解いた後に出るすべての部屋を書き留めるためにそれを使用することを決定します。これにより、入り口に戻ることができます。それは天才的な考えです、あなたあなたの戦略を実装するのに1セントも費やしません

あなたはダンジョンに入り、最初の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関数を呼び出すことはほとんどありません。関数から戻ると、コールスタックは使用されたアドレスから解放されることを理解することも重要です(そのため、名前「スタック」。戻りアドレスは、関数に入る前にプッシュされ、戻るときに引き出されます)。単純な再帰の特別な場合(関数f自分自身を1回呼び出す-何度も-)f計算が完了するまで(宝物が見つかるまで)何度も繰り返し入力し、最初にf呼び出した場所に戻るまで戻りますf。呼び出しスタックは、すべての戻りアドレスから順番に解放されるまで、何も解放されません。

この問題を回避するには?

それは実際には非常に単純です。「それがどれだけ深くなることができるかわからない場合は、再帰を使用しないでください」。場合によっては、テールコールの再帰を最適化(TCO)できるため、これは常に正しいとは限りません。しかし、Pythonではそうではなく、「よく書かれた」再帰関数でさえスタックの使用を最適化しません。この質問について、Guidoからの興味深い投稿があります。TailRecursion Eliminationです。

再帰的な関数を反復的にするために使用できる手法があります。この手法は、独自のノートブックを持っていくことができます。たとえば、私たちが特定のケースで単にリストを探索している場合、部屋を入力することはサブリストを入力することと同じです。自問する必要があるのは、リストからその親リストに戻る方法です。答えはそれほど複雑ではありません。stack空になるまで以下を繰り返します。

  1. 新しいサブリストを入力するときに、現在のリストaddressをプッシュします(リストのアドレス+インデックスもアドレスであるため、コールスタックで使用されているのとまったく同じ手法を使用します)。indexstack
  2. アイテムが見つかるたびyieldに(またはリストに追加します)、
  3. リストが完全に探索されたら、stack return address(およびindexを使用して親リストに戻ります。

また、これはツリー内の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])が返されます。その場合、各アイテムで2回呼び出されることに注意してください。読者がこれをよりクリーンにすることが課題であるとしましょう。["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
参考:再帰的なソリューションを使用します:ソースへのリンク
ジョージー

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)とO(タイプの数)が必要ですが、もちろん、指定された無視されたタイプの派生タイプの値は失敗しますその使用して、なぜ、これはstrunicodeとても慎重にそれを使用します...

テスト:

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を定義した後、この関数を使用してフラット化を行うのは簡単です。または、それを1つの関数に組み合わせることができます。ネストされたリストに適用できるので、自分のソリューションが気に入っています。

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

これにより、リストまたは辞書(またはリストまたは辞書の辞書など)がフラット化されます。値が文字列であると想定し、各アイテムをセパレーター引数で連結する文字列を作成します。必要な場合は、セパレータを使用して、結果を後でリストオブジェクトに分割できます。次の値がリストまたは文字列の場合、再帰を使用します。key引数を使用して、辞書オブジェクトのキーまたは値(keyをfalseに設定)が必要かどうかを指定します。

def flatten_obj(n_obj, key=True, my_sep=''):
    my_string = ''
    if type(n_obj) == list:
        for val in n_obj:
            my_sep_setter = my_sep if my_string != '' else ''
            if type(val) == list or type(val) == dict:
                my_string += my_sep_setter + flatten_obj(val, key, my_sep)
            else:
                my_string += my_sep_setter + val
    elif type(n_obj) == dict:
        for k, v in n_obj.items():
            my_sep_setter = my_sep if my_string != '' else ''
            d_val = k if key else v
            if type(v) == list or type(v) == dict:
                my_string += my_sep_setter + flatten_obj(v, key, my_sep)
            else:
                my_string += my_sep_setter + d_val
    elif type(n_obj) == str:
        my_sep_setter = my_sep if my_string != '' else ''
        my_string += my_sep_setter + n_obj
        return my_string
    return my_string

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

収量:

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

0

再帰が好きなら、これはあなたにとって興味のある解決策かもしれません:

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

私は実際に、しばらく前に書いたいくつかの練習用のSchemeコードからこれを採用しました。

楽しい!


0

私はPythonが初めてで、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.