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, 文字列
-,