Pythonのin演算子でリストなどに特定の要素が含まれるか判定
Pythonの演算子in
およびnot in
を使うと、リストやタプルなどに特定の要素が含まれるか(存在するか)どうかを確認・判定できる。
for文やリスト内包表記の構文においてもin
という語句が使われる。本記事の最後でも触れるが、詳細は以下の記事を参照。
in演算子の使い方
基本的な使い方
以下のようにx in y
の形で記述する。x
がy
に含まれているとTrue
、含まれていないとFalse
を返す。
print(1 in [0, 1, 2])
# True
print(100 in [0, 1, 2])
# False
リストのほか、タプルや集合set
、range
などのイテラブルオブジェクトに対する演算が可能。
print(1 in (0, 1, 2))
# True
print(1 in {0, 1, 2})
# True
print(1 in range(3))
# True
辞書dict
、文字列str
については後述。
値として等しいかどうかで判定
in
による判定は、==
と同様に値として等しいかどうかで判定される。型が違っていても値が等しければTrue
となる。
- 関連記事: Pythonの==演算子とis演算子の違い
print(1.0 == 1)
# True
print(1.0 in [0, 1, 2])
# True
print(True == 1)
# True
print(True in [0, 1, 2])
# True
なお、bool
は整数int
のサブクラスなので、True
, False
はそれぞれ1
, 0
と等価。
if文での条件分岐
in
による演算はbool値(True
, False
)を返し、そのままif文の条件式として使える。
l = [0, 1, 2]
i = 0
if i in l:
print(f'{i} is a member of {l}.')
else:
print(f'{i} is not a member of {l}.')
# 0 is a member of [0, 1, 2].
l = [0, 1, 2]
i = 100
if i in l:
print(f'{i} is a member of {l}.')
else:
print(f'{i} is not a member of {l}.')
# 100 is not a member of [0, 1, 2].
なお、リストやタプル、文字列などは空だとFalse
、空でなければTrue
と判定される。空かどうかで条件分岐したい場合はオブジェクトをそのまま条件式として使えばよい。
l = [0, 1, 2]
if l:
print(f'{l} is not empty.')
else:
print(f'{l} is empty.')
# [0, 1, 2] is not empty.
l = []
if l:
print(f'{l} is not empty.')
else:
print(f'{l} is empty.')
# [] is empty.
各型の真偽の判定については以下の記事も参照。
辞書dictに対するin
辞書dict
をそのままin
演算で使うと、キーに対する判定となる。
d = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
print('key1' in d)
# True
print('value1' in d)
# False
値、あるいは、キーと値の組み合わせに対して処理したい場合はvalues()
, items()
を使う。
print('value1' in d.values())
# True
print(('key1', 'value1') in d.items())
# True
print(('key1', 'value2') in d.items())
# False
詳細は以下の記事を参照。
文字列strに対するin
文字列str
に対しては部分文字列の判定が可能。
print('a' in 'abc')
# True
print('x' in 'abc')
# False
print('ab' in 'abc')
# True
print('ac' in 'abc')
# False
文字列の検索についての詳細は以下の記事を参照。正規表現を使ったより柔軟な判定についても触れている。
not in(否定)で存在しないことを確認
in
演算子の否定はnot in
を使う。
print(10 in [1, 2, 3])
# False
print(10 not in [1, 2, 3])
# True
in
演算全体にnot
を付けても同じ結果。
print(not 10 in [1, 2, 3])
# True
ただし、in
演算全体にnot
を付けると、以下のようにnot
がどの範囲に掛かっているかについて2通りの解釈ができてしまうため、より明確なnot in
を使うことが推奨されている。
print(not (10 in [1, 2, 3]))
# True
print((not 10) in [1, 2, 3])
# False
in
のほうがnot
より優先順位が高い(先に処理される)ため、括弧がない場合は前者として処理される。
ちなみに後者の場合は以下のように認識される。
print(not 10)
# False
print(False in [1, 2, 3])
# False
複数の要素に対するin
複数の要素が含まれているかを判定したい場合、以下のように複数の要素をリストで書いてもうまくいかない。リスト自体が含まれているかの判定になってしまう。
print([0, 1] in [0, 1, 2])
# False
print([0, 1] in [[0, 1], [1, 0]])
# True
and
, or
を使うか、集合set
を使う。
and, orを使う
論理演算子and
(かつ)、or
(または)を使って、複数のin
演算を組み合わせる。どちらも含まれている、あるいは、どちらかが含まれている、という判定になる。
l = [0, 1, 2]
v1 = 0
v2 = 100
print(v1 in l and v2 in l)
# False
print(v1 in l or v2 in l)
# True
print((v1 in l) or (v2 in l))
# True
in
, not in
のほうがand
, or
より優先順位が高い(先に処理される)ので括弧は必要ないが、分かりにくい場合は最後の例のように括弧で囲んでも問題はない。
集合を使う
判定したい要素の数が多い場合は、and
, or
よりも集合set
を使うほうが簡単。
set()
で集合に変換したあとで集合演算を行う。集合演算についての詳細は以下の記事を参照。
例えば、リストA
にリストB
の要素がすべて含まれているかは、リストB
がリストA
の部分集合か(またはリストA
がリストB
の上位集合か)を判定すればよい。
l1 = [0, 1, 2, 3, 4]
l2 = [0, 1, 2]
l3 = [0, 1, 5]
l4 = [5, 6, 7]
print(set(l2) <= set(l1))
# True
print(set(l3) <= set(l1))
# False
リストA
にリストB
の要素がひとつも含まれていないことを判定したい場合は、リストA
とリストB
が互いに素であるかを確認すればよい。
print(set(l1).isdisjoint(set(l4)))
# True
リストA
とリストB
が互いに素でなければ、リストA
にリストB
の要素が少なくともひとつは含まれていると判定できる。
print(not set(l1).isdisjoint(set(l3)))
# True
集合を利用することで共通の要素を抽出したりすることも可能。以下の記事を参照。
inの処理速度比較
in
演算子の処理速度は対象のオブジェクトの型によって大きく異なる。
ここではリスト、集合、辞書に対するin
の処理速度の計測結果を示す。以下のコードはJupyter Notebookのマジックコマンド%%timeit
を利用しており、Pythonスクリプトとして実行しても計測されないので注意。
時間計算量については以下を参照。
要素数10個と10000個のリストを例とする。
n_small = 10
n_large = 10000
l_small = list(range(n_small))
l_large = list(range(n_large))
以下はCPython3.7.4による結果であり、他の実装では異なる可能性がある。特別な実装を使っているという認識がない場合はCPythonだと思ってまず間違いない。また、当然ながら、測定結果の絶対値は環境によって異なる。
リストlistは遅い: O(n)
リストlist
に対するin
演算子の平均時間計算量はO(n)
。要素数が多いと遅くなる。結果の単位に注意。
%%timeit
-1 in l_small
# 178 ns ± 4.78 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%%timeit
-1 in l_large
# 128 µs ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
探す値の位置によって処理時間が大きく変わる。探す値が最後にある場合や存在しない場合に最も時間がかかる。
%%timeit
0 in l_large
# 33.4 ns ± 0.397 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%%timeit
5000 in l_large
# 66.1 µs ± 4.38 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%%timeit
9999 in l_large
# 127 µs ± 2.17 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
集合setは速い: O(1)
集合set
に対するin
演算子の平均時間計算量はO(1)
。要素数に依存しない。
s_small = set(l_small)
s_large = set(l_large)
%%timeit
-1 in s_small
# 40.4 ns ± 0.572 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%%timeit
-1 in s_large
# 39.4 ns ± 1.1 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
リストのように探す値によって処理時間が大きく変わることもない。
%%timeit
0 in s_large
# 39.7 ns ± 1.27 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%%timeit
5000 in s_large
# 53.1 ns ± 0.974 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%%timeit
9999 in s_large
# 52.4 ns ± 0.403 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
要素数が多いリストに対してin
による処理を繰り返すような場合は、あらかじめ集合set
に変換しておくと速い。結果の単位に注意。
%%timeit
for i in range(n_large):
i in l_large
# 643 ms ± 29.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit
s_large_ = set(l_large)
for i in range(n_large):
i in s_large_
# 746 µs ± 6.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
なお、リストからset
に変換するのにも時間がかかるので、in
の処理回数が少ないとリストのままのほうが速いこともある。
辞書dictの場合
キーと値が同じ数値の辞書を例とする。
d = dict(zip(l_large, l_large))
print(len(d))
# 10000
print(d[0])
# 0
print(d[9999])
# 9999
上述のように、辞書dict
をそのままin
演算で使うとキーに対する判定となる。辞書のキーは集合set
と同様に一意な値であり、set
と同程度の処理速度となる。
%%timeit
for i in range(n_large):
i in d
# 756 µs ± 24.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
一方、辞書の値はリストのように重複を許す。values()
に対するin
の処理速度はリストと同程度。
dv = d.values()
%%timeit
for i in range(n_large):
i in dv
# 990 ms ± 28.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
キーと値の組み合わせは一意。items()
に対するin
の処理速度はset
+ αぐらい。
di = d.items()
%%timeit
for i in range(n_large):
(i, i) in di
# 1.18 ms ± 26.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
for文やリスト内包表記におけるin
for文やリスト内包表記の構文においてもin
という語句が使われる。このin
はin
演算子ではなく、True
またはFalse
を返しているわけではない。
l = [0, 1, 2]
for i in l:
print(i)
# 0
# 1
# 2
print([i * 10 for i in l])
# [0, 10, 20]
for文やリスト内包表記についての詳細は以下の記事を参照。
リスト内包表記では条件式としてin
演算子を使う場合があり、ややこしいので注意。
l = ['oneXXXaaa', 'twoXXXbbb', 'three999aaa', '000111222']
l_in = [s for s in l if 'XXX' in s]
print(l_in)
# ['oneXXXaaa', 'twoXXXbbb']
はじめのin
がリスト内包表記のin
で、うしろのin
がin
演算子。