作者: Hiroshi Shinohara
日時: 2003/8/25(21:37)
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