Nim (4)
頼んでいた『暗号解読』が入荷しましたから、一週間以内に借りに来てくださいってTELが有った。 散歩がてら、畦道、間道を抜けて、図書館まで取りに行った。往復1時間とは、丁度良い距離だな。
年度の切り替わり時期で、図書館の決算報告書が掲示板に出てた。図書館は、皆様からの 税金のうち2000円づつを持って運営されてます、ですって。
そうか、年会費2000円の強制永久会員か。2000円じゃ、ハードカバー本1冊、もしくは文庫本3冊 ぐらいしか買えないな。オイラーは、読み放題の特典を生かして、月に10冊以上、20000円 相当ぐらいを読破してるから、図書館のヘビーユーザーだな。
何時行ってもいる、新聞読み比べ老人達とか、おかあさんと何時も一緒って母子セットもいるから、 図書館の使い方は多用ですな。
最近は、 3月13日公開の映画『イミテーション・ゲーム』:世界最初のコンピューター科学者がナチスを打ち負かす物語 なんてのも出てて、チューリングがブームですな。勿論、暗号解読本にも出てきます。
ドイツが作った、暗号・復号用タイプライターである、エニグマの話が面白い。この謎めいた 機械の動作が良く分かりましたよ。
暗号の原理には、大きく分けて2種類ある。平文の1字1字を、違う文字に置き換えていく方式。 文字を置き換えるので換字式暗号と言う。前回やった、ROT13なんてのは、典型的な例になる。
もう一つの種類は、転置式暗号と呼ばれるものだ。平文の文字の配置を入れ替える、トランプで シャッフルするのと同じ。完全にシャッフルしちゃうと、元に戻せなくなるので、ある規則で 文字を並べ替える。一番簡単なのは、2文字づつに分解して、そのペアの中で左右の文字を 入れ替える方法。rubyってのを転置するなら、ruとbyに分けてから入れ替える。urybになる。 他にも、原稿用紙に縦書きにしたのを、横書きで読むとか。。。
エニグマは、前者の換字式暗号を使ってる。但し、普通に文字を置き換える方式だと、平文の 持つ言語特徴を勘案して(文字の使われ方の頻度から、元の文字を推測するとか)、暗号を破る事が 出来るので、特徴を消し去るような仕掛けが何重にも施されている。
換字式暗号の破り方は色々と解説されてるけど、転置式の破り方には全く触れられていない。 素人さんには難しすぎて説明出来ないのかな。どうしても知りたかったら、数学を勉強してねって事か。
それから、もう一つ疑問が有って、平文は勿論人間が読めるものだけど、例えば平文をコンピュータで 圧縮したものを暗号化する手がある。これだと、正しく解読しても、圧縮した物が出てくるはず。 そもそも、圧縮の時点で、平文の言語的特性が失われているので、解読出来るのか? (圧縮されてるのがバレないように、magicコマンドの目の付け所を調べておく等の工夫が必要)
こういう疑問は、誰に聞けばいいの? 暗号技術入門のあの人?
プチ不満は有ったけど、 暗号方式を開発する人と破る人のせめぎ合いを楽しめましたよ。そう言えば、こういう知恵比べ。おいらも昔、やったね。 ゲームのプロテクト外し。
別に、プロテクトを外して、ゲームを無料でやりたいとか、金儲けをしたいとか言う不純な 動機からではない。
プロテクトがかかったゲームが有る。どうやってるの? 外してみたいな。 小供が、目覚まし時計ってどうなってるの? 分解して調べてみようと思う気持ちですよ。 世界の有名な学者だって、同じ事をやってるよ。いい大人ですけどね。
スイスとフランスとドイツの国境をまたがるあたりの地下数100メーターの所に作られた、秘密基地に結集してね。 日夜、原子に鉄砲玉を打ち込んで分解し、その破片を調べて、どうなってるか考えてる。
何と純粋な人達でしょう。チューリングさんも同じですね。ただ、彼らの場合、暗号破りと言う 国家機密に関わっていたため、厳重に秘されていましたけどね。
RSA
上記の本に有名な公開鍵暗号が(やはり)話題として取り上げられている。巨大な素数の積を 、素数に分解するのは大変だよって事に基礎を置いている。
どれぐらい大変なの? 素因数分解。
素因数分解問題調査研究報告書 とか、 素因数分解とかエラトステネスの篩(ふるい)とかのメモ に、調べた人がおられた。
自分のマシンでも追試してみるかな。書かれたコードがPythonだけど、オイラーにはNimの ごとく見えるな。ちょこざいと修正するだけで動きそう。だったら、気を利かせた人が、python2nimとかの、 トランスレータを書かないかねぇ。そうすれば、実行スピードはC語と同等なんだから、流行ると 思うよ。
このRSAは、素数夜曲な本にも取り上げられてて、イギリスの情報部が、R・S・Aな人達よりも、 3年前に発明してたってなってたな。数学なんて世の中の役にたつの?って疑問に対する、 りっぱな回答ですな。
第一次戦争は化学者が活躍した(毒ガス兵器で)。第二次戦争は物理学者が活躍(核兵器で)。 そして、大惨事戦争が有るとすれば、数学者が活躍するはず。(暗号とかの情報戦争用兵器)
アメリカは熱心に数学者をアメリカ国家安全保障局(NSA)に囲い込んで、いろいろやってるらしい。 NSAってのは、NSAはあまりに全貌が不明瞭なため、その略称は「Never Say Anything(何も喋るな)」 「No Such Agency(そんな部署はない)」の略だと揶揄される事も有る。
素因数分解が難しいなら、新しいパラダイムに基づくコンピュータを開発しちゃえ。そう量子 コンピュータが出来れば、一瞬にして解けちゃうぞ。RSAは役に立たなくなる。
そんな事もあろうかと、量子暗号が研究されてる。こちらは、盗聴してる事が判定出来るって代物。 盗聴出来なければ、極端な話、平文で相手に送っても大丈夫。
但し、Never Say Anything なので、どこまで実現されてるかは不明。機密情報暴露で有名に なったのは、元CIAのエージェントであるスノーデン氏だったけど、世界平和の実現の為、 NSA版の第二のスノーデン氏が現れることを期待。
解読難易度が高い暗号の一つにストリーム暗号ってのがある。平文を鍵とEXORしてくやつ。 鍵が常に変わっていけば、解読出来ない。これの応用先として、WiFiが有る。
常に変わる鍵は乱数生成器によって得られる。乱数の周期が長ければ、事実上解読は出来ない。 暗号強度は、ひとえに乱数発生器の品質いかかっている。 円周率は、乱数発生器としては、 優秀らしいぞ。
ああ、こんなのが有った。
Haskell memo
read_csv
前回からの続き。今度はCSVファイルのデータを読み込む所。nimのモジュールを探ると 旨い具合に提供されてるので、そのまま利用させてもらいました。
import os, parsecsv, streams import strutils const SEED = "./z.csv" type ds = array[4, int] proc pp(things: seq[ds]) = for d in things: echo(d[0]," ",d[1]," ",d[2]," ",d[3]) proc read_csv() : seq[ds] = var s = newFileStream(SEED, fmRead) var x: CsvParser var rv = newSeq[ds](0) open(x, s, SEED) while readRow(x): var al : ds var i : int =0 for val in items(x.row): al[i] = parseInt(val) i += 1 rv.add(al) close(x) return rv var bld = read_csv() pp(bld)
nimの場合も勿論、サイズ成長可能な配列が用意されてます。型名は seqです。シーケンスの 略かな。で、この中には、サイズ固定の配列が追加されます。配列の型は、typeを使って宣言しときました。 これで、多少入力が楽になりますね。
配列をアクセスしるインデックス変数は、自前でプラス1してるけど、nimの流儀では、inc(i)と 書くようです。果たして違いは何処に?
浮動小数点の印字
nimには、いわゆるフォーマットプリントが無いんだよな。だから、初回でやったように、勇士さんが 拡張モジュールを提供してる。
それは有り難いんだけど、あちこちにソースを持って行ってコンパイルしようとすると、面倒になる。 なるべく、標準品だけで済ませたいのが本音。
今回は、フォーマットは固定で良いので、頑張って書いてみる。%6.1f で良いのさ。正式な やつだと、少数点2桁目を四捨五入して1桁目に反映してるけど、面倒には目を瞑る。
10倍してそれを整数に変換。10で割った商と余りに分けて、ドットで結合すればいいな。 桁を揃えるための空白の挿入が面倒。何か補助が無いかと、strutilisをまさぐる。
repeatChrなんてのが見つかった。これを使えば良いな。と、思って周辺を見渡したら、 もっと楽を出来るものが見つかった。
proc f2s(f: float) : string = # Same as sprintf("6.1f", f) let f10 = int(f * 10) let u = f10 div 10 let d = f10 mod 10 return align($u & "." & $d, 6)
alignがそれ。特に指定しないと、空白を補ってくれる。便利。
で、ものはついでと思って、このモジュールをぱらぱらしてたら、結構充実してましたよ。 上の関数を書いちゃった後だけど、
proc c_sprintf(buf, frmt: cstring) {.header: "<stdio.h>", importc: "sprintf", varargs, noSideEffect.} type FloatFormatMode* = enum ## the different modes of floating point formating ffDefault, ## use the shorter floating point notation ffDecimal, ## use decimal floating point notation ffScientific ## use scientific notation (using ``e`` character) {.deprecated: [TFloatFormat: FloatFormatMode].} proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, precision: range[0..32] = 16): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. :
実体は、C側のsprintfに丸投げしてるんですけど、良く使いそうなやつを用意してるNim開発者に 拍手を送りますよ。Webとかをやる時、このあたりが充実してないと、顧客に逃げられますからね。
ちょいと疑問が出てきたので、実験。疑問とは、importするとき、モジュール全部を取り込むのと、 指定したものだけにするのとでは、出来上がるオブジェクトサイズに差が出るのってやつ。
[sakae@fedora t]$ cat size.nim import strutils let f = 12.345678 echo formatFloat(f, format = ffDecimal, precision =3) [sakae@fedora t]$ nimhs size.nim 12.346
strutils.cは、行数が69行だった。もう結論が出ちゃったけど、一応確認。
from strutils import formatFloat, FloatFormatMode
先頭行を変更したもので確認したら、実行サイズ、C語のソースサイズも同一だった。 Nimのソース上でみんな持って来る指定をしておいても大丈夫だよ。関係者しかC語に変換されないからね。
たとえ関数名が同一でも、アリティが異なれば、違う関数と 看做されるしね。衝突する事はめったに無いでしょう。
差は、どこのモジュール出身かプログラミングしてる人に明確になる事ぐらいかなあ。 python陣営では、使うものだけインポートしろってのが推奨されてる。あちらは、あちらの 事情が有るんでしょうな。
無理して特定なものだけにすると、足をすくわれるから注意。 実は、最初、FloatFormatModeを持ってくるのを忘れ、ffDecimal なんて知らないって怒られたのさ。
完成したぞ
### blood data stat import os, parsecsv, streams, sequtils import strutils, osproc from algorithm import reversed from posix import exitnow const SEED = "./current.csv" type Opt = object of RootObj ap: int # 1: am, 2: pm tl: int # Number of data ss: int # stat pp: int # print lg: int # line graph er: int # error detect ds = array[4, int] proc getopt() : Opt = var args = newSeq[string](0) for i in 0 .. paramCount(): args.add(paramStr(i)) args[0] = "fin" result = Opt(ap:0, tl:0, ss:0, pp:0, lg:0, er:0) var av = 0 for s in args.reversed(): case s: of "fin": if paramCount() == 0: result.er = 1 of "-am": result.ap = 1 of "-pm": result.ap = 2 of "-ss": result.ss = 1 of "-pp": result.pp = 1 of "-lg": result.lg = 1 of "-tl": result.tl = av else: # Must be number try: av = parseInt(s) except: result.er = 2 proc pp(bld: seq[ds], cw: int) : seq[ds] = if cw != 0: for d in bld: echo(d[0]," ",d[1]," ",d[2]," ",d[3]) return bld proc read_csv(seed: string) : seq[ds] = var s = newFileStream(seed, fmRead) var x: CsvParser result = newSeq[ds](0) open(x, s, seed) while readRow(x): var al : ds var i : int =0 for val in items(x.row): al[i] = parseInt(val) i += 1 result.add(al) close(x) proc ampm(bld: seq[ds], cw: int) : seq[ds] = result = case cw of 1: filter(bld, proc(e: ds): bool = (e[0] mod 100) < 12) of 2: filter(bld, proc(e: ds): bool = (e[0] mod 100) >= 12) else: bld proc tl(bld: seq[ds], cw: int) : seq[ds] = if cw > len(bld): echo("Req size is too big, so can't do that") exitnow(3) result = newSeq[ds](0) if cw > 0: for i in bld.len - cw .. bld.len -1 : result.add(bld[i]) else: result = bld proc w(bld: seq[ds], hlp: int) : seq[int] = result = newSeq[int](0) for e in bld: result.add(e[hlp]) proc max(ar: seq[int]) : float = var rv = low(int) for v in ar: if v > rv: rv = v return rv.float proc mean(ar: seq[int]) : float = var sum = 0 for v in ar: sum += v return sum / ar.len proc f2s(f: float) : string = # Same as sprintf("%6.1f", f) let f10 = int(f * 10) let u = f10 div 10 let d = f10 mod 10 return align($u & "." & $d, 6) proc ssf(bld: seq[ds], fn: proc(x: seq[int]):float) : string = result = "" for hlp in 1 .. 3: result.add( f2s(fn(w(bld, hlp))) ) proc ss(bld: seq[ds], cw: int) : seq[ds] = if cw != 0: echo("size: " & $len(bld)) echo("max: " & ssf(bld, max)) echo("mean:" & ssf(bld, mean)) return bld template mk4gp(gd: string, bld: seq[ds], hlp: int) = var i =0 for e in bld: gd.add($i & " " & $e[hlp] & "\n") inc(i) proc lg(bld: seq[ds], cw: int) :seq[ds] = if cw != 0: var gd = "plot '-' w l\n" mk4gp(gd, bld, 1) gd.add(" \n") mk4gp(gd, bld, 2) gd.add("end\n") let pid = startCmd("gnuplot -p", {poStdErrToStdOut, poUsePath}) let inp = inputStream(pid) inp.write(gd) inp.close() close(pid) return bld ## main ################# let f = getopt() if f.er != 0: echo("Can not do") exitnow(f.er) var bld = read_csv(SEED) discard bld.ampm(f.ap).tl(f.tl).pp(f.pp).ss(f.ss).lg(f.lg)
ampmって関数は、測定データから、午前・午後のデータを抽出するものだ。ifの結果は 変数に代入出来る事は知ってたけど、caseも同様なのね。 そして、filterなんてのが有ったので、使ってみた。名無しのprocで、抽出条件を決めている。 関数型っぽく書けて、嬉しいな。
templateってのが使えるそうだったので、無理して使ってみました。関数と同じ感覚で 使えます。けど、関数にしちゃうと呼び出しのオーバーヘッドが課されるに対し、 templateは、インライン展開なので、オーバーヘッドが有りません。ここら辺が、 使い所になるのかなあ。
エラーを検出してプログラムを終了させるには、posixに有るexitnowってのを使うみたい。 posixって事で、Windowsで普通に動くか危惧したけど、mingwでは普通に動きました。
それに対し、bcc32のコンパイラーをチョイスすると、タイムゾーンのインクルードが無いとか 言われて、コンパイル失敗。ちなみに、上記の完成品は、
[sakae@fedora bld]$ wc bld.nim 131 537 3600 bld.nim [sakae@fedora bld]$ wc nimcache/*.c 229 602 5394 nimcache/algorithm.c 1369 3731 35072 nimcache/bld.c 14 61 508 nimcache/cpuinfo.c 14 61 504 nimcache/hashes.c 299 933 7802 nimcache/lexbase.c 14 61 500 nimcache/linux.c 452 1211 11086 nimcache/os.c 1029 2828 26851 nimcache/osproc.c 697 1971 17995 nimcache/parsecsv.c 388 1080 8796 nimcache/parseutils.c 43 136 1052 nimcache/posix.c 97 288 2747 nimcache/sequtils.c 496 1452 13745 nimcache/streams.c 74 206 1765 nimcache/strtabs.c 410 1097 9403 nimcache/strutils.c 4413 12603 107299 nimcache/system.c 14 61 500 nimcache/times.c 10052 28382 251019 合計
こんな具合に、nimの力によって展開されてます。nimは高級言語なんだなあ。
bcc32との連携
上でbccと連携するとコンパイル失敗と書いた。コンパイル時に、-d;releaseを付けてあげると、 余計なものが付属しなくなったせいか少し先まで進むようになった。
でも、posixがある限りunix寄りなんだよな。と思っていたら、quitなんていう汎用的な やつを見つけたぞ。
quit("Commad args error, Can not do...", f.er)
こんな風に軽い乗りで使える。このために特別なものをimportする必要無し。libの下を 覗いておくと思わぬ掘り出し物に出会うな。改めてソース嫁を実感しました。
気をよくしたオイラーは、emacs上からも使えるようにした。が、簡単なハロワの結果が 思わぬ所に表示されちゃう。
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland c:\homes\nimdev\nimcache\tt.c: Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland c:\homes\nimdev\nimcache\system.c: Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland hello world config\nim.cfg(45, 2) Hint: added path: 'C:\Users\sakae\.babel\pkgs\' [Path] config\nim.cfg(46, 2) Hint: added path: 'C:\Users\sakae\.nimble\pkgs\' [Path] Hint: used config file 'c:\app\nim\config\nim.cfg' [Conf] Hint: system [Processing] Hint: tt [Processing] [Linking] Hint: operation successful (8912 lines compiled; 1.014 sec total; 6.666MB) [SuccessX] c:\homes\nimdev\tt.exe Process nim-run finished
コンパイルの途中ですが、先に実行結果をお知らせ致します状態。bcc側の実行が速すぎて、 nim側で出しているメッセージの処理が追いついていないっぽい。
ならば、邪道だけど、ハロワアプリの中で処理を遅延させれば良い? osモジュールの 中に sleep(milsecs: int) なんてのが有ったんで入れてみたけど、今度はアプリが遅延 され、実行終了後に、nimの処理結果が表示される始末。なかなかむずい。
でも、よく考えたら、出来上がったアプリの実行後に出て来るやつは、nimからの、注意事項だった。 nimhsを忠実に真似て、 --verbosity:0 を入れて、抑止したら綺麗になった。やれやれ。