【Python】リストの平坦化(flatten)処理を徹底解説!

この記事の内容

  • Pythonで二次元リストを平坦化!
  • どんなリストでも平坦化する方法!

 

こんにちは、Youta(@youta_blog)です。

 

今回は、Pythonでのリストの平坦化についてお話します。

 

すずか

平坦化って何?
簡単に言えば、リストの中身をバラバラにすることです。

先生

 

次のコードをご覧ください。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = flatten(sequence)  # 平坦化処理

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

二次元リストだったsequenceが一次元リストのresultに生まれ変わりましたね。

これこそリストの平坦化処理です。

 

本記事の構成は次の通りです。

初めに、Pythonでお手軽にリストを平坦化する有名な手法を3選ご紹介します。

最後に、任意のリストに対応した平坦化関数を作ります。

 

最後まで読んでいただくと、次のような複雑な構造のリストの平坦化方法を学べます。

sequence = [(1, [2, 3]), [[[4, 5]]], [[range(6, 9), {'a': 8, 'b': 1}], {(b'\x50\x79\x74\x68\x6f\x6e',), 'flatten'}]]
result = flatten(sequence)

print(result)
# (1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', b'Python', 'flatten')

是非とも、一緒にこれを実現しましょう。

 

本記事では、解説・確認用コード・実行結果の3点を各項目ごとにご用意しております。

また必要に応じてコードの解説をしていますので、初心者でも安心です。

 

記事の信頼性は、確認用コードと実行結果が保証します。

少しでも疑問に感じたら、是非ともコードをコピペしてPCで確かめてみてください。

 

さあ、一緒にリストの平坦化を探求する旅に出掛けましょう。

2次元リストを平坦化する方法

この章では、Pythonでお手軽に二次元リストを平坦化する方法を3つご紹介します。

各手法の挙動やデメリットもまとめたので、手段を選ぶ際に参考になるはずです。

 

簡単にリストを平坦化する手段

 

なお、この章での平坦化処理対象は次の二次元リストとします。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

sum(iterable, start=0)

sum関数で2次元リストを平坦化することが可能です。

 

すずか

あれ、sumって合計の関数だよね?
そうです、リストの要素を合計するときに使われる関数ですよ。

先生

 

Pythonのsum関数といえば、総和を求めるときに使いますね。

sequence = [1, 2, 3]
result = sum(sequence)

print(result)
# 6

 

しかし、引数に次の2つを指定することで、リストの平坦化が可能なのです。

  • iterable: リストやタプル等(イテラブル)
  • start: 空のリスト

 

イテラブルとは?

簡単に言うと、for文で繰り返すことができるオブジェクトです。

「for i in ○○:」で「○○」に入れることができるものです。

例えば、リスト・タプル・辞書・文字列・集合などがあります。

 

まずは動きをご覧ください。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = sum(sequence, [])

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

すずか

え sumって合計出すんじゃないの?

てか[ ]って何?

きちんと合計を求めていますよ。

[ ]は加算の初期値です。

先生

 

前提として、sum関数はリストやタプル内の要素の合計を求めます。

ここまでは大丈夫だと思います。

sequence = [1, 2, 3]
result = sum(sequence)

print(result)
# 6

 

実は、sum関数は第二引数に加算の初期値を指定することができるのです。

sequence = [1, 2, 3]
result = sum(sequence, 4)

print(result)
# 10

 

内部的には以下の処理が行われています。

# result = 初期値 + sequenceの0番 + sequenceの1番 + sequenceの2番

result = 4 + sequence[0] + sequence[1] + sequence[2]
result = 4 + 1 + 2 + 3

 

初期値を省略することは、初期値を0とすることに等しいです。

先生

 

以上を踏まえて、平坦化の話に戻りましょう。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = sum(sequence, [])

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

sum関数の内部で以下の処理が行われています。

# result = 初期値 + sequenceの0番 + sequenceの1番 + sequenceの2番

result = [] + sequence[0] + sequence[1] + sequence[2]
result = [] + [1, 2, 3] + [4, 5, 6] + [7, 8, 9]

 

すずか

え リストって足し算できるの?
もちろんできますよ。

先生

a = [1, 2, 3]
b = [4, 5, 6]
c = []

d = a + b
e = a + c

print(d)
# [1, 2, 3, 4, 5, 6]

print(e)
# [1, 2, 3]
空リスト[ ]は、0のような扱いです。

先生

 

したがって、結果的に二次元リストの平坦化がなされるのです。

# result = 初期値 + sequenceの0番 + sequenceの1番 + sequenceの2番

result = [] + sequence[0] + sequence[1] + sequence[2]
result = [] + [1, 2, 3] + [4, 5, 6] + [7, 8, 9]
result = [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

リストではなくタプルでも効力を発揮します。

sequence = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
result = sum(sequence, ())

print(result)
# (1, 2, 3, 4, 5, 6, 7, 8, 9)

 

型同士の加算が定義されており、かつsumの第一引数の要素と第二引数の型を一致させるのがポイントです。

# 1 リストを要素に持つリスト
sequence1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result1 = sum(sequence1, [])

print(result1)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 2 タプルを要素に持つタプル
sequence2 = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
result2 = sum(sequence2, ())

print(result2)
# (1, 2, 3, 4, 5, 6, 7, 8, 9)


# 3 リストを要素に持つタプル
sequence3 = ([1, 2, 3], [4, 5, 6], [7, 8, 9])
result3 = sum(sequence3, [])

print(result3)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 4 タプルを要素に持つリスト
sequence4 = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
result4 = sum(sequence4, ())

print(result4)
# (1, 2, 3, 4, 5, 6, 7, 8, 9)

 

すずか

万能だね。もう全部sumでいいよ。
最後にデメリットを確認します。

先生

 

 

1つずつ見ていきましょう。


まず、第二引数を忘れるとエラーが出ます。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = sum(sequence)

# print(result)
# TypeError: unsupported operand type(s) for +: 'int' and 'list'

忘れないようにしましょう。

 

sumの第二引数を省略すると、加算の初期値は0になるんでしたよね。

つまり、関数内部では次のような処理になります。

# result = 初期値 + sequenceの0番 + sequenceの1番 + sequenceの2番

result = 0 + sequence[0] + sequence[1] + sequence[2]
result = 0 + [1, 2, 3] + [4, 5, 6] + [7, 8, 9]

 

しかし、int型の0とlist型の[1, 2, 3]の加算は定義されていません。

「’int’と’list’での+演算は対応していません!」とエラー文にも書いてありますね。

# TypeError: unsupported operand type(s) for +: 'int' and 'list'

次に、平坦化が失敗するケースについてです。

 

例えば、不規則なリストの場合。

sequence = [[1, 2, 3], 4, [5, 6], [7, 8, 9]]
result = sum(sequence, [])

# print(result)
# TypeError: can only concatenate list (not "int") to list

エラーが出てしまいますね。

 

sum関数内部では次のような処理となります。

# result = 初期値 + sequenceの0番 + sequenceの1番 + sequenceの2番 + sequenceの3番

result = [] + sequence[0] + sequence[1] + sequence[2] + sequence[3]
result = [] + [1, 2, 3] + 4 + [5, 6] + [7, 8, 9]

 

しかし既に述べた通り、int型とlist型を加算で結合することはできません。

「listはlist(‘int’ではなく)としか結合できないよ!」とエラー文にも書いてあります。

# TypeError: can only concatenate list (not "int") to list

 

また、ネスト(入れ子)が深いリストの場合。

sequence = [[[1, 2, 3]], [[4, 5, 6]], [[7, 8, 9]]]
result = sum(sequence, [])

print(result)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

一発で平坦化できません。

 

ましてやこんなリストなんて論外ですよね。

sequence = [(1, [2, 3]), [[[4, 5]]], [[range(6, 9), {'a': 8, 'b': 1}], {(b'\x50\x79\x74\x68\x6f\x6e',), 'flatten'}]]

 

sum関数でリストを平坦化する際は、簡単な構造のリストを対象とするのがベストです。

itertools.chain.from_iterable(iterable)

itertools.chain.from_iterable関数で2次元リストを平坦化することが可能です。

引数には平坦化するリストを渡します。

 

この関数を利用するには、Python標準ライブラリであるitertoolsをインポートします。

import itertools

 

では実際に確かめてみましょう。


import itertools


sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = itertools.chain.from_iterable(sequence)

print(list(result))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

リストの平坦化ができました。

 

すずか

printの中のlist(result)って何?
resultの型をlist型に変換しているのです。

先生

 

resultの正体を確認してみましょう。

print(result)
# <itertools.chain object at 0x107b88ca0>

print(type(result))
# <class 'itertools.chain'> 

resultは、itertools.chainクラスに属します。

平坦化済みの要素の情報が入っていますが、出力できるようlistに型変換しました。

 

タプルや集合に型変換しても問題ありません。

print(tuple(result))
# (1, 2, 3, 4, 5, 6, 7, 8, 9)

print(set(result))
# {1, 2, 3, 4, 5, 6, 7, 8, 9}

 

タプルとリストの組み合わせでもOKです。

# 1 リストを要素に持つリスト
sequence1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result1 = itertools.chain.from_iterable(sequence1)

print(list(result1))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 2 タプルを要素に持つタプル
sequence2 = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
result2 = itertools.chain.from_iterable(sequence2)

print(list(result2))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 3 リストを要素に持つタプル
sequence3 = ([1, 2, 3], [4, 5, 6], [7, 8, 9])
result3 = itertools.chain.from_iterable(sequence3)

print(list(result3))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 4 タプルを要素に持つリスト
sequence4 = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
result4 = itertools.chain.from_iterable(sequence4)

print(list(result4))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

すずか

平坦化と言えば、もうitertools.chain.from_iterableで決まりだね!
最後にデメリットを確認してから決めましょうね。

先生

 

 

1つずつ見ていきましょう。


itertools.chain.from_iterable関数は、itertoolsをインポートしなければ使えません。

平坦化するためだけに毎回import文を書く必要があるので、少々煩わしいです。

 

当然インポートしないとエラーが出ます。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = itertools.chain.from_iterable(sequence)

# print(list(result))
# NameError: name 'itertools' is not defined

 

「itertoolsが定義されていない!」とのこと。

# NameError: name 'itertools' is not defined

次に、平坦化された中身を見るには型変換をしないといけません。

itertools.chain.from_iterableはイテレータを返すからです。

 

うっかり型変換を忘れると、次のように出力されます。


import itertools


sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = itertools.chain.from_iterable(sequence)

print(result)
# <itertools.chain object at 0x107b88ca0>

 

動作確認やresultの要素にアクセスしたいとき、型変換を挟むので地味にストレスです。

 

itertools.chain.from_iterable関数をラップする関数を作れば、対症療法にはなります。


import itertools


def wrap_flatten(sequence):
    result = itertools.chain.from_iterable(sequence)

    return list(result)


sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = wrap_flatten(sequence)

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

ただ、それでも嫌ですよね…。

余分な関数が1つ増えるわけですし。


そしてsum関数と同様、平坦化できるリストには限りがあります。

例えば、要素の中にイテラブル(リスト・タプル等)以外が混じっている場合。

sequence = [[1, 2, 3], 4, [5, 6], [7, 8, 9]]
result = itertools.chain.from_iterable(sequence)

# print(list(result))
# TypeError: 'int' object is not iterable

エラーが出てしまいますね。

resultをlistに型変換するのに失敗します。

 

sequence内の整数4(int型)が原因です。

「’int’型はイテラブルじゃない!」と。

# TypeError: 'int' object is not iterable

 

また、ネスト(入れ子)が深いリストの場合。

sequence = [[[1, 2, 3]], [[4, 5, 6]], [[7, 8, 9]]]
result = itertools.chain.from_iterable(sequence, [])

print(list(result))
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

一発で平坦化できません。

 

itertools.chain.from_iterable関数も、できることはsum関数と大体同じです。

簡単なリストに対して使いましょう。

リスト内包表記

リスト内包表記を用いた平坦化を解説します。

 

すずか

リスト内包表記って何?
リストの中でif文・for文を使って、新しいリストを作ることです。

先生

 

例えば次のコードは、リスト内包表記でリストを作り、sequenceに代入する処理です。

sequence = [i for i in range(5)]

print(sequence)
# [0, 1, 2, 3, 4]

 

このコードと処理内容は等価です。

sequence = []

for i in range(5):
    sequence.append(i)

print(sequence)
# [0, 1, 2, 3, 4]

 

すずか

意味わからん。
1つずつ丁寧に解説していきまね。

先生

 

リスト内包表記でやりたいことは、ただリストを作ることなのです。

なぜなら、そもそも[ ]で囲んでいますから。

sequence = [                   ]

怖れる必要はありません。

 

次に、リストに追加したい要素を書きます。

sequence = [i                  ]

iを追加したいそうです。

でも、どんなiなのかわかりませんよね。

 

そこで、後ろから「iはこれだよ!」と指定してあげるのです。

sequence = [  for i in range(5)]

「for i in range(5)」とありますから、iは「0, 1, 2, 3, 4」と順に変化します。

 

まとめます。

リストにiを入れてね〜。

ただしiは0~4まで1ずつ動くけどね。

 

つまり「0, 1, 2, 3, 4」の各要素をリストに入れろという命令です。

sequence = [i for i in range(5)]

print(sequence)
# [0, 1, 2, 3, 4]

 

おわかりいただけたでしょうか。

先生

すずか

まぁ、なんとなく。

 

では、if文を交えたらどうなるでしょうか。

sequence = [i for i in range(5) if i % 2 == 0]

print(sequence)
# [0, 2, 4]

 

目的は、とある要素のリストを作ることです。

sequence = [                                 ]

 

リストにはiを入れたいようです。

sequence = [i                                ]

 

どんなiですか?

sequence = [  for i in range(5)              ]

「0, 1, 2, 3, 4」と順に変化するiです。

 

けれども、iに制限があります。

sequence = [                    if i % 2 == 0]

それは「2で割った余りが0」のiです。

つまり偶数ですね。

 

まとめます。

リストにiを入れてね〜。

え、iが何かって?

そうだな〜、iは0~4まで1ずつ変化する数だよ〜。

あ、言い忘れてた、iは偶数ね!

 

イラっとしますが堪えます。

これで0~4までの偶数をリストに入れろという命令文なのです。

sequence = [i for i in range(5) if i % 2 == 0]

print(sequence)
# [0, 2, 4]

 

次のコードと等価です。

sequence = []

for i in range(5):
    if i % 2 == 0:
        sequence.append(i)

print(sequence)
# [0, 2, 4]

 

こちらは、2次元リストをリスト内包表記によって平坦化する例です。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = [item for items in sequence for item in items]

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

リスト内包表記では二重ループも使えます。

その際は前のfor文ほど、より外側のループとなることにご注意ください。

 

それでは、resultの中身に注目して解説します。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = [item for items in sequence for item in items]

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

まず、そもそも作りたいのはリストですよね。

平坦化した要素を入れるために。

result = [                                            ]

 

リストにはitemを入れたいです。

result = [item                                        ]

 

どんなitemでしょうか?

result = [     for items in sequence                  ]

sequence内の要素itemsが出てきました。

sequence内の要素なので、itemsは[1, 2, 3]・[4, 5, 6]・[7, 8, 9]と順に切り替わります。

 

注意したいのはitemsなので、まだitemの情報は出てきません。

いったいitemとは何者でしょうかね。

 

ようやくitemが出てきました。

result = [                           for item in items]

どうやらitemは、items内の要素らしいです。

既に述べましたが、itemsはsequence内の各要素なので、[1, 2, 3]・[4, 5, 6]・[7, 8, 9]と順に3回切り替わります。

 

itemsが[1, 2, 3]の時、itemはitemsの要素となるので「1, 2, 3」と順に3回切り替わります。

itemsが[4, 5, 6]の時、itemはitemsの要素となるので「4, 5, 6」と順に3回切り替わります。

itemsが[7, 8, 9]の時、itemはitemsの要素となるので「7, 8, 9」と順に3回切り替わります。

 

つまりitemは「1, 2, 3, 4, 5, 6, 7, 8, 9」と移り変わりますね。

さて、そういえばitemはなんだったでしょうか。

 

そう、リストに入れたいものでしたね。

result = [item                                        ]

 

まとめます。

リストにitemを入れてね〜。

え、itemが何かって?

そうだな〜、まずsequenceの各要素をitemsってするじゃん?

そのitemsの中身だよ!

 

周りくどかったですよね。

少しでもわかっていただけたら幸いです。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = [item for items in sequence for item in items]

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

ちなみに以下の処理と同じです。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = []

for items in sequence:
    for item in items:
        result.append(item)

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

すずか

むっず!
少しずつ慣れていきましょうね。

先生

 

タプルとリストの組み合わせでも構いません。

# 1 リストを要素に持つリスト
sequence1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result1 = [item for items in sequence1 for item in items]

print(result1)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 2 タプルを要素に持つタプル
sequence2 = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
result2 = [item for items in sequence2 for item in items]

print(result2)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 3 リストを要素に持つタプル
sequence3 = ([1, 2, 3], [4, 5, 6], [7, 8, 9])
result3 = [item for items in sequence3 for item in items]

print(result3)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 4 タプルを要素に持つリスト
sequence4 = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
result4 = [item for items in sequence4 for item in items]

print(result4)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

最後に、リスト内包表記で平坦化処理をする際のデメリットを挙げます。

 

1つずつ見ていきましょう。


まず、そもそも読みづらいですよね。

二次元のリストを平坦化するには二重ループが必要です。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = [item for items in sequence for item in items]

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

個人的には、これでも読みづらいと感じます。

 

では、三次元だとどうなるでしょうか。

sequence = [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]]
result = [k for i in sequence for j in i for k in j]

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

リスト内部にfor文が3回使われています。

もう見たくもありませんよね。


そして、やはり平坦化できるリストが限られています。

 

例えば、二重ループのリスト内包表記では、三次元リストを完全に平坦化できません。

sequence = [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]]
result = [item for items in sequence for item in items]

print(result)
# [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]

 

また、不規則なリストではエラーが出ます。

sequence = [[1, 2, 3], 4, [5, 6], [7, 8, 9]]
result = [item for items in sequence for item in items]

# print(result)
# TypeError: 'int' object is not iterable

 

sequence内の整数4(int型)が原因です。

4はイテラブルではないため、itemsが4になったときに後ろのfor文で回せないからです。

# TypeError: 'int' object is not iterable

 

if文で例外を取り除くこともできますが、ただでさえ複雑なリスト内包表記にわざわざif文を付けたいでしょうか?

sequence = [[1, 2, 3], 4, [5, 6], [7, 8, 9]]
result = [item for items in sequence for item in (items if hasattr(items, '__iter__') else (items,))]

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

個人的には、これ以上読み手を混乱させるようなコードを書くべきではないと考えます。

 

リスト内包表記でも手軽に平坦化できますが、可読性とトレードオフの関係です。

どんなリストも平坦化する関数

この章では、どんなリストでも一発で平坦化可能な関数を作ります。

 

どうせなら、前章での不都合をすべて解消した関数が良いですよね。

既出のデメリット
  • 第二引数が必要
  • itertoolsをインポートする必要がある。
  • 中身を見るには型変換する必要がある。
  • 読みづらい。
  • 平坦化できるケースに限りがある。

実装例

先に結論をお見せます。

以下の関数は、あらゆるリストで平坦化処理が可能な関数です。

def flatten(sequence):
    result = []

    for item in sequence:
        if isinstance(item, (list, tuple, range, dict, set, frozenset)):
            result.extend(flatten(item))
        else:
            result.append(item)

    return result

上記のコードでは、イテラブルの平坦化処理を行う関数flattenを定義しています。

 

引数
  • イテラブルオブジェクト: リストやタプル等。
戻り値
  • リスト: 平坦化処理されたsequenceの要素

 

リストを平坦化するflatten関数を解説します。

def flatten(sequence):
    result = []

    for item in sequence:
        if isinstance(item, (list, tuple, range, dict, set, frozenset)):
            result.extend(flatten(item))
        else:
            result.append(item)

    return result

 

まずはリストを用意します。

    result = []

後ほど、平坦化してバラバラにしたsequenceの要素を入れるためです。

 

ここから平坦化処理を開始します。

    for item in sequence:

sequenceの0番目の要素、1番目の要素…と順にアクセスします。

 

まずは、sequenceの各要素の型をチェック。

        if isinstance(item, (list, tuple, range, dict, set, frozenset)):

itemの型が次のどれかに該当したら、if文に入ります。

  • list
  • tuple
  • range
  • dict
  • set
  • frozenset

つまり、if文に入ったらitemはイテラブル(リストやタプル等)で確定です。

 

isinstance関数の使い方は大丈夫でしょうか。

先生

すずか

不安かもー。

 

isinstance関数は、オブジェクトが指定した型のインスタンスであるかを確認します。

 

引数は次の2つです。

  • object: 型を確認するオブジェクト
  • classinfo: 型

 

例えば、aは’Python'(文字列)なのでstr型です。

a = 'Python'
result = isinstance(a, str)

print(result)
# True

 

また、文字列であるためint型ではありません。

a = 'Python'
result = isinstance(a, int)

print(result)
# False

 

第二引数に、型を要素とするタプルを指定するのも可能です。

オブジェクトがそのタプル内の型にどれか1つでも該当すれば、Trueを返します。

a = 'Python'
result = isinstance(a, (str, int, bytes, range))

print(result)
# True

 

以上、isinstance関数の説明でした。

先生

 

itemがイテラブル(リストやタプル等)ということは、まだバラせる余地がありますね。

ということで、再度このflatten関数にitemを入れてやります。

そう、flatten関数は再帰関数です。

            result.extend(flatten(item))

flatten関数はイテラブルのネストを平坦化するので、戻り値は一次元リストです。

それをresultに追加していきます。

 

今度は、itemがif文に入らなかった場合。

このとき、itemのネストは全て展開されています。

            result.append(item)

resultにitemを追加してあげましょう。

 

for文を抜けると同時にsequenceの平坦化が完了。

この時点で、result内部には平坦化された要素がズラーっと一列に並んでいます。

resultを関数の外に渡して終了です。

    return result

 

いかがでしょうか。

先ほどのデメリットを少し解決できましたね。

既出のデメリット
  • 第二引数が必要
  • itertoolsをインポートする必要がある。
  • 中身を見るには型変換する必要がある。
  • 読みづらい。
  • 平坦化できるケースに限りがある。

 

残りの点は、次のテストで確認しましょう!

テスト

二次元リストの平坦化

それでは、前章で登場した次の二次元リストを平坦化させてみます。

sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = flatten(sequence)

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

瞬殺でした。

 

リストとタプルの合わせ技も試してみます。

# 1 リストを要素に持つリスト
sequence1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result1 = flatten(sequence1)

print(result1)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 2 タプルを要素に持つタプル
sequence2 = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
result2 = flatten(sequence2)

print(result2)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 3 リストを要素に持つタプル
sequence3 = ([1, 2, 3], [4, 5, 6], [7, 8, 9])
result3 = flatten(sequence3)

print(result3)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]


# 4 タプルを要素に持つリスト
sequence4 = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
result4 = flatten(sequence4)

print(result4)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

完璧ですね。

 

型変換の必要はありませんでした。

既出のデメリット
  • 第二引数が必要
  • itertoolsをインポートする必要がある。
  • 中身を見るには型変換する必要がある。
  • 読みづらい。
  • 平坦化できるケースに限りがある。

不規則なリストの平坦化

さて、最後は不規則なリストでも平坦化が可能かを見ていきます。

 

まずは、散々sum関数やitertools.chain.from_iterable関数を撃沈させたこの例から。

sequence = [[1, 2, 3], 4, [5, 6], [7, 8, 9]]
result = flatten(sequence)

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

涼しい顔で平坦化してくれますね。

 

それでは卒業試験です。

冒頭のめちゃくちゃなリストで試しましょう。

sequence = [(1, [2, 3]), [[[4, 5]]], [[range(6, 9), {'a': 8, 'b': 1}], {(b'\x50\x79\x74\x68\x6f\x6e',), 'flatten'}]]
result = flatten(sequence)

 

実行結果です。

print(result)
# [1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', b'Python', 'flatten']

 

ついに完成しました!すべての場合に対応可能な平坦化関数!

先生

すずか

へーすごいね。

 

デメリットはすべて解消できました。

既出のデメリット
  • 第二引数が必要
  • itertoolsをインポートする必要がある。
  • 中身を見るには型変換する必要がある。
  • 読みづらい。
  • 平坦化できるケースに限りがある。

まとめ

今回は、Pythonでリストを平坦化する方法をご紹介しました。

二次元リストまでであれば、次の手法で簡単に平坦化が可能でしたね。

簡単にリストを平坦化する手段

 

しかし、複雑な構造のリストに対しては自作の関数で対処する必要がありました。

本記事で実装した平坦化関数が、皆様のお役に立てば幸いです。

最後までお付き合いいただき、ありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA