Iconミニ講座10(分割文字で辞書参照)
さて、まとめで、フルスペックのプログラムにまとめましょう。
・入力文字列を分割したもので、辞書参照。
・不明文字対応のため、曖昧検索指定。
・辞書は、文字数別に分割し、必要なものだけ使う。
というところで、まとめると次のようになります。
尚、試して頂きやすいように、全ての procedureを1つのファイルにしていますが、
サブ procedureを、別のファイルにして、mainファイルから linkするようにもでき
ます。
-----^ DICREFP5.ICN ( date:03-08-25 time:20:05 ) -----------<cut here
####################
# 文字列の組合せで、辞書参照。曖昧検索。分割辞書。ワイルドカード未対応
####################
# dicrefp5.icn Rev.1.0 2003/08/25 windy 風つかい H.S.
####################
# Usage dicrefp5 英文字列(.はワイルドカード)
# 文字数毎に分割された辞書ファイルを使用。 english.dicを分割。
# english.dicは、スペルチェック用の英単語が順に並んだもの。
# このプログラムのテストでは、DD SOFT SoundMixSpellコンポーネント
# Ver 0.3.0 に 同梱の辞書ファイルを使用。
# This file is in the public domain.
procedure main(args)
# コマンドライン引数チェック。無ければ Usage表示
if *args < 1 then stop("dicrefp5 英単語(.はワイルドカード)",
" 最大分割数 最小文字長")
# コマンドラインの引数から、辞書を読込
c_word := args[1] # コマンドラインの英文字列
ndiv := \args[2] | 1 # 分割数指定無しは、1(分割せず)
nmin := \args[3] | *c_word # 最小文字長指定無しは、引数文字列長
# 分割パターン格納
L_lpat := [] # 文字列分割パターン格納 list
every put(L_lpat,n_divdf(*c_word,ndiv,nmin)) # 分割パターン格納
# 必要な文字数に対応する辞書の読込
S_pat := set() # 文字数格納 set(指定文字数辞書読込のため)
every L := !L_lpat do every insert(S_pat,!L) # 文字数を全て 格納
T_dic := table() # 辞書格納 table生成
every n_pat := !S_pat do {
# ↓右寄せ桁合わせ関数
dic := "e" || right(n_pat,2,"0") # 辞書ファイル名
dir := open(dic) | stop(" ",n_pat,"文字用の辞書が見つかりません。")
# 辞書ファイルオープン
writes(" ",n_pat,"文字用辞書 ",dic," を読込中です。開始:",&clock)
n := 0 # 辞書行数カウンタ
while word := read(dir) do { # 辞書を1行ずつ読み込んで、
n +:= 1
if n % 1000 = 0 then writes(&errout,"*") # 読み込み状況表示
# 単語を小文字変換しソートしたものをインデックスにして格納
# 同一文字を含む単語は同じインデックスに listの要素として格納される。
# ↓文字列ソート
s_word := csort(map(word)) # 小文字へ変換し、ソートして
# ↑小文字変換
if member(T_dic,s_word) # 辞書テーブルにあるかチェック
then put(T_dic[s_word], word) # あれば、その listに追加
else T_dic[s_word] := [word] # 無ければ、listに入れて登録
}
close(dir) # 辞書ファイルクローズ
write(&errout)
write(" 終了:",&clock," ",n,"語ありました。")
}
write("\n",c_word," を、最大分割数 ",ndiv,"、最小文字長 ",nmin,
" にて分割してチェック。開始:",&clock)
# 辞書参照
n_comb := 0 # 組合せ数カウンタ
n_find := 0 # 辞書にある件数カウンタ
s_word := deletec(c_word,'.') # コマンドライン文字列から '.'を削除
n_dot := *c_word -*s_word # '.'の数
# ↓英小文字
every s_wild := mscombd(string(&lcase),n_dot) do { # ワイルドカード文字を、
s_check := s_wild || map(s_word) # コマンドライン引数の '.'以外に足し、
every Lpat:= !L_lpat do { # 分配文字数パターンから順次取り出して、
every Lword := expcomb(s_check,Lpat) do { # 分配結果を順次取り出して、
n_comb +:= 1 # チェック回数カウンタ+1
if n_comb % 1000 = 0 then writes(&errout,"*") # チェック状況表示
ERR := &null # 辞書参照エラーフラッグリセット
Llresult := [] # 辞書検索結果格納 list
every (ss := !Lword) & /ERR do { # 細分文字を取り出して、
# ↑既に辞書参照エラーが発生していなければ、
# 細分文字が辞書に存在するかチェック
if member(T_dic,ss) then put(Llresult,T_dic[ss]) # 結果データ格納
else ERR := "ERR" # 参照エラーフラッグセット
}
# 細分文字列が全て辞書にあれば、書き出し
if /ERR then { # エラーフラッグが立っていなければ
n_find +:= 1 # カウンタ+1
writes(&errout,"!") # 合致表示
writes(s_word) #
if *s_wild >= 1 then writes(" + ",s_wild)
writes(" ->") #
nn := 0 # 細分文字群カウンタ
every Lstr := !Llresult do { # 細分文字群を取り出して
nn +:= 1 # カウンタ+1
if nn > 1 then writes(" +") # 先頭でなければ、区切りマーク
every writes(" ",!Lstr) # 文字群を書き出し
}
write()
}
}
}
}
write(&errout)
write(n_comb," 通りの組合せのうち、",n_find," 通りが辞書にありました。",
" 終了:",&clock)
end
####################
# 分配の組合せ
####################
# arg [1]: string 分配する英文字列:分配パターンのトータルに合ってること。
# [2]: list 分配パターン(降順の list)
# [3]: string 前の文字列(同一文字数指定の場合に降順のものだけ選ぶための)
# value : list 分割された文字列( listに格納)
# Usage : every LL := expcomb(s,L) do ..
# ("abc", [2,1]) -> ["ab","c"], ["ac","b"], ["bc","a"]
# ("abcd",[2,2]) -> ["ab","cd"],["ac","bd"],["ad","bc"]
procedure expcomb(s,L,s_ref)
/s_ref := "" # 指定なければ、空文字
s := csort(s) # 文字列ソート
if *L = 1 then { # パターン要素の最後で、
if L[1] = *s_ref then { # 前回と文字数が同じで、
if s << s_ref then return &fail # 辞書順で前なら、失敗させる。
}
return [s] # ↑で、無ければ、文字列を返す。
}
# 1文字の文字列指定ならば、後は、順に1文字ずつ listに入れるだけ
if L[1] = 1 then { # 1文字の文字列指定ならば、
# 後は、順に1文字ずつ listに入れるだけ
return [s[1]] ||| expcomb(s[2:0], L[2:0])
} # ↑先頭文字 ↑残り文字列 ↑残りのリスト
# 2文字以上の文字列指定ならば、
every ss := exscomb(s,L[1]) do { # 文字の組合せを作って
if *ss = *s_ref then { # 前回と文字数が同じで、
if ss << s_ref then return &fail # 辞書順で前なら、失敗させる。
} # ↓参照用文字列
suspend [ss] ||| expcomb(ssub(s,ss),L[2:0],ss)
} # ↑組合せ文字 ↑残り文字列 ↑残りのリスト
end
####################
# 文字列の引き算
####################
# arg [1]: string 被削除文字列
# [2]: string 削除文字列
# value : string 結果文字列
# 文字列 s1の先頭から、文字列 s2の文字を削除。1:1で削除。
# ("abcabc","abd") -> "cabc" s2にある文字で、s1に無い文字は無視される。
procedure ssub(s1,s2)
every c := !s2 do { # s2から1文字ずつ取り出して
ss := "" # 上記文字を削除後の文字列の格納エリア
s1 ? { # s1を走査対象として、
if ss ||:= tab(upto(c)) then { # 文字が見つかれば そこ迄の文字列を
# ssに足し込み
move(1) # 1文字スキップ
ss ||:= tab(0) # 残りの文字列を足し込み
s1 := ss # s1更新
}
}
}
return s1
end
####################
# 文字列の nCm (n = *s) n個文字列から、m文字を選ぶ。 generator
####################
# stringの組み合わせ
# nCm (n = *s) n個のものから、m個を選ぶ。
# arg: [1]: s: string
# [2]: m: integer
# value: string
# Usage: every ss := exscomb(s,m) do ...
# Icon入門講座3(11)scomb()を同一文字指定対応に拡張
procedure exscomb(s,m)
initial {
s := csort(s) # 文字列をソートする。(BIPL:strings.icn)
/m := *s # デフォルト nCn (n = m = *s)
}
if m = 0 then return "" # mを文字数カウンターに使う。
# 0なったら、そこで打ち止め。
suspend s[i := new_pos(s)] || exscomb(s[i+1 : 0], m -1)
#↑ 1文字選ぶ ↑ ↑←前で選んだ文字以降 ↑指定文字数に
# | の文字列に対し同じ 文字列を抑え
# | 処理を行う。 るためのカウ
# 文字列の連結 ンター
end
####################
# ソートされた文字列 sの左はじから、順に文字位置を出力する generator。
####################
# 手前の文字と同一ならスキップする。
# arg [1]: s string
# value: integer
# Usage: every i := new_pos(s) do ...
procedure new_pos(s)
ss := "" # 手前の文字を記憶しておく変数
every i := 1 to *s do {
if ss ~== s[i] then { # 手前の文字と違っていたら
ss := s[i] # 手前文字を更新
suspend i # i を返す。
}
}
end
#############################
# 文字列から重複して、n個、降順のものを取り出す generator
#############################
# arg [1]: string
# [2]: integer
# value : string
# Usage : every ss := mscombd(s,n) do ..
# ("abc",2) -> "aa","ab","ac","bb","bc","cc"
procedure mscombd(s,n)
if n =0 then return "" # 再帰終了
every i := 1 to *s do { # 1〜文字列長まで
# ↓ i番目の文字を取り出して
suspend s[i] || mscombd(s[i:0],n-1)
} # ↑それ以降の文字と組み合わせる
end
####################
# 正整数分割 generator
####################
# 5=3+2 等に数字分割する。この procedureは n_divdを呼びフィルターを掛けている。
# 分割パターンが多すぎる時に、制限をかけるために使用。
# arg [1]: 分割する元の数
# [2]: 最大分割数
# [3]: 最小数
# value : list
procedure n_divdf(r,ndiv,nmin)
every L := n_divd(r) do { # r の分割結果を取り出し
if *L <= ndiv then { # 分割数チェック(listのサイズチェック)
if L[*L] >= nmin then suspend L # 最小数チェック(降順に入っているので
} # 末尾の要素をチェック)
}
end
####################
# 正整数分割 generator
####################
# 5=3+2 等に分割する。分割は降順のみ許す。(例 5=2+3は除外)
# arg [1]: 分割される元の数(再帰の場合は、前の処理の余り)
# [2]: 最大数 (5=2+2+1 の場合に、最初の 2の時 3が余るが、再帰して 3の
# 分割を始める時、3からではなく 2から始めるための細工。降順手当。)
# value : 分解結果の数。listに格納
# Usage : every L := n_divd(r) do ...
procedure n_divd(r,max)
/max := r # 指定無きは、分割される元の数そのもの
if r < 1 then fail # 念のため
if r = 1 then return [1] # 再帰終了
# r >= 2 の場合
if r > max then rs := max # 分割(取り去る)数の最大数の設定。
else rs := r # 分割される数か 最大数指定の 小さい方
every i := rs to 1 by -1 do { # 上記指定数〜1迄、順に−1しながら
rr := r -i # 新たな余り
if rr = 0 then suspend [i] # 余りが無ければ
# 余りがあれば、再帰処理
else if rr > i then suspend [i] ||| n_divd(rr,i) # 余りが大き過ぎる時
else suspend [i] ||| n_divd(rr)
}
end
# BIPL(Icon基本ライブラリー)より
####################
# strings.icnに含まれる 文字のソート procedure
####################
procedure csort(s) #: lexically ordered characters
local c, s1 # ローカル変数宣言(無くても良い)
s1 := "" # 初期値クリア
every c := !cset(s) do # 引数を cset(文字集合)へ変換し順に取り出す。
every find(c, s) do # 取り出した文字で、引数文字列を検索し、
s1 ||:= c # 見つかる度に、文字を s1に足し込む。
return s1
end
# csetから !で要素を取り出す時には、アルファベット順に取り出せる。
####################
# strings.icnに含まれる 文字の削除 procedure
####################
procedure deletec(s, c) #: delete characters
local result # ローカル宣言(無くても良い)
result := "" # 削除後の文字列格納エリア
s ? { # sを走査対象として、
while result ||:= tab(upto(c)) do # cが見つかる迄の文字列を足し込んで
tab(many(c)) # c以外の文字までスキップ
return result ||:= tab(0) # 余りの文字を足し込む
}
end
-----$ DICREFP5.ICN ( lines:290 words:1124 ) ---------------<cut here
曖昧検索指定が無い場合の例をを1つ。
dicrefp5 heavyrain 2 4 >kkk としますと、次のようになります。
-----^ KKK ( date:03-08-25 time:20:56 ) --------------------<cut here
5文字用辞書 e05 を読込中です。開始:20:56:50 終了:20:56:50 12483語ありました。
4文字用辞書 e04 を読込中です。開始:20:56:50 終了:20:56:51 5881語ありました。
9文字用辞書 e09 を読込中です。開始:20:56:51 終了:20:56:55 38640語ありました。
heavyrain を、最大分割数 2、最小文字長 4 にて分割してチェック。開始:20:56:55
heavyrain -> rayah + nevi vein vine
heavyrain -> Aryan + hive
heavyrain -> hiera + navy Navy
heavyrain -> haven + airy Iyar
heavyrain -> hyena + riva vair
heavyrain -> haver + ayin
heavyrain -> hayer + vain vina
heavyrain -> heavy Yahve + airn Arni Iran rain rani
heavyrain -> raven + hiya Yahi
heavyrain -> hairy + Evan nave vane vena
heavyrain -> Invar invar ravin Vanir + yeah
heavyrain -> rainy + have
heavyrain -> hiver + Yana
heavyrain -> nervi riven viner + ayah
heavyrain -> veiny + haar
92 通りの組合せのうち、15 通りが辞書にありました。 終了:20:56:55
-----$ KKK ( lines:21 words:119 ) --------------------------<cut here
さて不明文字が2つあるケースだと、
dicrefp5 a.ahviy.r 2 4 >lll としますと、こんな結果になります。
-----^ LLL ( date:03-08-25 time:20:59 ) --------------------<cut here
5文字用辞書 e05 を読込中です。開始:20:59:10 終了:20:59:10 12483語ありました。
4文字用辞書 e04 を読込中です。開始:20:59:10 終了:20:59:11 5881語ありました。
9文字用辞書 e09 を読込中です。開始:20:59:11 終了:20:59:15 38640語ありました。
a.ahviy.r を、最大分割数 2、最小文字長 4 にて分割してチェック。開始:20:59:15
aahviyr + aa -> Avahi + raya
aahviyr + aa -> varia + ayah
aahviyr + ab -> Bahai + vary
aahviyr + ab -> brava + hiya Yahi
aahviyr + ab -> Avahi + bray Brya
(中略)
aahviyr + en -> hayer + vain vina
aahviyr + en -> heavy Yahve + airn Arni Iran rain rani
aahviyr + en -> raven + hiya Yahi
aahviyr + en -> hairy + Evan nave vane vena
aahviyr + en -> Invar invar ravin Vanir + yeah
(中略)
aahviyr + uy -> hairy + Vayu
aahviyr + vx -> hyrax + viva
aahviyr + wy -> hairy + wavy
aahviyr + xx -> rayah + xxiv XXIV xxvi XXVI
aahviyr + xz -> varix + hazy
27602 通りの組合せのうち、943 通りが辞書にありました。 終了:20:59:20
-----$ LLL ( lines:949 words:7467 ) ------------------------<cut here
結構、合致するものがありますので、途中を略してあります。
943件だとチェックにも時間がかかりそうです。
ちょっと長引きましたが、ちょうど10回目ということで、Iconミニ講座は、
終わりにします。(宣言しておかないと、すぐまた続けそうなので。笑)
長文におつき合い頂きまして、有り難うございます。
風つかい(hshinoh@...)
IconのWWWは、 http://www.cs.arizona.edu/icon/
UniconのWWWは、http://unicon.sourceforge.net/index.html
BGM: Hello It's Me / Lani Hall