TSNETWiki Icon入門講座6 Iconミニ講座 2003/9/26 風つかい --- TS Network への書込から --- (hshinoh@mb.neweb.ne.jp)
■ TSfree > Iconミニ講座(前説) 風つかい
残梅雨お見舞い申し上げます。残暑じゃなくて、梅雨が未だ残っているような 天気ですね。 火星大接近とかいうニュースで夜空を見上げても、雲ばかり。
さて、先日、TS Networkの TSabcで、クロスワードパズルの関係で、面白い題材を 提供して頂いています。 こんなケースです。
・英文字のクロスワードパズルをまず解いて、その後指定の升目の文字を 組み合わせて最終的な単語を見つける。
・しかし、一部判っていない升目がある。
・辞書は、英語の単語がズラッと並んだ形式の辞書ファイルがある。
・サンプル 判らない文字を .で表すと、a.ahviy.r となる。 尚、この場合の正解は、heavy rain という2つの単語の組合せだそう です。
というこことがありまして、Icon版で簡単な支援プログラムを作ってみたのですが お盆休みに、もう少し機能アップできないかと考えてみました。 少し整理して、アップしてみます。
・指定文字の順列組合せの生成 (これは、比較的簡単。TSabcにアップ済み。)
・その順列を分けて、単語候補を生成
heavyrain -> heavy rainと分ける
・辞書の検索(不明文字があるので、曖昧検索が必要)
あたりを考えてみたのですが、 ・曖昧検索は、一挙に処理時間が増えてしまいそうなので、パス。 heavyではなく、h.avyだと、"."は a-zの可能性があるので、一挙に 26倍に なってしまいます。
・文字列を分割して、単語候補を生成する。これはできそう。 これには、まず、5文字の文字列だと、 5文字の単語、4+1文字、3+2文字、3+1+1文字、2+2+1文字、2+1+1+1文字、 1+1+1+1+1文字等の組合せが考えられます。 あまりに多い分割数とか、短い単語はある程度無視して良いかと思います。
・英語辞書は、スペルチェック用のフリーの辞書を探す。 辞書は、最初に一挙に読み込んで、setに登録し、それを使って検索する。
こんなところで、やってみましょう。
Iconのプログラムおよびライブラリーは、次の所から 入手できます。 http://www.cs.arizona.edu/icon/
この講座は、TS Networkの TSfree メーリングリストにポストしたものに加筆・修正
を行ったものです。
Iconは PDSですので、この講座も同じ扱いとします。(転載・編集自由) (This textbook is in the public domain.)
Iconミニ講座 目次 内容 第1回 辞書読込 ファイル読込、set(集合)生成・格納・参照 第2回 組合せ文字列の辞書参照 順列生成(nPm)、set参照 第3回 正整数の分割 生成数の部分和、再帰プログラム 第4回 フィルター generatorとフィルター 第5回 文字列分解・辞書参照 文字列の分解・繰り返し参照 第6回 辞書参照の別方式 table生成・格納・参照 第7回 曖昧参照 組合せ生成(nHm) 第8回 辞書分割 ファイル書込み 第9回 分配組合せ 文字列の組合せ分配 第10回 分割文字で辞書参照 繰り返し参照 おまけ プログラムの整理 link あまり procedure構成 再帰・every・while
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Battery's not included /森山威男&杉本喜代志 (2003/08/20 TSfree0.txt)
■ TSfree > Iconミニ講座1(辞書読込) 風つかい
今回は、辞書の読み込みです。 辞書と言っても、単語がずらっと並んでいる テキストファイルですので、テキストファイルの読込プログラムと変わりはあり ません。 後々、時間が問題に気になりそうなので、現在時刻を書き出すように しました。 &clockは、現在時刻を hh:mm:ss形式で保持している組込キーワード です。 最初、動作モニター用の表示は、エラー出力へ出していましたが、 アップの都合上、標準出力へ出すように修正してあります。
####################
# 辞書読込・辞書参照の習作。
####################
# dicref.icn Rev.1.0 2003/08/19 windy 風つかい H.S.
####################
# Usage dicref 文字列
# english.dicは、スペルチェック用の英単語が順に並んだもの。
# このプログラムのテストでは、DD SOFT SoundMixSpellコンポーネント
# Ver 0.3.0 に 同梱の辞書ファイルを使用。
# This file is in the public domain.
procedure main()
# 辞書読込 dic := "english.dic" # 辞書ファイル名 dir := open(dic) | stop(dic," が見つかりません") # 辞書ファイルオープン S_dic := set() # 辞書格納 set生成
# write(&errout,dic," を読込中です。") # 辞書読み込み
write(dic," を読込中です。") # 辞書読み込み
# write(&errout,"開始:",&clock)
write("開始:",&clock) n := 0 # 辞書行数カウンタ
while word := read(dir) do { # 辞書を1行ずつ読み込んで、 insert(S_dic,word) # setに登録 n +:= 1 if n % 1000 = 0 then writes(&errout,"*") # 読み込み状況表示 }
close(dir) # 辞書ファイルクローズ write(&errout)
# write(&errout,"終了:",&clock)
write("終了:",&clock)
# write(&errout,dic," の読込を終わりました。\n",*S_dic," 語ありました。")
write(dic," の読込を終わりました。\n",*S_dic," 語ありました。")
# 辞書参照テスト L := ["heavy","rain","yveah","niar"] # テストデータ
# write(&errout,"参照テストを開始します。")
write("参照テストを開始します。")
# write(&errout,"開始:",&clock)
write("開始:",&clock) every s := !L do { # テストデータを順次読み出し writes(s) if member(S_dic,s) then write(": OK") # 辞書にあるかチェック else write(": NG") }
# write(&errout,"終了:",&clock)
write("終了:",&clock)
# write(&errout,"参照テストを終わりました。")
write("参照テストを終わりました。")
end
dicref >aaa としますと、こんな結果になります。
english.dic を読込中です。 開始:23:35:43 終了:23:35:51 english.dic の読込を終わりました。 257650 語ありました。 参照テストを開始します。 開始:23:35:51 heavy: OK rain: OK yveah: NG niar: NG 終了:23:35:51 参照テストを終わりました。
私のPCでは、辞書読込・setへの格納に、8秒ほどかかっています。 CPU Celeronで、クロック 733M、メモリーは多分32M、MS-DOS版 の Iconを、Windows-MEのDOS窓で動作させています。
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Battery's not included /森山威男&杉本喜代志 (2003/08/20 TSfree1.txt)
■ TSfree > Iconミニ講座2(組合せ文字列の辞書参照) 風つかい
今回は、 ・コマンドラインから入力した文字列の順列組合わせを作成 ・その組合せ文字が辞書にあるか参照し、あれば出力する 処理を行います。 前回の辞書読込の辞書参照の部分に、コマンドライン入力の文字列の順列組み 合わせを適用する構成となっています。
####################
# 辞書読込・文字列順列の辞書参照
####################
# dicrefp.icn Rev.1.0 2003/08/20 windy 風つかい H.S.
####################
# Usage dicrefp 文字列
# 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("dicrefp 英単語") # 辞書読込 dic := "english.dic" # 辞書ファイル名 dir := open(dic) | stop(dic," が見つかりません") # 辞書ファイルオープン S_dic := set() # 辞書格納 set生成
# write(&errout,dic," を読込中です。") # 辞書読み込み
write(dic," を読込中です。") # 辞書読み込み
# write(&errout,"開始:",&clock)
write("開始:",&clock) n_line := 0 # 辞書行数カウンタ
while word := read(dir) do { # 辞書を1行ずつ読み込んで、 insert(S_dic,word) # setに登録 n_line +:= 1 if n_line % 1000 = 0 then writes(&errout,"*") # 読み込み状況表示 }
close(dir) # 辞書ファイルクローズ write(&errout)
# write(&errout,"終了:",&clock)
write("終了:",&clock)
# write(&errout,dic," の読込を終わりました。\n",*S_dic," 語ありました。")
write(dic," の読込を終わりました。\n",*S_dic," 語ありました。")
# コマンドラインの引数の順列を生成し、辞書を参照 c_word := args[1]
# write(&errout,c_word," の組合せが辞書にあるかチェック中です。")
write(c_word," の組合せが辞書にあるかチェック中です。")
# write(&errout,"開始:",&clock)
write("開始:",&clock) n_comb := 0 # 組合せ数カウンタ n_find := 0 # 辞書にある件数カウンタ
every word := exsperm(c_word) do { # 順列 generatorから文字を取り出し n_comb +:= 1 # カウンタ+1 if n_comb % 1000 = 0 then writes(&errout,"*") # チェック状況表示 if member(S_dic,word) then { # 組合せ文字列が辞書にあれば、 n_find +:= 1 # カウンタ+1 writes(&errout,"!") # 発見表示 write(word) # 組合せ文字列出力 } }
write(&errout)
# write(&errout,"終了:",&clock)
write("終了:",&clock)
# write(&errout,n_comb," 通りの組合せのうち、",n_find," 通りが辞書にありました。")
write(n_comb," 通りの組合せのうち、",n_find," 通りが辞書にありました。")
end
# 以下は、Icon入門講座から引用
####################
# 英文字の組み合わせ(同一文字指定対応)
####################
# 名称変更 expermute -> exsperm
# arg [1]: s string
# value: string
# Usage: every ss := exsperm(s) do ...
# Icon入門講座2(18) procedure exsperm(s) # string permutations
if *s = 0 then return "" # ss := csort(s) # 文字列をソートする。(strings.icn) suspend ss[i := new_pos(ss)] || exsperm(ss[1:i] || ss[i+1:0]) # ↑同一文字はスキップする
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
####################
# 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から !で要素を取り出す時には、アルファベット順に取り出せる。
dicrefp sunrise >bbb としますと、こんな結果になります。
english.dic を読込中です。 開始:19:18:22 終了:19:18:30 english.dic の読込を終わりました。 257650 語ありました。 sunrise の組合せが辞書にあるかチェック中です。 開始:19:18:30 insures sunrise 終了:19:18:30 2520 通りの組合せのうち、2 通りが辞書にありました。
いくつかの単語で試してみましたが、短い単語ですと並べ替えで結構他の単語に なりますが、長い単語だと並べ替えてもうまくは他の単語にはならないみたいです。
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: ふたりのビッグショー ~Talk百歌~ vol.7 /篠原美也子&奥井亜紀 (2003/08/20 TSfree2.txt)
■ TSfree > Iconミニ講座3(正整数の分割) 風つかい
今回は、正整数の部分和への分解です。これは、5文字の文字列があるとして、 例えば、3文字と2文字の2つの単語として、辞書を参照するためのものです。 5の分解でも、5(分解しない)、4+1、3+2、3+1+1、...と いって、最後は1+1+1+1+1と、かなりのパターンが発生します。 こういう面倒な繰り返しが必要な処理は、再帰処理を行うとプログラムが楽に なることが多いみたいですね。
処理は、5の例ですと、5から順次 5、4、3、2、1と引き算していく やり方にしています。
最初は、5から5を引くケースで、 余りは0です。 結果は[5]を返します。
次は、5から4を引くケースで、 余りは1です。余りがある場合は、中間結果として[4]を保持して、 自分自身を更に呼んで(再帰)、余りの1を与えて、結果をもらいます。 その結果として[1]が返って来ます。中間結果の[4]に[1]を追加して [4,1]を結果として返します。
次は、5から3を引くケースで、 1回目の動作 中間結果1 [3] 余り 2 ・・・余り処理のため再帰 2回目の動作 中間結果1 [2] 余り 0 2 [1] 余り 1 ・・・余り処理のため再帰 3回目の動作 結果1 [1] という動作を経ますので、結果は[3,2]と、[3,1,1]の2つが返り ます。
こんな風な動作となります。
####################
# 正数分割
####################
# n_div.icn Rev.1.0 2003/08/20 windy 風つかい H.S.
####################
# Usage n_div 正数
# 与えられた数字を部分和に分解する。
# 例:5->[5],[4,1],[3,2],[3,1,1],[2,2,1],[2,1,1,1],[1,1,1,1,1]
# This file is in the public domain.
procedure main(args)
Usage := "n_div 正数" if *args < 1 then stop(Usage) # コマンドライン引数が無ければ Usage表示 if args[1] < 1 then stop(Usage) # 正数でなければ Usage表示
# ↓正数分割 generatorから順次結果を取り出し every L := n_div(args[1]) do show_sl(L) # ↑listの内容を表示する
end
####################
# 正数分割 generator
####################
# arg [1]: 分割される数(正数)(再帰の場合は、余り)
# value : 分解結果の数 listに格納
# Usage : every L := n_div(r) do ... procedure n_div(r)
if r < 1 then fail # 念のため if r = 1 then return [1] # 再帰終了
# r >= 2 の場合 every i := r to 1 by -1 do { # r から順に -1しながら rr := r -i # 元の数から引き算していく if rr = 0 then suspend [i] # 余りが 0なら結果を返す。 else suspend [i] ||| n_div(rr) # ↑余りがでたら、再帰してその結果を listの末尾 # に追加。 ||| は listの要素の追加演算子 } # suspend は、複数の結果を返す return
end
# 以下は、Icon入門講座より
####################
# listの 内容表示
####################
# arg : list
# value : null
# Usage : show_sl(L)
# 最終的に stringか numberが要素であること
# Icon入門講座(13),Icon入門講座3(15)
procedure show_sl(list)
# listの内容表示(test/表示用)
every writes(" ",!list) write() return
end
n_div 5 >ccc としますと、こんな結果になります。
5 4 1 3 2 3 1 1 2 3 2 2 1 2 1 2 2 1 1 1 1 4 1 3 1 1 2 2 1 2 1 1 1 1 3 1 1 2 1 1 1 1 2 1 1 1 1 1
以上の結果を良く見ますと、5=4+1と1+4や 3+2と2+3の両方が 現れています。 辞書検索は、どちらか一方を行えば、残りは入れ替えるだけで 人が判断できると思います。 ということで、片方だけにするために、結果は、 降順しか許さないという条件を付けましょう。 ということで、再帰の際に制限 を付けました。
####################
# 正数分割
####################
# n_divd.icn Rev.1.0 2003/08/20 windy 風つかい H.S.
####################
# Usage n_divd 正数
# 与えられた数字を部分和に分解する。結果は降順の分解のみ。
# 例:5->[5],[4,1],[3,2],[3,1,1],[2,2,1],[2,1,1,1],[1,1,1,1,1]
# This file is in the public domain.
procedure main(args)
Usage := "n_divd 正数" if *args < 1 then stop(Usage) # コマンドライン引数が無ければ Usage表示 if args[1] < 1 then stop(Usage) # 正数でなければ Usage表示
# ↓正数分割 generatorから順次結果を取り出し every L := n_divd(args[1]) do show_sl(L) # ↑listの内容を表示する
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
# 以下は、Icon入門講座より
####################
# listの 内容表示
####################
# arg : list
# value : null
# Usage : show_sl(L)
# 最終的に stringか numberが要素であること
# Icon入門講座(13),Icon入門講座3(15)
procedure show_sl(list)
# listの内容表示(test/表示用)
every writes(" ",!list) write() return
end
n_divd 5 >ddd とすると結果は、こうなります。 だいぶパターンが減り ました。 それでも、未だ随分ありますので、分割数や最小数に制限をつけたい と思います。それは、次回に。
5 4 1 3 2 3 1 1 2 2 1 2 1 1 1 1 1 1 1 1
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Battery's not included / 森山威男&杉本喜代志 (2003/08/20 TSfree3.txt)
■ TSfree > Iconミニ講座4(フィルター) 風つかい
今回は、正整数の部分和への分解で、結構な数のパターンが生成されますので、 これに制限を掛けます。 分割数の制限と最小数の制限を付けられるようにします。
前回の n_divd.icn に制限を付ける処理を追加しても良いのですが、降順のみの 制限を付けるだけで結構面倒したので、更に修正する気は起きません。 そこで、n_divd.icnはそのままにして、n_divdを呼ぶ方で制限を付けようと思い ます。 あるプログラムの処理結果を、別のプログラムが加工して、更に別のプログラムに 渡すことを、フィルターと言いますので、この procedureも、フィルターと言って 良いと思います。 こんな格好の構成です。
+----------------+ +--------------+ +--------------+ | n_divd | | n_divdf | | main | | 正整数分割生成 | ---> | 分割数制限 | ---> | 結果を使用 | | | | 最小数制限 | | | | | | フィルター | | | | generator | | generator | | | +----------------+ +--------------+ +--------------+
####################
# 正整数分割 分割数・最小数制限付き
####################
# n_divdf.icn Rev.1.0 2003/08/20 windy 風つかい H.S.
####################
# Usage n_divdf 正整数 最大分割数 最小数
# 与えられた数字を部分和に分解する。結果は降順の分解のみ。
# 例:5->[5],[4,1],[3,2],[3,1,1],[2,2,1],[2,1,1,1],[1,1,1,1,1]
# この中で、最大分割数、最小数の制限に合うものを出力する。
# This file is in the public domain.
procedure main(args)
Usage := "n_divd 正整数 最大分割数 最小数" if *args < 1 then stop(Usage) # コマンドライン引数が無ければ Usage表示 if args[1] < 1 then stop(Usage) # 正数でなければ Usage表示 n := args[1] # ↓defaultはなるべく生成パターンが少なくなるよう ndiv := \args[2] | 1 # defaultの最大分割数は、1(分割せず) nmin := \args[3] | *n # defaultの最小数は、分割される数そのもの
write(n," を、最大分割数 ",ndiv,"、最小数 ",nmin," にて分割")
# ↓正整数分割 generatorから順次結果を取り出し every L := n_divdf(n,ndiv,nmin) do show_sl(L) # ↑listの内容を表示する
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
# 以下は、Icon入門講座より
####################
# listの 内容表示
####################
# arg : list
# value : null
# Usage : show_sl(L)
# 最終的に stringか numberが要素であること
# Icon入門講座(13),Icon入門講座3(15)
procedure show_sl(list)
# listの内容表示(test/表示用)
every writes(" ",!list) write() return
end
n_divdf 9 3 3 >eee とすると、結果はこうなります。 相当、制限が付けられ ますね。
9 を、最大分割数 3、最小数 3 にて分割
9 6 3 5 4 3 3 3
さて、次回は、まとめです。
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Battery's not included / 森山威男&杉本喜代志 (2003/08/21 TSfree4.txt)
■ TSfree > Iconミニ講座座5(文字列分解・辞書参照) 風つかい
今日は、当地では、午前中は日が差していましたが、午後に雲が広がって曇空に。 暑さ寒さに弱い私には、涼しくて好都合なのですが。
さて今回は、今までのまとめで、文字列を分解したもので辞書を参照する処理 をやります。 プログラムは、辞書参照の所で、文字列分解パターン(切り出す 文字列の長さ)を取り出してきて、それに従い順に部分文字列で辞書を参照します。
move()という関数出てきますが、これは文字列に対するポインターを動かすもの です。 "abcde" ? write(move(2)) とすると、"abcde"を走査対象文字列として、 最初はポインターは先頭の "a"の手前にあります。 move(2)で、ポインターは2文字分進んで、"b"と"c"の中間に来ます。 move(2)の値は、その進んだ2文字分の間の文字列 すなわち "ab"と なります。 write(move(2))で、"ab"の書き出しができます。 もう一度、write(move(2))とすると、"cd"が書き出せます。 このように、文字列から順に指定長の部分文字列を取り出すことができます。
####################
# 辞書読込・文字列順列生成/分割・辞書参照
####################
# dicrefpd.icn Rev.1.0 2003/08/21 windy 風つかい H.S.
####################
# Usage dicrefpd 英文字列 最大分割数 最小文字長
# english.dicは、スペルチェック用の英単語が順に並んだもの。
# このプログラムのテストでは、DD SOFT SoundMixSpellコンポーネント
# Ver 0.3.0 に 同梱の辞書ファイルを使用。
# 5文字の文字列を、5=2+2+1に分割した時に 2のところで、ダブリが生じる。
# 例えば、"ab"、"cd"、"e"が辞書にあれば ab cd e と cd ab eが出力される。
# This file is in the public domain.
procedure main(args)
# コマンドライン引数チェック。無ければ Usage表示 if *args < 1 then stop("dicrefpd 文字列 最大分割数 最小文字長") # 辞書読込 dic := "english.dic" # 辞書ファイル名 dir := open(dic) | stop(dic," が見つかりません") # 辞書ファイルオープン S_dic := set() # 辞書格納 set生成
# write(&errout,dic," を読込中です。") # 辞書読み込み
write(dic," を読込中です。") # 辞書読み込み
# write(&errout,"開始:",&clock)
write("開始:",&clock) n_line := 0 # 辞書行数カウンタ
while word := read(dir) do { # 辞書を1行ずつ読み込んで、 insert(S_dic,word) # setに登録 n_line +:= 1 if n_line % 1000 = 0 then writes(&errout,"*") # 読み込み状況表示 }
close(dir) # 辞書ファイルクローズ write(&errout)
# write(&errout,"終了:",&clock)
write("終了:",&clock)
# write(&errout,dic," の読込を終わりました。\n",*S_dic," 語ありました。")
write(dic," の読込を終わりました。\n",*S_dic," 語ありました。")
# コマンドラインの引数の順列を生成し、辞書を参照 c_word := args[1] ndiv := \args[2] | 1 # 分割数指定無しは、1(分割せず) nmin := \args[3] | *c_word # 最小文字長指定無しは、引数文字列長 n_comb := 0 # 組合せ数カウンタ n_find := 0 # 辞書にある件数カウンタ L_pat := [] # 文字列分割パターン格納 list every put(L_pat,n_divdf(*c_word,ndiv,nmin)) # 分割パターン格納
# write(&errout,"\n",c_word," を、最大分割数 ",ndiv,"、最小文字長 ",nmin,
# " にて分割して、その全てが辞書にあるかチェック中。")
write("\n",c_word," を、最大分割数 ",ndiv,"、最小文字長 ",nmin, " にて分割して、その全てが辞書にあるかチェック中。")
# write(&errout,"開始:",&clock)
write("開始:",&clock)
# ↓コマンドライン文字列の組合せを順次取り出し every s := exsperm(c_word) do { every L := !L_pat do { # 文字列を細分するデータを取り出して ERR := &null # 辞書参照エラーフラッグリセット ss := "" # 細分後の文字列格納エリア s ? { # sを走査対象として、 every (length := !L ) & /ERR do { # 細分文字数を取り出して、 # ↑既に辞書参照エラーが発生していなければ、 n_comb +:= 1 # チェック回数カウンタ+1 if n_comb % 1000 = 0 then writes(&errout,"*") # チェック状況表示 sss := move(length) # 細分文字列の取り出し # 細分文字が辞書に存在するかチェック if member(S_dic,sss) then ss ||:= (sss || " ") # データ足し込み else ERR := "ERR" # 参照エラーフラッグセット } # 細分文字列が全て辞書にあれば、書き出し if /ERR then { # エラーフラッグが立っていなければ n_find +:= 1 # カウンタ+1 write(s," -> ",ss) # 組合せ文字列出力 writes(&errout,"!") # 合致表示 } } } }
write(&errout)
# write(&errout,"終了:",&clock)
write("終了:",&clock)
# write(&errout,n_comb," 通りの組合せのうち、",n_find," 通りが辞書にありました。")
write(n_comb," 通りの組合せのうち、",n_find," 通りが辞書にありました。")
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
# 以下は、Icon入門講座から引用
####################
# 英文字の組み合わせ(同一文字指定対応)
####################
# 名称変更 expermute -> exsperm
# arg [1]: s string
# value: string
# Usage: every ss := exsperm(s) do ...
# Icon入門講座2(18) procedure exsperm(s) # string permutations
if *s = 0 then return "" # ss := csort(s) # 文字列をソートする。(strings.icn) suspend ss[i := new_pos(ss)] || exsperm(ss[1:i] || ss[i+1:0]) # ↑同一文字はスキップする
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
####################
# 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から !で要素を取り出す時には、アルファベット順に取り出せる。
dicrefpd sunburn 2 3 >e723 としますと、結果はこうなります。 知らない単語が沢山でてきます。
english.dic を読込中です。 開始:19:15:50 終了:19:15:59 english.dic の読込を終わりました。 257650 語ありました。
sunburn を、最大分割数 2、最小文字長 3 にて分割して、その全てが辞書にあるかチェック中。 開始:19:15:59 bunsnur -> buns nur bunsrun -> buns run bunsurn -> buns urn burnnus -> burn nus burnsun -> burn sun bursnun -> burs nun nubsnur -> nubs nur nubsrun -> nubs run nubsurn -> nubs urn nunsbur -> nuns bur nunsrub -> nuns rub nunsurb -> nuns urb nursbun -> nurs bun nursnub -> nurs nub rubsnun -> rubs nun runsbun -> runs bun runsnub -> runs nub snubnur -> snub nur snubrun -> snub run snuburn -> snub urn sunburn -> sunburn sunnbur -> sunn bur sunnrub -> sunn rub sunnurb -> sunn urb urbsnun -> urbs nun urnsbun -> urns bun urnsnub -> urns nub 終了:19:15:59 2592 通りの組合せのうち、27 通りが辞書にありました。
dicrefpd midnight 2 3 >e823 としますと、結果はこうなります。
english.dic を読込中です。 開始:19:16:14 終了:19:16:22 english.dic の読込を終わりました。 257650 語ありました。
midnight を、最大分割数 2、最小文字長 3 にて分割して、その全てが辞書にあるかチェック中。 開始:19:16:23 dightmin -> dight min dightnim -> dight nim midnight -> midnight mightdin -> might din mightnid -> might nid mindthig -> mind thig nightdim -> night dim nightmid -> night mid thigmind -> thig mind thingdim -> thing dim thingmid -> thing mid 終了:19:16:26 60954 通りの組合せのうち、11 通りが辞書にありました。
8文字を4文字+4文字に分けた場合に、同じ組合せが2度出てきます。 mindthig -> mind thig と thigmind -> thig mind のところです。 今回は、とりあえず見なかったことに。(汗)
dicrefpd heavyrain 2 4 >e923 としますと、結果はこうなります。
english.dic を読込中です。 開始:19:16:40 終了:19:16:48 english.dic の読込を終わりました。 257650 語ありました。
heavyrain を、最大分割数 2、最小文字長 3 にて分割して、その全てが辞書にあるかチェック中。 開始:19:16:48 aviaryhen -> aviary hen hairynave -> hairy nave hairyvane -> hairy vane hairyvena -> hairy vena havenairy -> haven airy haverayin -> haver ayin hayervain -> hayer vain hayervina -> hayer vina heavyairn -> heavy airn heavyrain -> heavy rain heavyrani -> heavy rani hieranavy -> hiera navy hyenariva -> hyena riva hyenavair -> hyena vair invaryeah -> invar yeah naiverhay -> naiver hay naiveryah -> naiver yah navierhay -> navier hay navieryah -> navier yah nerviayah -> nervi ayah rainyhave -> rainy have ravenhiya -> raven hiya ravinehay -> ravine hay ravineyah -> ravine yah ravinyeah -> ravin yeah rayahnevi -> rayah nevi rayahvein -> rayah vein rayahvine -> rayah vine rivenayah -> riven ayah vahineray -> vahine ray vahinerya -> vahine rya vahineyar -> vahine yar vainerhay -> vainer hay vaineryah -> vainer yah veinyhaar -> veiny haar viharanye -> vihara nye viharayen -> vihara yen vinerayah -> viner ayah vineryaah -> vinery aah vineryaha -> vinery aha 終了:19:17:21 545145 通りの組合せのうち、40 通りが辞書にありました。
この場合、545145 通りで、33秒かかっています。 これに1文字の曖昧検索が できるようににすると、単純に作ると 26倍かかるプログラムになりそうな気が します。 2文字の曖昧検索にすると、更にその26倍? 曖昧検索を入れるのでしたら、検索方式を根本的に考え直した方が良さそうです。 heavyrainの例ですと、'a'が2個、'ehinrvy'の各文字が1個ずつの単語もしくは 単語の組合せが辞書にあるか? という問題で、解法は色々あると思います。
9文字の文字列で54万通りというと、辞書単語数26万語を越えていますので、 これ以上の文字数を対象とするには、方式を考え直した方が良いだろうと思います。 今のプログラムの工夫でも速くなるとは思いますが。 あるいは、マシンを速い物 にしたりメモリー増設するという手もありますが、先立つものが...
しばらく、Iconを動かしていませんでしたが、面白い題材を提供して頂いたお陰で、 多少 Iconのリハビリができました。ありがとうございます。
Iconは、この題材のような文字列をいじりまわす処理のために作られた言語です。 ご興味をお持ちになった方がいらっしゃれば、うれしいです。
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Battery's not included / 森山威男&杉本喜代志 (2003/08/21 TSfree5.txt)
■ TSfree > Iconミニ講座6(辞書参照の別方式) 風つかい
前回で、ミニ講座は終わったつもりでいたのですが、辞書参照がどうもスッキリ しませんので、ツラツラ考えていました。(シツコイ!)
特定文字を使った単語をスバヤク検索するのだったら、 ・スバヤイ検索には、setか tableを使うのが良い。 ・同一文字を使った文字列に、何か共通のインデックスを付けて、テーブルに
登録しておけば、スバヤク検索できる。 ということで、
・辞書の単語の文字列をソートしたもので、インデックスを作っておいて、 同一文字を使用した単語をまとめて、テーブルに登録しておき、 ・検索したい文字列をソートしたもので、テーブルを参照すれば、 ・一回の参照で、候補の単語群が見つかる。 と、気が付きました。
例えば、"abcd"と "cdab"と "dcab"という単語があったとして、皆ソートすると、 "abcd"となります。 ソートした "abcd"を インデックス(Iconでは keyと言い ます。)として、値に list形式で、["abcd","cdab","dcab"]と格納しておけば、 "abcd"で参照すれば、文字の配列を入れ替えた場合の候補が、一挙に出てきます。 この方式ですと、不明文字を、1~2文字入れて 26倍とか、26^2倍程度に処理が 増えても、ガマンできる時間で処理ができそうです。
↓ソート 辞書 "abcd" ----> テーブル
"cdab" ----> key: "abcd" ->value: ["abcd","cdab","dcab"] "dcab" ----> ↑ |参照
検索文字 --->ソート---------
という考えで、辞書読込・参照のプログラムを修正してみました。 辞書の登録文字にダブリがあります。setに登録する場合は問題は起きないのですが テーブルに登録する時に valueでダブルといけないので、チェックを入れてあります。 また keyは、小文字に統一して処理するようにしました。
####################
# 辞書読込・使用文字種毎の分類をした辞書参照の習作。
####################
# dicref2.icn Rev.1.0 2003/08/23 windy 風つかい H.S.
####################
# Usage dicref2
# english.dicは、スペルチェック用の英単語が順に並んだもの。
# このプログラムのテストでは、DD SOFT SoundMixSpellコンポーネント
# Ver 0.3.0 に 同梱の辞書ファイルを使用。
# This file is in the public domain.
procedure main()
# 辞書読込 dic := "english.dic" # 辞書ファイル名 dir := open(dic) | stop(dic," が見つかりません") # 辞書ファイルオープン S_dic := set() # 辞書ダブリチェック用 set生成 T_dic := table() # 辞書格納 table生成 write(dic," を読込中です。") # 辞書読み込み write("開始:",&clock) n := 0 # 辞書行数カウンタ
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に登録 # 単語を小文字変換しソートしたものをインデックスにして格納 # 同一文字を含む単語は同じインデックスに 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(dic," の読込を終わりました。 ",*S_dic," 語ありました。") write("同一文字で構\成される単語をまとめると、",*T_dic," 種類となります。") # ↑Shift-JISでは、0x5cを含むので、"\"を補完。 # 辞書参照テスト L := ["heavy","rain","yveah","niar","noword"] # テストデータ write("参照テストを開始します。") write("開始:",&clock) every s := !L do { # テストデータを順次読み出し ss := csort(map(s)) # テストデータを、小文字変換し、ソートして writes(s,": ") # 変換前のデータを書き出し if member(T_dic,ss) # 辞書にあれば then { every writes(" ",!T_dic[ss]) # 辞書内容を書き出す write() } else write("辞書にありません。") }
write("終了:",&clock) write("参照テストを終わりました。")
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から !で要素を取り出す時には、アルファベット順に取り出せる。
dicref2 >fff とすると、こういう結果になります。 辞書読込の際に、加工を していますので、読込時間が 42秒に増えています。
english.dic を読込中です。 開始:11:12:25 終了:11:13:07 english.dic の読込を終わりました。 257650 語ありました。 同一文字で構成される単語をまとめると、223704 種類となります。 参照テストを開始します。 開始:11:13:07 heavy: heavy Yahve rain: airn Arni Iran rain rani yveah: heavy Yahve niar: airn Arni Iran rain rani noword: 辞書にありません。 終了:11:13:07 参照テストを終わりました。
このプログラムでは、tableの値に listを入れていますが、Iconでは、tableの値に
tableとか、listの値に tableや listとか、割と複雑なデータ構造を比較的簡単に
実現できます。
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Battery's not included / 森山威男&杉本喜代志 (2003/08/23 TSfree6.txt)
■ TSfree > Iconミニ講座7(曖昧参照) 風つかい
やめられない・止まらないエビセン体質のため、ついついミニ講座の続きを考えて しまいます。 曖昧検索を入れてみました。 コマンドラインから、英文文字列を指定して辞書検索を行いますが、'.'をワイルド カード (a-z)と見なせるようにしてみました。 '.'が2文字以上の場合は、"ab"と "ba"のようなものは、別に検索しないように 細工を入れました。 &lcaseは、英小文字に対応する組込キーワードです。
####################
# 辞書読込・使用文字種毎の分類をした辞書参照で、曖昧検索対応。
####################
# dicrep2.icn Rev.1.0 2003/08/23 windy 風つかい H.S.
####################
# Usage dicrefp2 英文字列(.はワイルドカード)
# 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("dicrefp2 英単語 ( . はワイルドカード)") # 辞書読込 dic := "english.dic" # 辞書ファイル名 dir := open(dic) | stop(dic," が見つかりません") # 辞書ファイルオープン S_dic := set() # 辞書ダブリチェック用 set生成 T_dic := table() # 辞書格納 table生成 write(dic," を読込中です。") # 辞書読み込み write("開始:",&clock) n := 0 # 辞書行数カウンタ
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に登録 # 単語を小文字変換しソートしたものをインデックスにして格納 # 同一文字を含む単語は同じインデックスに 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(dic," の読込を終わりました。 ",*S_dic," 語ありました。") 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
dicrefp2 sunris. >ggg と、ワイルドカードを 1ついれた場合は こんな結果になります。
english.dic を読込中です。 開始:21:51:57 終了:21:52:40 english.dic の読込を終わりました。 257650 語ありました。 同一文字で構成される単語をまとめると、223704 種類となります。 sunris. の組合せが辞書にあるかチェック中です。 開始:21:52:40 ainrssu: Russian Surnias dinrssu: sundris einrssu: insures Serinus sunrise 終了:21:52:40 26 通りの組合せのうち、3 通りが辞書にありました。
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: 日本組曲 / 有馬徹とノーチェ・クバーナ with 杉本喜代志 (2003/08/23 TSfree7.txt)
■ TSfree > Iconミニ講座8(辞書分割) 風つかい
辞書にインデックスを付けて格納するようにしたせいで、辞書読込が40秒以上に なってしまいました。 流石に、待ち時間がツライので、辞書から必要な字数の部分だけ、読み込むように しました。
辞書を、まず文字数毎のファイルに分割します。 辞書を、単語の文字数毎に、テーブルに読込みます。次に文字数毎のファイルに出力 します。 辞書のダブリチェックも入れて、出力ファイルはダブリを無くします。
english.dic T_dic +----------+ +-----------------------------+ |辞書 | |テーブル | |ファイル |--->|key:1 ->value:[a,i] |---> ファイル e01へ書き出し | | |key:2 ->value:[AA,Ab,AB,...] |---> ファイル e02へ書き出し +----------+ | | +-----------------------------+
####################
# 辞書を文字数毎に分割
####################
# 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 english.dic としますと、次のように分割されます。
ドライブ 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 バイトの空きがあります.
一番長い単語は、32文字で、dichlorodiphenyltrichloroethanes ですが、 なんという意味なんでしょうね。
辞書参照を、この分割辞書の必要なファイルだけ読み込むように、変更しました。
####################
# 辞書読込・使用文字種毎の分類をした辞書参照で、曖昧検索対応。
####################
# 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
(追記 mscombdという命名は間違っていますね。最後の dは降順という意味なの ですが、ご覧の通り、ソートを一切かけていませんので、降順にはなりません。 この procedureに対する名前は、mscombが正しいのです。)
dicrefp3 mi.nigh. >hhh とすると、次のような結果になります。 だいぶ、辞書読込時間が短くなりました。
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 通りが辞書にありました。
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Battery's not included / 森山威男&杉本喜代志 (2003/08/24 TSfree8.txt)
■ TSfree > Iconミニ講座9(分配組合せ) 風つかい
文字列で、辞書参照をするときに、文字の組合せを作ろうと思うのですが、例えば 4文字 "abcd" とあって、組合せのプログラムを書くと例えば、こうなります。
####################
# 文字列分配プログラムの習作1
####################
# pcomb1.icn Rev.1.0 2003/08/25 windy 風つかい H.S.
####################
# This file is in the public domain.
procedure main()
L := [[4],[3,1],[2,2],[2,1,1],[1,1,1,1]] # 文字列分配パターン(文字数) s := "abcd" # テスト文字列
write(s) every LL := !L do { # 分配文字数パターンから順次取り出して、 write("---") # 表示用 every LLL := pcomb(s,LL) do { # 分配結果を順次取り出して、 writes(" ->") # 表示用 every writes(" ",!LLL) # 出力する。 write() } }
end
####################
# 分配の組合せ
####################
# arg [1]: string 分配する英文字列:分配パターンのトータルに合ってること。
# [2]: list 分配パターン(降順の list)
# value : list 分割された文字列
# Usage : every LL := pcomb(s,L) do ..
# ("abc",[2,1]) -> ["ab","c"],["ac","b"],["bc","a"]
# 同一分配数が複数ある場合は、無駄が生じる。
# 例:("abcd",[2,2])の場合に、["ab","cd"] と ["cd","ab"]の両方が出る。 procedure pcomb(s,L)
if *L = 1 then return [s] # 再帰終了 if L[1] = 1 then { # 後は、順に1文字ずつ listに入れるだけ return [s[1]] ||| pcomb(s[2:0], L[2:0]) # } # ↑先頭文字 ↑残り文字列 ↑残りのリスト # 2文字以上の指定ならば、 every ss := scomb(s,L[1]) do { # 文字の組合せを作って suspend [ss] ||| pcomb(ssub(s,ss),L[2:0]) # 残りの文字のため再帰 } # ↑組合せ文字 ↑残り文字列 ↑残りのリスト
end
####################
# 文字列の引き算
####################
# arg [1]: string 被削除文字列
# [2]: string 削除文字列
# value : string 結果文字列
# 文字列 s1の先頭から、文字列 s2の文字を削除。1:1で削除。
# ("abcabc","abd") -> "cabc" 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
####################
# 1998/04/16 windy 名称変更 comb -> scomb (BIPLとダブルので)
# stringの組み合わせ
# nCm (n = *s) n個のものから、m個を選ぶ。
# arg: [1]: s: string
# [2]: m: integer
# value: string
# Usage: every ss := scomb(s,m) do ...
# Icon入門講座3(11)
procedure scomb(s,m)
/m := *s # デフォルト nCn (n = m = *s) if m = 0 then return "" # mを文字数カウンターに使う。 # 0なったら、そこで打ち止め。 suspend s[i := 1 to *s] || scomb(s[i+1 : 0], m -1) #↑ 1文字選ぶ ↑ ↑←前で選んだ文字以降 ↑指定文字数に # | の文字列に対し同じ 文字列を抑え # | 処理を行う。 るためのカウ # 文字列の連結 ンター
end
pcomb1 > iii とすると、こうなります。
abcd
-> abcd
-> abc d -> abd c -> acd b -> bcd a
-> ab cd -> ac bd -> ad bc -> bc ad -> bd ac -> cd ab
-> ab c d -> ac b d -> ad b c -> bc a d -> bd a c -> cd a b
-> a b c d
結果を見てみますと、同じ文字数を連続して取り出す(2文字を2回)時に、 ["ab","cd"] と ["cd","ab"]が、両方でてきます。 これは、片方だけで良いのです。 よく見ると、不要なケースは、結果の 並びが、降順になっていないケースみたいです。 そこで、降順の組合わせだけ 取り出すように、修正してみました。 文字列の比較式で、<< という記号が出てきますが、これは文字列が辞書順で 先か後かの比較を行うものです。 文字列組合せの scombが、同一文字指定に対応していませんでしたので、対応する exscombを作りました。
####################
# 文字列分配プログラムの習作2
####################
# pcomb2.icn Rev.1.0 2003/08/25 windy 風つかい H.S.
####################
# This file is in the public domain.
procedure main()
L := [[4],[3,1],[2,2],[2,1,1],[1,1,1,1]] # 文字列分配パターン(文字数) s := "abcd" # テスト文字列
write(s) every LL := !L do { # 分配文字数パターンから順次取り出して、 write("---") # 表示用 every LLL := expcomb(s,LL) do { # 分配結果を順次取り出して、 writes(" ->") # 表示用 every writes(" ",!LLL) # 出力する。 write() } }
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
####################
# 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から !で要素を取り出す時には、アルファベット順に取り出せる。
pcomb2 > jjj とするとこうなります。2文字を2回取り出す所での重複がなく なりました。 なんとか、ケースを減らせたみたいです。
abcd
-> abcd
-> abc d -> abd c -> acd b -> bcd a
-> ab cd -> ac bd -> ad bc
-> ab c d -> ac b d -> ad b c -> bc a d -> bd a c -> cd a b
-> a b c d
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: 蝉の声 / 近所の蝉たち (2003/08/25 Ts_free9.txt)
■ TSfree > Iconミニ講座10(分割文字で辞書参照) 風つかい
さて、まとめで、フルスペックのプログラムにまとめましょう。 ・入力文字列を分割したもので、辞書参照。 ・不明文字対応のため、曖昧検索指定。 ・辞書は、文字数別に分割し、必要なものだけ使う。 辞書分割の際にダブリチェックして、分割後の辞書ファイルには ダブリが無いので、このプログラムでは単語のダブリチェックは 削除します。 というところで、まとめると次のようになります。
尚、試して頂きやすいように、全ての procedureを1つのファイルにしていますが、 サブ procedureを、別のファイルにして、mainファイルから linkするようにもでき ます。
####################
# 文字列の組合せで、辞書参照。曖昧検索。分割辞書。ワイルドカード未対応
####################
# 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
(追記 コメントの 「ワイルドカード未対応」は、削除漏れです。ワイルドカード に対応しています。)
曖昧検索指定が無い場合の例をを1つ。 dicrefp5 heavyrain 2 4 >kkk としますと、次のようになります。
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
さて不明文字が2つあるケースだと、 dicrefp5 a.ahviy.r 2 4 >lll としますと、こんな結果になります。
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
結構、合致するものがありますので、途中を略してあります。 943件だと結果のチェックに時間がかかりそうです。 ちょっと長引きましたが、ちょうど10回目ということで、Iconミニ講座は、 終わりにします。(宣言しておかないと、すぐまた続けそうなので。笑) 長文におつき合い頂きまして、有り難うございます。
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Hello It's Me / Lani Hall (2003/08/25 TSfreeA.txt)
■ TSfree > Iconミニ講座 おまけ(プログラムの整理) 風つかい
何かのプログラムを作っても、しばらく経つと、作った自分でも、「一体何のための プログラムだっけ?」とか「一体何のためにこういう処理にしてるんだろう?」と悩む ことが沢山あります。 私のハードディスクには、そんな正体不明のプログラムがあふれています。
今後も使えそうな procedureはライブラリーにして、独立プログラムはコメントを 充実して保管したい。とは、思っていますが、なかなかできないですね。
今回は、少しやっておこうと思います。 また、少し Iconの特徴を生かしていない 部分がありますので、書き直してみました。
<書き直し1>
if member(T_dic,s_word) # 辞書テーブルにあるかチェック then put(T_dic[s_word], word) # あれば、その listに追加 else T_dic[s_word] := [word] # 無ければ、listに入れて登録
という部分があります。
辞書 tableには、例えば key:"abc" に対応した 値として、ソートして小文字化 すると"abc"となる単語を登録します。 そういう単語は複数ある可能性がありますので、key:"abc"に対応する 値は list として、その中に 順次追加していきます。
<1>最初 値に "ABC"を追加 key:"abc" -> ["ABC"] としたい。 ・・・key:"abc"が無い状態から追加
<2>次は 値に "BcA"を追加 key:"abc" -> ["ABC","BcA"] としたい。 ・・・既に key:"abc"に対応する要素 "ABC"があって更に "BcA"を追加
<1>の状態(key:"abc"が無い状態)で、key:"abc"の値に listがあるつもりで
2>と同じやり方で、単語を追加しようと listへの追加処理をすると、該当する
keyが無いということで、エラーが発生します。
put(T_dic[s_word],word) は、エラーが発生。 (ランタイムエラー:存在しない値(&null)にアクセスしようとした。)
(補足:listの追加処理で、追加先の listが無いというエラーも発生する条件だが、 tableの keyが無いというエラーが先だと思います。)
<2>の状態なら key:"abc"は存在して、値には list ["ABC"]が存在していますので、 このlistへの追加処理で単語が追加できます。 put(T_dic[s_word],word) で、単語が追加できる。
<1>の状態(key:"abc"が無い状態)で値に単語を追加する場合は、
T_dic["abc"] := ["ABC"]で、 key:"abc"の値に要素数1の listをセット しなければいけません。
この<1>と<2>処理の違いを if..then..else..で分けています。
さて、(key:"abc"が無い状態)をチェックするには、member(T_dic,"abc")で できますが、実際に key:"abc"で tableにアクセス(T_dic["abc"])してエラーが 発生するかどうかでもチェックできます。
でも、ランタイムエラーが発生するとプログラムが止まってしまいます。 それでは困るので、Iconには、存在しない(&null)かどうかを判定する仕掛け があります。
\T_dic["abc"] は、存在すれば key:"abc"に対応する T_dicの値を、 存在しなければ、&fail(エラー状態)となり、 ランタイムエラーにはなりません。
そこで、if member(T_dic,s_word) は、if \T_dic[s_word] とできますので、
if \T_dic[s_word] # 辞書テーブルにあるかチェック then put(T_dic[s_word], word) # あれば、その listに追加 else T_dic[s_word] := [word] # 無ければ、listに入れて登録
と、書き直せます。
key:"abc"が無い状態では、 put(T_dic["abc"],word)は、ランタイムエラーを発生しますが、 put(\T_dic[["abc"],word)は、ランタイムエラーを発生せず failして、 値は、&failとなります。
そこで、更に、if .. then .. else ..式を、 put(\T_dic[s_word],word) | (T_dic[s_word] := [word])
と、1行にできます。 "|"の左辺が failした場合は、右辺の式が生きて、 (T_dic[s_word] := [word])を実行する動作となり、if .. then .. else .. と 同等の動作となります。
<書き直し2>
ERR := &null # 辞書参照エラーフラッグリセット Llresult := [] # 辞書検索結果格納 list every (ss := !Lword) & /ERR do { # 細分文字を取り出して、 # ↑既に辞書参照エラーが発生していなければ、 # 細分文字が辞書に存在するかチェック if member(T_dic,ss) then put(Llresult,T_dic[ss]) # 結果データ格納 else ERR := "ERR" # 参照エラーフラッグセット }
ここは、分解した文字列の各々が辞書に存在するかのチェックをしています。 ERR というフラッグを立てているのが、美しくありません。 これは、辞書 table参照用の別 procedureを作ると、
Llresult := tbl_ref(T_dic,Lword) | &null # 分配文字列が全て辞書にあれば 辞書参照結果をセット。 # いずれかが辞書になければ、&nullでクリア。
と、一行にできます。 このように、Iconでは "|"を使うと、表記がコンパクトになる場合があります。
<書き直しその他> 共通的な procedureは、別ファイルにして、メインファイルから linkするように してみました。 また変数名は若干変更してあります。 procedure名も1つ統一が 取れてなかったので修正しました。
link指定は、私のライブラリ区分に従っていましたが、お試しになる場合を考えて 元のファイルから必要な部分だけ抜粋したファイルを作成し、それを linkしてあり ます。
ライブラリを、icont -c foo.icn と、予めコンパイルして、中間コード化
しておき、 その後に、 icont decrefp6.icn と、すれば実行ファイルが作成できます。
####################
# クロスワードパズル支援
####################
# dicrefp6.icn Rev.1.1 2003/08/29 windy 風つかい H.S.
####################
# クロスワードパズル支援
# ・英文字のクロスワードパズルをまず解いて、その後指定の升目の文字を
# 組み合わせて最終的な単語を見つける。
# ・一部判っていない升目がある。
# ・辞書ファイルを参照。英語の単語がズラッと並んだ形式の辞書ファイル。
# ・例: 判らない文字を .で表すと、a.ahviy.r となる。
# この場合の正解(例)は、heavy rain という2つの単語の組合せ。
# 補足
# ・コマンドライン引数から、文字列を生成して、辞書を参照し、辞書にあれば出力。
# 不明文字のための曖昧検索。単語組合せのため文字列分割。
# ・辞書参照時間短縮のため、辞書を読み込む時に、単語を文字ソート・小文字変換
# した keyをつけて、tableに格納。 この tableを参照。
# ・辞書読込時間短縮のため、文字数毎に分割された辞書ファイルから必要な字数の
# ファイルのみを読込む。
# 元の辞書(english.dic)は、スペルチェック用の英単語が順に並んだもの。
# このプログラムのテストでは、DD SOFT SoundMixSpellコンポーネント
# Ver 0.3.0 に 同梱の辞書ファイルを使用。
# ・辞書は、予め文字数毎に、e01,e02,...等に分割しておく。(別プログラム)
# ・TS Network TSabc,TSfree関連
# 履歴
# dicrefp5.icnを修正。サブ procedureを link形式にした。
# 条件式 変数名 を見直し。訂正: mscombd -> mscomb procedure名の付与ミス
# This file is in the public domain. link file_x, # ファイル関係 f_name: 実行ファイル名取得
string_x, # 文字列関係 mscomb: 英文字列重複組合せ # expcomb: 英文字列分配組合せ number_x, # 数字関係 ndivdf: 正整数の部分和(降順 分割数・最小制限) stringsx # 文字列関係(Icon基本ライブラリ BIPLに含まれるもの) # deletec: 英文字列から文字を削除 # csort: 英文字列の辞書順ソート
procedure main(args)
# コマンドライン引数チェック。無ければ Usage表示 Usage := " 英単語(.はワイルドカード) 最大分割数 最短文字長" if *args < 1 then stop(f_name(),Usage)
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)) # 分割パターン格納 # L_lpat例:[[6],[3,3]] # 必要な文字数の生成( setに入れて、ダブリを削除) S_pat := set() # 文字数格納 set(指定文字数ため) every L := !L_lpat do every insert(S_pat,!L) # 文字数を全て 格納 # S_pat例: (6,3) T_dic := table() # 辞書格納 table生成 every n_pat := !S_pat do { # 辞書ファイル名生成 # ↓rightは、右寄せ桁合わせ関数 dic := "e" || right(n_pat,2,"0") # 辞書ファイル名 例:"e06","e03" # 辞書ファイルオープン dir := open(dic) | stop(" ",n_pat,"文字用の辞書が見つかりません。") writes(" ",n_pat,"文字用辞書 ",dic," を読込中です。開始:",&clock) # 辞書読込 n_line := 0 # 辞書行数カウンタ # 辞書を1行ずつ読み込んで、 while word := read(dir) do { n_line +:= 1 # 辞書行数+1 if n_line % 1000 = 0 then writes(&errout,"*") # 読み込み状況表示 # 単語を小文字変換しソートしたものを keyにして格納。 # 同一文字を含む単語は同じ keyに対応する valueに listの形式で格納。 # tableの key生成 # ↓文字列ソート s_word := csort(map(word)) # 小文字へ変換しソート # ↑小文字変換 例: "ACb" -> "abc" # 辞書 tableへ格納 put(\T_dic[s_word],word) | (T_dic[s_word] := [word]) # 格納例: key:"abc" -> value: ["ACb","Bca","cab"] } # end of while word .. close(dir) # 辞書ファイルクローズ write(&errout) write(" 終了:",&clock," ",n_line,"語ありました。") } # end of every n_pat ..
write("\n",c_word," を、最大分割数 ",ndiv,"、最小文字長 ",nmin, " にて分割してチェック。開始:",&clock)
# 辞書参照 n_comb := 0 # 組合せ数カウンタ n_find := 0 # 辞書にある件数カウンタ s_word := deletec(c_word,'.') # コマンドライン文字列から '.'を削除 n_dot := *c_word -*s_word # '.'の数 # ワイルドカード文字生成 ↓&lcaseは英小文字 ↓例: a-z,aa-zz,aaa-zzz every s_wild := mscomb(&lcase,n_dot) do { # ワイルドカード文字を、 s_check := s_wild || map(s_word) # コマンドライン引数の "."以外に足し、 # 分配文字数パターン取り出し every Lpat:= !L_lpat do { # Lpat例: [6]とか、[3,3]とか # 文字列分割結果取り出し Lword例: ["sunday"]とか,["day","sun"]とか every Lword := expcomb(s_check,Lpat) do { # 分配文字列を順次取り出して、 n_comb +:= 1 # チェック回数カウンタ+1 if n_comb % 1000 = 0 then writes(&errout,"*") # チェック状況表示 # 辞書参照して、分配結果が全て辞書にあれば、L_loutにセット L_lout := tbl_ref(T_dic,Lword) | &null # 分配文字列が全て辞書にあれば、 # 辞書参照結果をセット。いずれかが辞書になければ、&nullでクリア。 # L_lout例: [["Day"],["day"],["Sun","sun","nus"]] # 分配文字列が全て辞書にあれば、書き出し if \L_lout then { # L_outに値がセットされていれば n_find +:= 1 # カウンタ+1 writes(&errout,"!") # 合致表示
# "."を除く元の文字書き出し writes(s_word) # if *s_wild >= 1 then writes(" + ",s_wild) # ワイルドカード文字 writes(" ->") #
# 辞書検索結果(2重 list)の書き出し every Lout := L_lout[i := 1 to *L_lout] do { # 参照結果を取り出して if i > 1 then writes(" +") # 先頭でなければ、区切りマーク every writes(" ",!Lout) # 参照結果を書き出し } # end of Lout .. write()
} # end of if \L_lout .. } # end of every Lword .. } # end of every Lpat .. } # end of s_word ..
write(&errout) write(n_comb," 通りの組合せのうち、",n_find," 通りが辞書にありました。", " 終了:",&clock)
end
###################
# Tableを list要素で参照し、結果を list連結
###################
# arg [1]: table key: "abc" -> value: "ABC"
# [2]: list 参照する要素の list 例:["abc","def",...]
# value : list table参照結果を格納 例:["ABC","DEF",...]
# Usage : L := tbl_ref(T,L)
# listの 要素のいずれかが tableに存在しなければ、fail
procedure tbl_ref(T,L) #
L1 := [] # ↓ Lの全ての要素につき every x := !L do put(L1,\T[x]) | fail # 参照 tableになければ failさせる。 return L1
end
# 条件が全て揃った場合だけ結果を返す(いずれかの条件が成立しなかった場合は、
# 全体を failさせる。)には、その部分を サブ procedureにすること。
tbl_ref()は、今回作りましたので、この dicrefp6.icnに入れてありますが、汎用的 な procedureですので、共通ライブラリに移す予定です。
ちょっと長いですが、ライブラリから抜粋したファイルを付けます。
####################
# file操作関係 procedure
####################
# file_x.icn Rev.1.1 2003/08/29 windy 風つかい H.S.
# file_e.icn から抜粋。
# This file is in the public domain. link string_x # top_get,top_cut
# f_name(file_name) # 拡張子を除くファイル名生成(小文字)
# F_name(file_name) # 拡張子を除くファイル名生成(大文字)
####################
# 拡張子を除くファイル名を生成
####################
# Usageの file名訂正漏れを 防ぐための作成
# f_name(),F_name() 1997/12/27 windy 大文字、小文字対応に分ける。
# f_name() 1997/08/15 windy exe_name() -> f_name()
# exe_name() 1997/06/02 windy 風つかい H.S.
# args : ファイル名
# value: string
# Icon入門講座4 Icon回り道(4)
procedure f_name(file_name)
/file_name := &progname # defaultは、実行ファイル名 return map(top_get('.',top_cut('\\',file_name)))
end
procedure F_name(file_name)
/file_name := &progname # defaultは、実行ファイル名 return map(top_get('.',top_cut('\\',file_name)),&lcase,&ucase)
end
####################
# 数字関係補助 procedure
####################
# number_x.icn Rev1.1 2003/08/29 windy 風つかい H.S.
# number_e.icnから抜粋
# This file is in the public domein.
# n_divd(r,max) # 正整数の分割(降順のみ) genarator
# n_divdf(r,ndiv,nmin) # 正整数の分割(降順、分割数・最小数制限)generator
####################
# 正整数分割 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 ...
# Iconミニ講座3
# (4) -> [4],[3,1],[2,2],[2,1,1],[1,1,1,1]
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
####################
# 正整数分割 generator(フィルター)
####################
# 5=3+2 等に数字分割する。この procedureは n_divdを呼びフィルターを掛けている。
# 分割パターンが多すぎる時に、制限をかけるために使用。
# arg [1]: 分割する元の数
# [2]: 最大分割数
# [3]: 最小数
# value : list
# Usage : every L := n_divdf(r,ndiv,nmin) do ..
# Iconミニ講座4
# (4,2,2) -> [4],[2,2]
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
####################
# 文字列操作 procedure
####################
# string_x.icn Rev.1.1 2003/09/29 windy 風つかい H.S.
# string_e.icn から抜粋
# This file is in the public domain. link stringsx # BIPL csort
# ssub(s1,s2) # 文字列の引き算
# top_cut(c,s) # 先頭削除(最長一致削除)
# top_get(c,s) # 先頭切り出し(最短一致切り出し)
# 組合せ関係
# exscomb(s,m) # 文字の組合せ生成(同一文字対応) generator
# mscomb(s,m) # 文字の組合せ生成 nHm(重複取り出し) genarator
# 順列・組合せ共通
# new_pos(s) # 文字位置生成 (同一文字対応用) generator
# 組合せ分配
# pcomb(s,L) # 文字の組合せを 分配 generator
# expcomb(s,L,s_ref) # 文字の組合せを 分配(同一文字 同一数)generator
####################
# 文字列の引き算
####################
# arg [1]: string 被削除文字列
# [2]: string 削除文字列
# value : string 結果文字列
# 文字列 s1の先頭から、文字列 s2の文字を削除。1:1で削除。
# ("abcabc","abd") -> "cabc" s2にある文字で、s1に無い文字は無視される。
# Iconミニ講座9
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
####################
# 文字列の 先頭から特定文字までの部分 を取り去る。最長部分を取り去る。
####################
# arg : cset
# value : string
# Usage : top_cut(c,s)
# Icon入門講座2(5)
procedure top_cut(c,s)
/s := &subject # sの指定がなければ &subject /c := ' \t' # cの指定がなければ spaceか tab s ? { # sを走査対象とする。 while tab(upto(c)) do tab(many(c)) # cが見つかる限り、走査位置を # その文字の後に移動。 return tab(0) # 残りの文字列を返す。 }
end
####################
# 文字列の 先頭から特定文字までの部分 を得る。最短部分を得る。
####################
# arg : cset
# value : string
# Usage : top_get(c,s)
# Icon入門講座2(5)
procedure top_get(c,s)
/s := &subject # sの指定がなければ &subject /c := ' \t' # cの指定がなければ spaceか tab s ? { # sを走査対象とする。 return tab(upto(c) | 0) # cまで走査位置を移動しその間の } # 文字列を返す。cがみつからなけ # れば、末尾まで返す。
end
####################
# 組合せ関係
####################
####################
# 文字列の組合せ 同一文字指定を許す n文字列から m個を選ぶ組合せ generator
####################
# stringの組み合わせ
# n個から、m個を選ぶ。(同一文字指定対応)
# arg: [1]: s: string
# [2]: m: integer
# value: string
# Usage: every ss := exscomb(s,m) do ...
# Icon入門講座3(11)scomb()を同一文字指定対応に拡張。 Iconミニ講座9
# ("abca",3) -> "aab","aac","abc"
procedure exscomb(s,m)
initial { /m := *s # デフォルト nCn (n = m = *s) s := csort(s) # 文字列をソートする。(BIPL:strings.icn) } if m = 0 then return "" # mを文字数カウンターに使う。 # 0にになったら、再帰終了。 suspend s[i := new_pos(s)] || exscomb(s[i+1 : 0], m -1) #↑ 1文字選ぶ ↑ ↑←前で選んだ文字以降 ↑指定文字数に # | の文字列に対し同じ 文字列を抑え # | 処理を行う。 るためのカウ # 文字列の連結 ンター
end
#############################
# 文字列の nHm n文字列から、重複して m個を選ぶ組合せ generator
#############################
# n個から 重複して、m個を選ぶ。
# arg [1]: string
# [2]: integer
# value : string
# Usage : every ss := mscomb(s,m) do ..
# ("abc",2) -> "aa","ab","ac","bb","bc","cc"
procedure mscomb(s,m)
initial { /m := *s } if m =0 then return "" # 再帰終了 every i := 1 to *s do { # 1~文字列長まで # ↓ i番目の文字を取り出して suspend s[i] || mscomb(s[i:0],m-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
####################
# 分配の組合せ(同一文字指定・同一文字数指定対応)
####################
# 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"]
# 分配パターン生成に、number_e.icnの n_div(),n_divd(),n_divdf()が使えるかも。
# Iconミニ講座9
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
####################
# 文字列操作 procedure BIPL(Icon基本ライブラリー)より
####################
# stringsx.icn Rev.1.1 2003/08/29 windy 風つかい H.S.
# BIPL strings.icn から抜粋
# This file is in the public domain.
####################
# 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
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: Yesterday's Dream / 山見慶子 (2003/08/30 TSfreeB.txt)
■ TSfree > Iconミニ講座 あまり(procedure構成) 風つかい
ライブラリ整理の際、ちょっと遊んでみましたので、ご参考に。 テーブルをリストの要素で参照する procedureの3つの例です。
####################
# テーブル参照プログラム例
####################
# proc01.icn Rev.1.1 2003/08/29 windy 風つかい H.S.
# TS Network TSfree
# This file is in the public domain. procedure main()
T := table() # table生成 T["a"] := "AA" ; T["b"] := "BB" ; T["c"] := "CC" # データ登録 # ↓データ1 # ↓データ2 L := [["a","b","c"],["a","d","c"]] # テストデータ
# procedure のテスト procedureを引数にして、procedureを呼ぶ。 test(tbl_ref1,T,L) test(tbl_ref2,T,L) test(tbl_ref3,T,L)
end
# procedureのテスト
# arg [1]: procedure
# [2]: table テスト用 table
# [3]: list テスト用 list procedure test(prcdr,T,L)
write(image(prcdr)) # procedure名 every L1 := !L do { # テスト用 listからデータを取り出して every writes(" ",!L1) # 書き出し writes(" -> ") L2 := prcdr(T,L1) | ["error"] # 処理結果 every writes(" ",!L2) # 処理結果書き出し write() } write("----- ----- -----") # 仕切線
end
###################
# Table参照し、結果を list連結
###################
# arg [1]: table key -> value
# [2]: list 参照する要素の list 例:["abc","def",...]
# value : list table参照結果を格納 例:["ABC","DEF",...]
# Usage : L := tbl_ref(T,L)
# args[2]の 要素のいずれかが tableに存在しなければ、fail
# 条件が全て揃った場合だけ結果を返す(いずれかの条件が成立しなかった場合は、
# 全体を failさせる。)には、その部分を procedureにすること。
# 再帰 procedure tbl_ref1(T,L) # ↓ \xは存在すればその値、なければ fail
return if *L = 1 then [ \T[ L[1] ] ] #←listの最後なら再帰終了 else [ \T[ L[1] ] ] ||| tbl_ref1(T,L[2:0])
end # listの連結 ↑ ↑最後でなければ、再帰
# every procedure tbl_ref2(T,L) # <- この辺が分かりやすいかな。
L1 := [] every x := !L do put(L1,\T[x]) | fail # いずれか辞書になければ、fail return L1
end
# whileループ procedure tbl_ref3(T,L)
L1 := [] while x := get(L) do put(L1,\T[x]) | fail # いずれか辞書になければ、fail return L1
end
動かすとこんな風です。
procedure tbl_ref1
a b c -> AA BB CC a d c -> error
procedure tbl_ref2
a b c -> AA BB CC a d c -> error
procedure tbl_ref3
a b c -> AA BB CC a d c -> error
風つかい(hshinoh@mb.neweb.ne.jp) IconのWWWは、 http://www.cs.arizona.edu/icon/ UniconのWWWは、http://unicon.sourceforge.net/index.html BGM: for Masqurade / 山見慶子 (2003/08/30 TSfreeC.txt)