Pythonでリスト(配列)から重複した要素を削除・抽出

Pythonで、リスト(配列)から、

重複した要素を削除(一意な要素・ユニークな要素のみを抽出) 重複した要素を抽出

して、新たなリストを生成する方法について説明します。 それぞれ、

元のリストの順序を保持しない方法 元のリストの順序を保持する方法

および、

二次元配列(リストのリスト)の場合

についてサンプルコードとともに説明します。 なお、リストではなくタプルの場合も同様の考え方で実現できます。 リストやタプルが重複した要素を持っているかどうかを判定したい場合は

また、一つのリストではなく複数のリスト間で共通する要素や共通しない要素を抽出したい場合は

なお、リストは異なる型のデータを格納可能で、厳密には配列とは異なります。メモリサイズやメモリアドレスを必要とするような処理や大規模なデータの数値計算処理などで配列を扱いたい場合はarray(標準ライブラリ)やNumPyを使います。

重複した要素を削除し、新たなリストを生成

元のリストの順序を保持しない: set() 元のリストの順序を保持する必要がない場合は、集合型setのコンストラクタset()を使います。 set型は重複した要素をもたないデータ型で、コンストラクタset()にリストを渡すと、重複する値は無視されて一意な値のみが要素となるset型のオブジェクトを返します。 これをlist()で再びリストにすれば問題ありません。タプルの場合はtuple()でタプルにすればOKです。

l = [3, 3, 2, 1, 5, 1, 4, 2, 3]

l_unique = list(set(l))

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

集合型setについては

元のリストの順序を保持する: dict.fromkeys(), sorted()

元のリストの順番が重要な場合は、辞書型dictのクラスメソッドfromkeys()、または組み込み関数sorted()を使います。 dict.fromkey()は引数に指定したシーケンス型(リストやタプルなど)をキーとした新たな辞書オブジェクトを生成します。第二引数を省略した場合、値はNone。 辞書のキーは重複した要素をもたないので、set()と同じく重複する値は無視されます。dict.fromkey()では引数のシーケンスの順序が保持される(Python3.6以降)。 さらに辞書オブジェクトをlist()の引数に渡すと辞書のキーを要素とするリストが得られるので、これを利用します。

print(dict.fromkeys(l))
# {3: None, 2: None, 1: None, 5: None, 4: None}

l_unique_order = list(dict.fromkeys(l))

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

なお、dict.fromkey()で引数のシーケンスの順序が保持されるのは、Pythonのバージョンが3.6から。 それより前のバージョンでは組み込み関数sorted()を使います。 要素をソートしたリストを返すsortedの引数keyにリスト・タプルのメソッドindex()を指定します。 index()は値のインデックス(リスト中の何番目の要素か)を返すメソッドで、sorted()のkeyに指定することで、元のリストの順番を基準に並べ替えられます。 引数keyには呼び出し可能(コーラブル)なオブジェクトとして指定するので()は書かない。

l_unique_order = sorted(set(l), key=l.index)

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

二次元配列(リストのリスト)の場合

二次元配列(リストのリスト)の場合、set()やdict.fromkey()を使う方法はエラーTypeErrorになります。

l_2d = [[0], [2], [2], [1], [0], [0]]

print(l_2d)
# [[0], [2], [2], [1], [0], [0]]

# l_2d_unique = list(set(l_2d))
# TypeError: unhashable type: 'list'

# l_2d_unique_order = dict.fromkeys(l_2d)
# TypeError: unhashable type: 'list'

これは、リストなどのミュータブル(更新可能)なオブジェクトはset型の要素やdict型のキーにできないから。 以下のような関数を定義します。 元のリストの順番は保持され、一次元のリストやタプルに対しても動作します。

def get_unique_list(seq):
    seen = []
    return [x for x in seq if x not in seen and not seen.append(x)]

l_2d_unique = get_unique_list(l_2d)

print(l_2d_unique)
# [[0], [2], [1]]

l_unique = get_unique_list(l)

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

リスト内包表記を使っています。

ここでは、

and演算子のショートサーキット(短絡評価)でX and YのXがFalseであればYは評価されない(実行されない)こと append()メソッドがNoneを返すこと

を利用しています。 元のリストseqの要素がseenに存在しない場合(x not in seenがTrue)、seenにその要素を追加(seen.append(x))しnot seen.append(x)もTrueとなるため、リスト内包表記の条件式がTrueとなり、最終的に生成されるリストの要素として追加されます。 重複した要素を抽出し、新たなリストを生成

元のリストの順序を保持しない

元のリストから重複している要素のみを抽出する場合は、リスト、タプルのメソッドcount()を使います。 count()はリスト・タプルに引数に指定した値の要素が何個あるかを返すメソッド。 リスト内包表記で、set()で得られる一意な要素の中から元のリストに2個以上存在する(=重複している)要素を抽出します。

l = [3, 3, 2, 1, 5, 1, 4, 2, 3]

l_duplicate = [x for x in set(l) if l.count(x) > 1]

print(l_duplicate)
# [1, 2, 3]

リストの要素数のカウントについては

元のリストの順序を保持する

重複した要素を削除する場合と同様に、dict.fromkey()を使うか(Python3.6以降)、sorted() でソートします。

l_duplicate_order = [x for x in dict.fromkeys(l) if l.count(x) > 1]

print(l_duplicate_order)
# [3, 2, 1]

l_duplicate_order = sorted([x for x in set(l) if l.count(x) > 1], key=l.index)

print(l_duplicate_order)
# [3, 2, 1]

二次元配列(リストのリスト)の場合

元のリストの順番を保持しない場合と保持する場合で、それぞれ以下のような関数が考えられます。一次元のリストやタプルに対しても動作します。

l_2d = [[0], [2], [2], [1], [0], [0]]

print(l_2d)
# [[0], [2], [2], [1], [0], [0]]

def get_duplicate_list(seq):
    seen = []
    return [x for x in seq if not seen.append(x) and seen.count(x) == 2]

def get_duplicate_list_order(seq):
    seen = []
    return [x for x in seq if seq.count(x) > 1 and not seen.append(x) and seen.count(x) == 1]

l_2d_duplicate = get_duplicate_list(l_2d)

print(l_2d_duplicate)
# [[2], [0]]

l_2d_duplicate_order = get_duplicate_list_order(l_2d)

print(l_2d_duplicate_order)
# [[0], [2]]

l_duplicate = get_duplicate_list(l)

print(l_duplicate)
# [3, 1, 2]

l_duplicate_order = get_duplicate_list_order(l)

print(l_duplicate_order)
# [3, 2, 1]

もっとスマートな方法があるかも知れない。

Last Updated: 6/26/2019, 10:34:03 PM