note.nkmk.me

Pythonで文字列を検索(〜を含むか判定、位置取得、カウント)

Date: 2019-06-12 / tags: Python, 文字列操作, 正規表現

Pythonで文字列を検索して任意の文字列を含むか判定したり、その位置を取得したり、ヒットした個数をカウントしたりする方法について説明する。

  • 任意の文字列を含むか判定: in演算子
  • 任意の文字列の位置を取得: find(), rfind()
  • 任意の文字列の個数をカウント: count()
  • 単語を検索、個数をカウント
  • 大文字小文字を区別せずに検索

標準ライブラリのreモジュールを使うと正規表現でより柔軟な処理が可能。標準ライブラリなので追加のインストールは必要ない。

  • 正規表現で判定、位置取得: re.search()
  • 正規表現ですべての結果を取得、カウント: re.findall(), re.finditer()
  • 正規表現で複数の文字列を検索
  • 正規表現パターンを活用
  • 正規表現で大文字小文字を区別せずに検索: re.IGNORECASE

特定の文字列を別の文字列に置換したい場合は以下の記事を参照。空文字列''に置換すると対象の文字列を削除する処理としても利用できる。

文字列同士の比較(完全一致や前方一致、後方一致などの判定)については以下の記事を参照。

テキストファイルの中身を検索したい場合は、ファイルを文字列として読み込めばよい。

スポンサーリンク

任意の文字列を含むか判定: in演算子

文字列の中に任意の文字列が含まれているか判定・確認するにはin演算子を使う。含まれているとTrue、含まれていないとFalseを返す。

大文字小文字は区別される(以降で説明する文字列のメソッドでも同様)。複数の文字列が含まれているかはand(かつ: 両方含む)やor(または: いずれかを含む)を使って判定可能。

s = 'I am Sam'

print('Sam' in s)
# True

print('sam' in s)
# False

print('I' in s and 'Sam' in s)
# True

後述の正規表現を使うとより柔軟な判定ができる。

なお、in演算子はリストやタプル、辞書に対しても使える。if文の条件式でin演算子を使う例など、詳細は以下の記事を参照。

任意の文字列の位置を取得: find(), rfind()

文字列のメソッドfind()で文字列中の任意の文字列の位置を取得できる。

第一引数に指定した文字列が呼び出し元の文字列に含まれている場合はそのスタートの位置(最初の文字の位置)、含まれていない場合は-1が返される。

s = 'I am Sam'

print(s.find('Sam'))
# 5

print(s.find('XXX'))
# -1

位置は0始まり。例の文字列の場合、各文字と位置の対応関係は以下の通り。

I am Sam
01234567

元の文字列に検索した部分文字列が複数存在していても、返されるのは最初の部分文字列(一番左側の部分文字列の)の位置のみ。すべての位置を取得したい場合、次に説明する引数start, endで範囲を狭めていってもいいが、後述の正規表現を使うほうが簡単。

print(s.find('am'))
# 2

第二引数startを指定するとその位置以降が対象、さらに第三引数endを指定するとその位置の前までが対象、すなわち、スライス[start:end]の範囲が対象となる。

print(s.find('am', 3))
# 6

print(s.find('am', 3, 5))
# -1

rfind()は右側から(後ろから)検索してその位置を返すメソッド。

部分文字列が複数存在する場合、一番右側の部分文字列の位置が返される。find()と同様、第二引数start、第三引数endも指定可能。

print(s.rfind('am'))
# 6

print(s.rfind('XXX'))
# -1

print(s.rfind('am', 2))
# 6

print(s.rfind('am', 2, 5))
# 2

find(), rfind()と似たメソッドにindex(), rindex()がある。違いは検索した文字列が存在しない場合。find(), rfind()-1を返すが、index(), rindex()はエラーとなる。

print(s.index('am'))
# 2

# print(s.index('XXX'))
# ValueError: substring not found

print(s.rindex('am'))
# 6

# print(s.rindex('XXX'))
# ValueError: substring not found

任意の文字列の個数をカウント: count()

文字列のメソッドcount()で文字列中に任意の文字列が何個含まれているかをカウントできる。

s = 'I am Sam'

print(s.count('am'))
# 2

print(s.count('XXX'))
# 0

第二引数start、第三引数endを指定可能。find()rfind()と同様、スライス[start:end]の範囲が対象となる。

print(s.count('am', 2, 4))
# 1

同じ文字が重複してカウントされることはない。

s = 'aaaa'

print(s.count('aa'))
# 2

単語を検索、個数をカウント

上の例のように、文字列のcount()メソッドでは、例えばamを検索したい場合にSamもカウントされてしまう。

単語としてカウントしたい場合、split()メソッドで単語ごとのリストに変換する方法がある。リストにもcount()メソッドがあり、値が完全に一致した要素のみがカウントされる。

s = 'I am Sam'

l = s.split()
print(l)
# ['I', 'am', 'Sam']

print(l.count('am'))
# 1

長い文章の場合、Python標準ライブラリcollectionsのCounterクラスを使うと各単語の出現回数などが簡単にカウントできるので便利。以下の記事を参照。

なお、split()で単語に分割するのは最もシンプルな方法。実際の文章は様々な記号が含まれていたりするので、厳密に分割するにはNLTKなどの自然言語処理ライブラリを利用するのが安全。

また、Pythonで日本語の文章を分かち書きするにはJanomeが便利。

大文字小文字を区別せずに検索

ここまで説明した方法(in演算子や文字列のメソッド)は大文字小文字を区別して処理する。

大文字小文字を区別せずに検索したい場合は、元の文字列と検索する文字列をどちらも大文字または小文字に変換して統一すればよい。

文字列をすべて大文字に変換するにはupper()、小文字に変換するにはlower()を使う。

s = 'I am Sam'

print(s.upper())
# I AM SAM

print(s.lower())
# i am sam

print('sam' in s)
# False

print('sam' in s.lower())
# True

print(s.find('sam'))
# -1

print(s.lower().find('sam'))
# 5

print(s.count('sam'))
# 0

print(s.lower().count('sam'))
# 1

なお、日本語のような大文字小文字の区別がない文字を含む文字列にupper()lower()メソッドを適用しても問題ない。

s = '私はSam'

print(s.lower())
# 私はsam

print(s.upper())
# 私はSAM

正規表現で判定、位置取得: re.search()

ここからは標準ライブラリのreモジュールで正規表現を使う。reモジュールについての詳細は以下の記事を参照。同じパターンで処理する際に効率的な正規表現パターンのコンパイルについても触れている。

正規表現で特定の文字列を含むか判定するにはre.search()を使う。

第一引数に正規表現パターンの文字列、第二引数に対象の文字列を指定する。正規表現パターンにはメタ文字などを使うこともできるが、ここでは、最もシンプルなパターンとして検索文字列をそのまま使う。

マッチする部分があるとマッチオブジェクト、マッチする部分がないとNoneが返される。

import re

s = 'I am Sam'

print(re.search('Sam', s))
# <re.Match object; span=(5, 8), match='Sam'>

print(re.search('XXX', s))
# None

マッチオブジェクトは常にTrueと判定されるので、if文で条件分岐したい場合は、条件式としてre.search()またはその返り値をそのまま指定すればよい。マッチオブジェクトの詳細については以下の記事を参照。

マッチオブジェクトのメソッドgroup()でマッチした文字列(この場合は検索文字列そのまま)、start(), end(), span()で開始位置、終了位置、(開始位置, 終了位置)のタプルがそれぞれ返される。

m = re.search('Sam', s)

print(m.group())
# Sam

print(m.start())
# 5

print(m.end())
# 8

print(m.span())
# (5, 8)

正規表現ですべての結果を取得、カウント: re.findall(), re.finditer()

re.search()はマッチする部分が複数含まれていても最初の部分のマッチオブジェクトを返すのみ。

s = 'I am Sam'

print(re.search('am', s))
# <re.Match object; span=(2, 4), match='am'>

re.findall()は、マッチするすべての部分を文字列のリストとして返す。その要素数(組み込み関数len()で取得)がマッチした部分の数となる。

print(re.findall('am', s))
# ['am', 'am']

print(len(re.findall('am', s)))
# 2

マッチするすべての部分の位置を取得したい場合は、re.finditer()とリスト内包表記を組み合わせる。

print([m.span() for m in re.finditer('am', s)])
# [(2, 4), (6, 8)]

例はspan()なので(開始位置, 終了位置)のタプルのリストだが、開始位置または終了位置のみのリストを取得したい場合はstart(), end()を使えばよい。

なお、re.finditer()はマッチするすべての部分のマッチオブジェクトのイテレータを返す関数。詳細は以下の記事を参照。

正規表現で複数の文字列を検索

正規表現に詳しくない場合も覚えておくと便利なのが|。正規表現パターンをA|BとするとAまたはBにマッチする。A, Bはただの文字列でも問題ない(もちろんメタ文字などを使ってもOK)。3個以上でも同様にA|B|Cとすればよい。

以下のように、複数の文字列を検索できる。

s = 'I am Sam Adams'

print(re.findall('Sam|Adams', s))
# ['Sam', 'Adams']

print([m.span() for m in re.finditer('Sam|Adams', s)])
# [(5, 8), (9, 14)]

正規表現パターンを活用

正規表現パターンとして検索文字列をそのまま使うと単純な処理しかできないが、正規表現のメタ文字や特殊シーケンスを利用するとより柔軟に検索できる。

s = 'I am Sam Adams'

print(re.findall('am', s))
# ['am', 'am', 'am']

print(re.findall('[a-zA-Z]+am[a-z]*', s))
# ['Sam', 'Adams']

Python(reモジュール)の正規表現も基本的には標準的な正規表現のシンタックスと同じ。ワイルドカード的な指定など、正規表現パターンを活用した基本的な例を以下の記事で紹介しているので参考にされたい。

正規表現で大文字小文字を区別せずに検索: re.IGNORECASE

re.search()re.findall()などの関数の引数flagsre.IGNORECASEを指定することで、大文字小文字を区別せずに検索できる。

s = 'I am Sam'

print(re.search('sam', s))
# None

print(re.search('sam', s, flags=re.IGNORECASE))
# <re.Match object; span=(5, 8), match='Sam'>
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事