以下の続きです。
下の動画を参考にしています。
決定性有限オートマトンを使うと、文字列検索アルゴリズムは以下の流れで表現できます。
オートマトンを作る
あるパターン\( P[0, 1, … , m-1] \)が与えられたとして、そのパターンに基づきオートマトンを作ります。
$$ \begin{align} & Q = \{ 0, 1, 2, …, m \} \\ & Σ = \{x | x はパターンPに含まれる文字\} \\ & q_0 = 0 \\ & F = \{m\}\\ & δ : 状態遷移表の作り方は以下に述べる。 \\ \end{align} $$
⊏ と ⊐ の意味
⊐ という記号が、このアルゴリズムについての疑似コードを検索すると良く出てきます。文脈で何となくは意味は分かりますが、ちゃんと説明しているところがなかなか見つからず苦労しました。こちらのサイトに説明がありました。
Prefix 接頭辞
文字列 w が 文字列 x の接頭辞である時、 w ⊏ x と書く。例えば、 ab ⊏abcca である。
Suffix 接尾辞
文字列 w が文字列 x の接尾辞である時、 w ⊐ x と書く。例えば、 cca ⊐ abcca である。
また、空文字列 ε は、全ての文字列の接頭辞でもあり、接尾辞でもある。
状態遷移表の作り方
状態遷移表は、入力文字列から新しい文字を持ってくる時、「文字列の接尾辞はパターンの接頭辞と一致する必要がある」ということから考えることができます。一致すると考えられる最長の文字列から比較を開始し、全く一致しない場合は初期状態に戻ります。一致する場合は、その一致する長さにより次の状態へと遷移します。
文字列の検索
オートマトンが出来れば、あとはただ文字列をスキャンして、受理状態になればマッチングしたと考えることができます。
import random
import string
def match_pattern_finite_automaton(text, transition_table, pattern):
m = len(pattern)
s = 0
for i, c in enumerate(text):
s = transition_table[s][c]
if s == m:
return i - m + 1
return -1
def delta(pattern, text):
prospected_letters = set(text)
m = len(pattern)
transition_table = [{c:0 for c in prospected_letters} for i in range(m)]
for s in range(m):
for c in prospected_letters:
k = min(m, s+1)
while (pattern[:s]+c)[-k:] != pattern[:k]:
k-=1
transition_table[s][c] = k
return transition_table
def print_match_pattern_result(text, pattern):
transition_table = delta(pattern, text)
index = match_pattern_finite_automaton(text, transition_table, pattern)
if index>=0:
print(f'"{pattern}"は {index}番目に見つかりました。')
else:
print(f'"{pattern}"は見つかりません。')
random.seed(1)
target_str = ''.join(random.choices(string.ascii_letters + string.digits, k=20))
# i0VpEBOWfbZAVaBSo63b
print(target_str)
# "BSo63"は 14番目に見つかりました。
print_match_pattern_result(target_str, "BSo63")
# "BSo6E"は見つかりません。
print_match_pattern_result(target_str, "BSo6E")
想定通りの結果を得ることができました。