Python Tech 文字列

Python 文字列の比較・一致検索・類似度計算の方法一覧(保存版)

文字列の比較は、プログラムで最もよく使われる操作のひとつです。

文字列と文字列の以下のような比較はPythonでも頻繁に用いられます。

  • 完全に一致する
  • 部分的に一致/含まれる
  • 前方一致、後方一致
  • パターンの一致
  • 類似度を計算する

このようなPythonでの文字列の比較・一致の検証手法を、現役のエンジニアが解説しています。

一致した部分を置換(置き換え)する方法は以下のページでまとめています。

 

結論

完全に一致するか検証
一致するとき==がTrue, 不一致のとき!=がTrue

print('bar' == 'bar')
# True

print('bar' != 'foo')
# True

 

部分的に一致するか検証

text = 'abcd'
print('bc' in text)
# True

 

前方一致・後方一致を検証

text = 'ABCDEFGH'
print(text.startswith('AB'))
# True
text = 'ABCDEFGH'
print(text.endswith('GH'))
# True

 

パターンとの一致を検証
・全体とパターンが一致するか検討する re.fullmatch()

m = re.fullmatch('abcd', text)
if m:
    print('matched!')
# matched!

 

・一部とパターンが一致するか検証

text = 'foobar-123456-buzbuz'

m = re.search(r'\d+', text)
if m:
    print(m.group(0))
# 123456

 

・類似度を計算

import difflib

text_a = "This is a sample"
text_b = "This is the sample"
r = difflib.SequenceMatcher(None, text_a, text_b).ratio()
print(r)
# 0.8823529411764706

 

実行環境

Python 3.9.2

 

完全に一致するか検証

文字列が完全に一致(同一の内容)か判定するには == 比較演算子を用います。

== は文字列がすべて一致するときの他、数値や論理式が等価のときにTrueを返します。

print('bar' == 'bar')
# True

print('bar' == 'foo')
# False

 

!===の否定形であり、非等価(不一致)のときにTrueを返します。

print('bar' != 'foo')
# True

 

==による比較では、大文字・小文字が区別されます。

print('bar' == 'baR')
# False

 

同様に、全角・半角文字は異なる文字として区別されます。

print('abc' == 'abc')
# False

 

==はスペースを含む文字列、長い文章にも適用できます。

text_a = "This is the same sentence"
text_b = "This is the same sentence"
print(text_a == text_b)
# True

 

部分的に一致/含むを検証

文字列が含まれているか(ある文字列が、他の文字列の部分文字列かどうか)はin 演算で判定できます。

文字列 x が s に含まれているか検討するには x in sとし、含まれていればTrueとなります。

text = 'abcdefg'
print('bc' in text)
# True

print('xy' in text)
# False

 

in 演算の否定形はnot inです。

よく使われるのは、not inif文を使って、文字列に含まれているか否かで条件分岐をする形です。

text = 'abcdefg'
if 'x' not in text:
    print("No 'x' in text")
# No 'x' in text

 

前方一致・後方一致を検証

文字列の先頭に特定の文字列が含まれるか(前方一致)を検討するには、str.startswith()を用います。

text = 'ABCDEFGH'
print(text.startswith('AB'))
# True

print(text.startswith('GH'))
# False

 

文字列の末尾に特定の文字列が含まれるか(後方一致)を検討するには、str.endswith()を用います。

text = 'ABCDEFGH'
print(text.endswith('GH'))
# True

print(text.endswith('AB'))
# False

 

なお、str.startswith()str.endswith() 共に、文字列の代わりにタプルを渡すことで、複数条件を実現できます。

text = 'ABCDEFGH'
cand = ('XX', 'AB', 'IJ')
print(text.startswith(cand))
# True

 

パターンの一致を検証

あるパターンで文字列が構成されているかどうかを調べるには、正規表現の標準ライブリ re を使用します。
使用するにはreライブラリのインストールは不要ですが、importは必要です。

import re

 

全体がパターンに一致するか検証

全体がパターンに一致しているか検討するには、re.fullmatch() を用います。一致する場合はMatchオブジェクト、不一致ならNoneが返されます。

引数の構造は以下のとおり。

match = re.fullmatch(pattern, string)

pattern正規表現で表したパターン
string検証対象の文字列

patternには、通常の文字列をそのまま書いてもOKです。
一致する場合はMatchオブジェクトが返され、MatchオブジェクトはifではTrueとなります。

import re

text = 'abcd'

m = re.fullmatch('abcd', text)
if m:
    print('matched!')
# matched!

 

. は任意の1文字を表現します。

m = re.fullmatch(r'a..d', text)
if m:
    print('matched!')
# matched!

 

+ は直前の文字の1文字以上の繰り返しを表現します。.+なら、任意の文字の1文字以上の組み合わせとなります。

以下の例では textは "c" で終わるパターンではないので、不一致となり、Noneが返されます。

m = re.fullmatch(r'.+c', text)
print(m)
# None

 

部分的にパターンが一致するか検証

文字列の全体ではなく、部分的にあるパターンに一致しているか検討するには、re.search() を使います。文字列の先頭にあるパターンを検討するときは re.match() でも問題ありませんが、ここでは簡潔にre.search()のサンプルを示しています。

re.search()の使い方や戻り値は re.fullmatch()と同じです。

正規表現で任意の数字を表すには[0-9]としますが、\dでも同じ意味です。\d+で任意の数字の繰り返しとなります。

Matchオブジェクトから、一致した部分を取り出すには group() を用います。

import re

text = 'foobar-123456-buzbuz'

m = re.search(r'\d+', text)
if m:
    print('matched!')
    print(m.group(0))
# matched!
# 123456

 

re.search()で不一致の場合はNoneが返されます。

特定の文字のいずれかにマッチさせるには、それらを [ ] で囲んで表します。

以下の例では text には "x", "y"のいずれも含まれないのでマッチせず、Noneが返ってきています。

m = re.search(r'[xy]', text)
print(m)
# None

 

任意の小文字アルファベットが[a-z]と表現できるので、任意の英小文字の繰り返し部分が含まれるパターンは以下のようになります。^ は文字列の先頭を表します。

m = re.search(r'^[a-z]+', text)
if m:
    print('matched!')
    print(m.group(0))
# matched!
# foobar

 

反対に、文字列の末尾を表すには $ を用います。

m = re.search(r'[a-z]+$', text)
if m:
    print('matched!')
    print(m.group(0))
# matched!
# buzbuz

 

大文字・小文字・全角・半角の違いを吸収

大文字・小文字

==比較演算では、大文字・小文字を区別するので次の2変数は同値ではありません。

s_large = "Hoge Fuga"
s_small = "hoge fuga"
print(s_large == s_small)
# False

 

大文字と小文字の違いを吸収し、差を考慮せずに比較するには、どちらかに統一すればよいでしょう。

小文字に統一するときは str.lower()、大文字ならstr.upper() を使用します。

print(s_large.lower())
# hoge fuga

print(s_large.upper())
# HOGE FUGA

print(s_large.lower() == s_small.lower())
# True

 

全角・半角

全角文字と半角文字も、異なる文字として評価されます。

full = "カタカナモジ"
half = "カタカナモジ"
print(full == half)
# False

 

半角文字が含まれる文字列を全角に統一するには、標準ライブラリの unicodedataパッケージを利用します。

unicodedata.normalize() はユニコード文字列を一定の規則に従って正規化します。

str = unicodedata.normalize(form, unistr)

正規化の形式 form は'NFC'、'NFKC'、'NFD'、'NFKD'の4つの中から指定でき、それぞれ同値とみなす文字が異なってきます。詳しくはドキュメント参照。

半角カタカナを全角カタカナに置き換えるだけなら、'NFKC'でよいでしょう。

import unicodedata

print(half)
# カタカナモジ

half_normal = unicodedata.normalize('NFKC', half)
print(half_normal)
# カタカナモジ

print(full == half_normal)
# True

 

類似度を計算する

文字列を比較するテクニックとして、文字列同士の類似度を計測することが考えられます。

Pythonではさまざまな実装があり、それぞれ使用しているアルゴリズムは多様なものです。実行時間や適する応用ジャンルが異なりますが、ここでは使用方法を軽く解説するのに留めます。

 

difflib(Ratcliff-Obershelp)

最も手軽に実装できるのは、Pythonの標準ライブラリである difflib を使用する方法。

Ratcliff-Obershelpというアルゴリズムにより類似度が計算されます。

使用には difflib のimportが必要です(インストールは不要)。

r = difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

isjunk 類似度算出で無視する文字を指定(デフォルトはNone)
a 文字列1
b 文字列2
autojunk 自動 junk ヒューリスティックの有効化フラグ(デフォルトはTrue)

difflib --- 差分の計算を助ける — Python 3.9.4 ドキュメント

SequenceMatcher.ratio()

[0, 1] の範囲の浮動小数点数で、シーケンスの類似度を測る値を返します。

類似度が高いほど(完全一致に近づくほど)1.0に近くなり、反対に類似度が低い文字列は0に近づきます。

import difflib

# 同じテキスト
text_a = "Same Text"
text_b = "Same Text"
r = difflib.SequenceMatcher(None, text_a, text_b).ratio()
print(r)
# 1.0

# 全く異なるテキスト
text_a = "ABC"
text_b = "XYZ"
r = difflib.SequenceMatcher(None, text_a, text_b).ratio()
print(r)
# 0.0

# 類似するテキスト
text_a = "This is a sample"
text_b = "This is the sample"
r = difflib.SequenceMatcher(None, text_a, text_b).ratio()
print(r)
# 0.8823529411764706

 

Levenshtein (レーベンシュタイン距離法)-

パッケージのインストールが必要ですが、文字列の類似度比較で比較的手軽に使えるのがLevenshteinです。

文字列間の編集距離を求めるアルゴリズム Levenshtein は直感的にも理解しやすいもので、よく使われます。情報の授業や雑誌などで見かけたことのある人も多いのでは。

pipなら以下のようにインストールします。

$ pip install python-Levenshtein

Levenshtein.distance(string1, string2)

string1, string2 対象の文字列

戻り値はレーベンシュタイン距離です。difflib.SequenceMatcherのように0~1の数値に正規化するには、distance()のレーベンシュタイン距離を文字列の長さで除します。(最も長い文字列長に合わせます)

このようにすると、結果の数値は、difflibの ratio() とは反対になり、類似度が高いほど(完全一致に近づくほど)0に近くなり、反対に類似度が低い文字列は1.0に近づきます。

text_a = "Same Text"
text_b = "Same Text"
d = Levenshtein.distance(text_a, text_b)
max_len = len(text_a) if len(text_a) > len(text_b) else len(text_b)
r = d / max_len
print(r)
# 0

 

いちいち上記のように書くわけにはいかないので、関数化します。

def levens_normalize(text1:str, text2:str) -> float:
    d = Levenshtein.distance(text1, text2)
    max_len = len(text1) if len(text1) > len(text2) else len(text2)
    r = d / max_len
    return r

# 全く異なるテキスト
text_a = "ABC"
text_b = "XYZ"
r = levens_normalize(text_a, text_b)
print(r)
# 1.0

# 類似するテキスト
text_a = "This is a sample"
text_b = "This is the sample"
r = levens_normalize(text_a, text_b)
print(r)
# 0.16666666666666666

 

Pythonの学習法について

 

Python の勉強が辛くなっていませんか?

 

Pythonは比較的取り組みやすい言語と言われていますが、そうはいってもプログラミング初心者にとっては分からないことだらけ。

ゼロから独学で勉強するのは厳しい道のりです。

今回、様々な現場、システム、言語を経験してきた現役エンジニアの立場から、初心者でも挫折しない学習方法を解説する記事を書きました。

できるだけストレスがかからない勉強法を無料で解説しているので、ぜひ参考にしてみてくださいね。

 

今回参考にしたページ・資料

比較 組み込み型 — Python 3.9.4 ドキュメント

共通のシーケンス演算 組み込み型 — Python 3.9.4 ドキュメント

unicodedata.normalize unicodedata --- Unicode データベース — Python 3.9.4 ドキュメント

distance Levenshtein.html

 

  • この記事を書いた人

次世代ペンギン

長いのでペンギンとお呼びください。システム開発・プログラミングのお仕事をしています。甘味とコーヒーは生命線。多くの人に役立つ情報のシェアが目標です。

人気の記事

1

会社員でプログラマーとして働いている人、インフラやネットワークのエンジニアとして働いている人の中には、フリーランスのプログラマーとして独立、もしくは転向したい人もいるので ...

2

キャリアアップのため、または高収入を目指して、しっかりプログラミングを学びたいという人が増えてきましたね。 この記事では現役のエンジニアである私が、実際に仕事で稼げるよう ...

3

フリーランスのプログラマーにとって収入の向上に最も直結するのはスキルです。 必要なスキル、スキルの獲得方法が気になる人も多いでしょう。 また、これからフリーランスを目指す ...

4

Vuetifyの v-progress-circular コンポーネントは、数値データや処理状況を環状(円状)のデザインで教えてくれるUIデザインです。 ローディングのス ...

5

Vuexのstore(ストア)を使うと、各コンポーネント間で個別にデータのやり取りすることなく、データを一元的に管理できます。Vueでは欠かせない機能といえるでしょう。 ...

-Python, Tech, 文字列
-,

© 2021 ペンギンのーと