頑張らないために頑張る

ゆるく頑張ります

2つのリストと比較したとき一致しない要素をリスト内包表記で取り出す

Posted at — Feb 25, 2023

概要

小ネタです。

あるリストの要素について、別のリストと比較したときにマッチしない要素を取り出すコードを考えます。ここでは、1つだけでなく2つ以上の要素を1度に削除することを考慮します。

>>> hoge = [1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
>>> fuga = [4, 'c', 6, 7]
>>> piyo = [1, 4, 7, 'e']

ここではリストhogeが持つ要素のうち、リストfugapiyoが持つ要素と一致しない要素を取り出します。

[1, 'a', 'b', 'd', 'e']

たとえばリストhogefugaを比較したとき、上記の結果が返ってくればOKです。これをどう取得しましょうか。

コード

>>> hoge.remove(fuga)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list

単純に考えると、リストfugaが持つ要素をhogeから「削除」してやればいいわけです。そして、リストから要素を削除するにはremove()メソッドを使います。

ただ、remove()は引数に指定した「値」が存在したときに該当の要素を削除します。上記のように、remove()の引数にリストを指定すると、「リストである1要素」を削除しようとします。つまり、リストfuga同じ要素を持つリストが1要素になっている箇所を探そうとするわけです。

>>> foo
[[1, 2, 3], [4, 'A']]
>>> foo.remove([4, 'A'])
>>> foo
[[1, 2, 3]]

たとえば、上記のように要素がリストであるならば、削除したい要素としてリストを指定できます。ただ、リストhogeにはリストである要素は存在しません。そのため、ValueError: list.remove(x): x not in listと言われます。あくまでも引数には「削除したい要素」を指定するので、どんな形式や型であれ、もともとのリストに存在そなければこのエラーが返ってきます。

というわけで、今回は単純に引数に比較したいリストを渡すわけには行かなさそうです。

>>> hoge.remove(fuga[0])
>>> 

となると、上記のようにリストfugaの要素を1つずつ指定してremove()すればいいことになります。そうなると繰り返し文の出番です。

>>> hoge
[1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
>>> fuga
[4, 'c', 6, 7]
>>> for item in fuga:
...   hoge.remove(item)
...   
>>> print(hoge)
[1, 'a', 'b', 'd', 'e']

そのため、上記のようにfor文を使ってfugaの要素を1つずつ指定しつつremove()すれば、効率はともかくリストfugaには存在しなかったリストhogeの要素のみを抽出できます。

ただ、リストhoge本来の値を残しておきたい場合はこの処理がきません。なぜなら、remove()は要素を直接削除してリストの内容を変えてしまうしまうためです。

そこでリスト内包表記を使います。

>>> hoge
[1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
>>> fuga
[4, 'c', 6, 7]
>>> piyo
[1, 4, 7, 'e']
>>> [e for e in hoge if e not in fuga]
[1, 'a', 'b', 'd', 'e']
>>> [e for e in hoge if e not in piyo]
['a', 'b', 'c', 6, 'd']
>>> hoge
[1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']

forを使って要素を1つずつ削除しなくとも、リスト内包表記はifをつけて「比較したいリストに存在しないこと」という条件を設定できます。ここではnotを付与して、「~ではない」という条件に合致する要素で新しいリストを作成しています。そう、出力されるのは新しいリストです。つまり、既存のリストには変更を加えません。実際、リスト内包表記による要素の抽出を実行した前後、リストhogeの内容は変化していません。

さらには、上記のようにリスト内包表記を使って記述することで可読性を向上できます。1行で済むコンパクトさもありつつ、出力がリストであり要素の出力元から出力条件までがちゃんと収まっているわけです。

>>> hoge
[1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
>>> fuga
[4, 'c', 6, 7]
>>> piyo
[1, 4, 7, 'e']
>>> [e for e in hoge if e not in (fuga + piyo)]
['a', 'b', 'd']

さらには、上記のように複数のリストを指定すれば複数のリストと比較が可能なので、どのリストとも一致しない要素を抽出したりもできます。

>>> import pandas as pd
>>> import io
>>> s = '''a,b,c,d,e,f,g
... 1,2,3,4,5,6,7
... 8,9,10,11,12,13,14
... 15,16,17,18,19,20,21
... '''
>>> df = pd.read_csv(io.StringIO(s))
>>> df
    a   b   c   d   e   f   g
0   1   2   3   4   5   6   7
1   8   9  10  11  12  13  14
2  15  16  17  18  19  20  21
>>> df.loc[:, [col for col in df.columns if col not in ['a', 'c', 'f']]]
    b   d   e   g
0   2   4   5   7
1   9  11  12  14
2  16  18  19  21 

余談ですが、上記の考え方はpandasのDataFrameにて、列を抽出する場合にも利用できます。というか、リストの形式なので同様に処理できるわけですね。

参考

  1. リストの内包表記
comments powered by Disqus