この記事の内容
- 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で確かめてみてください。
さあ、一緒にリストの平坦化を探求する旅に出掛けましょう。
Contents
2次元リストを平坦化する方法
この章では、Pythonでお手軽に二次元リストを平坦化する方法を3つご紹介します。
各手法の挙動やデメリットもまとめたので、手段を選ぶ際に参考になるはずです。
簡単にリストを平坦化する手段
なお、この章での平坦化処理対象は次の二次元リストとします。
sequence = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
sum(iterable, start=0)
sum関数で2次元リストを平坦化することが可能です。
すずか
先生
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関数はリストやタプル内の要素の合計を求めます。
ここまでは大丈夫だと思います。
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
先生
以上を踏まえて、平坦化の話に戻りましょう。
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]
先生
したがって、結果的に二次元リストの平坦化がなされるのです。
# 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)
すずか
先生
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]
リストの平坦化ができました。
すずか
先生
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]
すずか
先生
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関数と大体同じです。
簡単なリストに対して使いましょう。
リスト内包表記
リスト内包表記を用いた平坦化を解説します。
すずか
先生
例えば次のコードは、リスト内包表記でリストを作り、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]
すずか
先生
リスト内包表記でやりたいことは、ただリストを作ることなのです。
なぜなら、そもそも[ ]で囲んでいますから。
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関数は、オブジェクトが指定した型のインスタンスであるかを確認します。
引数は次の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
先生
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でリストを平坦化する方法をご紹介しました。
二次元リストまでであれば、次の手法で簡単に平坦化が可能でしたね。
簡単にリストを平坦化する手段
しかし、複雑な構造のリストに対しては自作の関数で対処する必要がありました。
本記事で実装した平坦化関数が、皆様のお役に立てば幸いです。
最後までお付き合いいただき、ありがとうございました。