作者: Hiroshi Shinohara
日時: 2003/8/24(14:36)
Iconミニ講座8(辞書分割)

 辞書にインデックスを付けて格納するようにしたせいで、辞書読込が40秒以上に
なってしまいました。
 流石に、待ち時間がツライので、辞書から必要な字数部分だけ、読み込むように
しました。

 辞書を、まず文字数毎のファイルに分割します。
 辞書を、単語の文字数毎に、テーブルに読込みます。次に文字数毎のファイルに出力
します。 辞書のダブリチェックも入れて、出力ファイルはダブリを無くします。
  english.dic     T_dic
  +----------+    +-----------------------------+
  |辞書      |    |テーブル                     |
  |ファイル  |--->|key:1 ->value:[a,i]          |---> ファイル e01へ書き出し
  |          |    |key:2 ->value:[AA,Ab,AB,...] |---> ファイル e02へ書き出し
  +----------+    |                             |
                  +-----------------------------+
-----^ DICDIV.ICN ( date:03-08-24 time:12:27 ) -------------<cut here
####################
# 辞書を文字数毎に分割
####################
# dicdiv.icn Rev.1.0 2003/08/24 windy 風つかい H.S.
####################
# Usage dicdiv dictionary_name
#    辞書をファイル読込高速化のため、字数毎に分割するのに使用
#    english.dicは、スペルチェック用の英単語が順に並んだもの。
#    このプログラムのテストでは、DD SOFT SoundMixSpellコンポーネント
#    Ver 0.3.0 に 同梱の辞書ファイルを使用。
# This file is in the public domain.

procedure main(args)
  if *args < 1 then stop("dicdiv dictionary_name")
  dic := args[1]
  # 辞書読込
  dir := open(dic) | stop(dic," が見つかりません")   # ファイルオープン
  n := 0                                 # ファイル行数カウンタ
  S_dic := set()                         # ダブリチェック用 set生成
  T_dic := table()                       # 字数毎ファイル格納 table生成
  write(&errout,dic," を読込中です。")   # ファイル読み込み
  write(&errout,"開始:",&clock)

  while word := read(dir) do {           # ファイルを1行ずつ読み込んで、
    n +:= 1
    if n % 1000 = 0 then writes(&errout,"*")    # 読み込み状況表示
    if member(S_dic,word) then writes(&errout,"?") # 登録済みならエラー表示
    else {
      insert(S_dic,word)                 # setに登録
    # 文字数を keyとしたテーブルに list形式で格納
      if member(T_dic,*word)             # テーブルにあるかチェック
      then  put(T_dic[*word],    word)   # あれば、その listに追加
      else      T_dic[*word] := [word]   # 無ければ、listに入れて登録

    }
  }

  close(dir)                             # ファイルクローズ
  write(&errout)
  write(&errout,"終了:",&clock)
  write(&errout,dic," の読込を終わりました。  ",*S_dic," 語ありました。")

  # 字数毎辞書 書き出し
  write(&errout,"辞書の文字数別分割を始めます。")
  write(&errout,"開始:",&clock)
  every x := key(T_dic) do {
                       # ↓右寄せ桁合わせ関数
    f_out := dic[1] || right(x,2,"0")  # 出力ファイル名: 元のファイル名の
                                       # 先頭1字+字数
                    # ↓書込モードでファイルオープン
    dir := open(f_out,"w") | stop(f_out,"ファイルが開けません。")
    write(&errout,x,"字: ",f_out,": ",*T_dic[x]," 語あります。")
    every write(dir,!T_dic[x])    # x字の list要素を全て書き出し
    close(dir)
  }
  write(&errout,"終了:",&clock)
  write(&errout,"辞書の文字数毎の分割を終わりました。")

end
-----$ DICDIV.ICN ( lines:59 words:187 ) -------------------<cut here

  dicdiv english.dic としますと、次のように分割されます。
-----^ DIR.E ( date:03-08-24 time:12:41 ) ------------------<cut here

 ドライブ D: のボリュームラベルは DATA       
 ボリュームシリアル番号は 112D-12DF
 ディレクトリは D:\2003\Unicon\TS_NW\CROSS\CROSS9

E01                      6  03-08-24  12:28 E01
E02                  1,444  03-08-24  12:28 E02
E03                  9,070  03-08-24  12:28 E03
E04                 35,286  03-08-24  12:28 E04
E05                 87,381  03-08-24  12:28 E05
E06                172,848  03-08-24  12:28 E06
E07                286,461  03-08-24  12:28 E07
E08                386,510  03-08-24  12:28 E08
E09                425,040  03-08-24  12:28 E09
E10                399,012  03-08-24  12:28 E10
E11                329,615  03-08-24  12:28 E11
E12                248,346  03-08-24  12:28 E12
E13                176,520  03-08-24  12:28 E13
E14                122,784  03-08-24  12:28 E14
E15                 79,084  03-08-24  12:28 E15
E16                 49,194  03-08-24  12:28 E16
E17                 29,716  03-08-24  12:28 E17
E18                 17,500  03-08-24  12:28 E18
E19                  9,093  03-08-24  12:28 E19
E20                  3,916  03-08-24  12:28 E20
E21                  1,932  03-08-24  12:28 E21
E22                    912  03-08-24  12:28 E22
E23                    450  03-08-24  12:28 E23
E24                    338  03-08-24  12:28 E24
E25                     54  03-08-24  12:28 E25
E27                     29  03-08-24  12:28 E27
E28                     30  03-08-24  12:28 E28
E29                     31  03-08-24  12:28 E29
E30                     32  03-08-24  12:28 E30
E31                     33  03-08-24  12:28 E31
E32                     34  03-08-24  12:28 E32
ENGLISH  DIC     2,875,008  03-07-21   9:33 ENGLISH.DIC
        32 個          5,747,709 バイトのファイルがあります.
         0 ディレクトリ 1,183,285,248 バイトの空きがあります.
-----$ DIR.E ( lines:39 words:177 ) ------------------------<cut here

 一番長い単語は、32文字で、dichlorodiphenyltrichloroethanes ですが、
なんという意味なんでしょうね。

 辞書参照を、この分割辞書の必要なファイルだけ読み込むように、変更しました。
-----^ DICREFP3.ICN ( date:03-08-24 time:12:48 ) -----------<cut here
####################
# 辞書読込・使用文字種毎の分類をした辞書参照で、曖昧検索対応。
####################
# dicrep3.icn Rev.1.0 2003/08/23 windy 風つかい H.S.
####################
# Usage dicrefp3 英文字列(.はワイルドカード)
#    文字数毎に分割された辞書ファイルを使用。 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("dicrefp3 英単語 ( . はワイルドカード)")
  # 辞書読込
  n_dic := *args[1]                      # 引数の文字数
               # ↓右寄せ桁合わせ関数
  dic := "e" || right(n_dic,2,"0")       # 辞書ファイル名
  dir := open(dic) | stop("その文字数の辞書は見つかりません") 
                                         # 辞書ファイルオープン
  T_dic := table()                       # 辞書格納 table生成
  write(" ",n_dic,"文字用辞書 ",dic," を読込中です。")  # 辞書読み込み
  write("開始:",&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)
  write(" ",n_dic,"文字用辞書 ",dic," の読込を終わりました。",n," 語ありました。")
  write("同一文字で構\成される単語をまとめると、",*T_dic," 種類となります。")
                 # ↑Shift-JISでは、0x5cを含むので、"\"を補完。

  # 辞書参照テスト
  # コマンドラインの引数にて、辞書を参照
  c_word := args[1]
  s_word := deletec(c_word,'.')    # コマンドライン文字列から '.'を削除
  n := *c_word -*s_word            # '.'の数
  n_comb := 0                      # 組合せ数カウンタ 
  n_find := 0                      # 辞書にある件数カウンタ

  write(c_word," の組合せが辞書にあるかチェック中です。")
  write("開始:",&clock)

  every s := mscombd(string(&lcase),n) do { # ワイルドカード対応文字を取り出し
    n_comb +:= 1                   # 組合せ数カウンタ+1
    ss := csort(map(s || s_word))  # コマンドライン引数の '.'以外の部分に足し
                                   # 小文字変換し、ソートして
    if member(T_dic,ss)            # 辞書にあれば
    then {
      n_find +:= 1                 # 辞書にある件数カウンタ+1
      writes(&errout,"!")          # 発見表示
      writes(ss,": ")              # 変換前のデータを書き出し
      every writes(" ",!T_dic[ss]) # 辞書内容を書き出す
      write()
    }
  }

  write(&errout)
  write("終了:",&clock)
  write(n_comb," 通りの組合せのうち、",n_find," 通りが辞書にありました。")

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

# 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
-----$ DICREFP3.ICN ( lines:120 words:401 ) ----------------<cut here

 dicrefp3 mi.nigh. >hhh とすると、次のような結果になります。
 だいぶ、辞書読込時間が短くなりました。
-----^ HHH ( date:03-08-24 time:14:13 ) --------------------<cut here
 8文字用辞書 e08 を読込中です。
開始:14:13:51
終了:14:13:54
 8文字用辞書 e08 の読込を終わりました。38651 語ありました。
同一文字で構成される単語をまとめると、32836 種類となります。
mi.nigh. の組合せが辞書にあるかチェック中です。
開始:14:13:54
acghiimn:  Michigan
cghiimnr:  chirming
cghiimns:  michings
cghiimnt:  mitching
dghiimnt:  midnight
ghiimmns:  shimming
ghiimmnw:  whimming
ghiimnnu:  inhuming
ghiimnst:  smithing
終了:14:13:54
351 通りの組合せのうち、9 通りが辞書にありました。
-----$ HHH ( lines:18 words:36 ) ---------------------------<cut here

風つかい(hshinoh@...)
IconのWWWは、  http://www.cs.arizona.edu/icon/
UniconのWWWは、http://unicon.sourceforge.net/index.html
BGM: Battery's not included / 森山威男&杉本喜代志