シーザー暗号 (2)
なんとなく以前Haskellで書いたシーザー暗号をSchemeとRubyでも書いてみた。
Haskell版ももっと綺麗になるので書き直してみた。
Haskell。
Haskellは型推論があるので型指定は省略できるけど、書いておいた方がいい。
動的言語でも保守性を考えればコメントには書いておくべきだろう。
- 文字列 = 文字のリスト は気分的に良い
- パターンマッチ … 今回の例では微妙だけどこの機能は記述力がかなり上がる
- 関数合成 … 自己満足w
- IOモナド/do構文 … 慣れればけっこういいかも
- where … ローカル束縛の後置はトップダウン的に読めて良い
多分まだチューンアップできると思う。
ちなみに私の場合コードのチューンアップは効率向上ではなくて冗長性除去。
import Char
import System.Environment
caesar :: Int -> String -> String
caesar = map . shift
shift :: Int -> Char -> Char
shift n ch
| elem ch ['A'..'Z'] = _shift n 'A' ch
| elem ch ['a'..'z'] = _shift n 'a' ch
| otherwise = ch
where
_shift :: Int -> Char -> Char -> Char
_shift n a = chr.(+ na).(`mod` alphas).(+ n).(flip (-) na).ord
where alphas = 26; na = ord a
main :: IO ()
main = do
[fname, ns] <- getArgs
readFile fname >>= (putStrLn . caesar (read ns :: Int))
Scheme。(Gauche)
- src->dst … 変換関数の名前が統一されてて良い
- cut … 部分適用マクロ。lambdaはゴチャゴチャするのであまり使いたくない
- cond … 一般条件式。ちょっと複雑になると読みにくくなってしまう。
- define内define … let/labelsはインデント深くなって読みにくくなるので
- list-ref … first,secondとかあっても良いと思う。cadrとか嫌い。
- scheme-modeの自動インデントが気に食わない … カスタマイズしなさい
それなりにすっきり書けたけど、どうも不満がいろいろ出てしまう。
正直読みにくいんだよなぁ…
(use srfi-13)
(define (caesar n msg)
(string-map (cut shift n <>) msg))
(define (shift n ch)
(define (_shift n a c)
(let ((alphas 26)
(na (char->ucs a))
(nc (char->ucs c)))
(ucs->char
(+ na (modulo (+ n (- nc na)) alphas)))))
(cond
((and (char>=? ch #\a) (char<=? ch #\z))
(_shift n #\a ch))
((and (char>=? ch #\A) (char<=? ch #\Z))
(_shift n #\A ch))
(#t ch)))
(let ((fname (list-ref *argv* 0))
(n (string->number (list-ref *argv* 1))))
(print (caesar n
(call-with-input-file fname (cut port->string <>)))))
Ruby。
やはり一番わかりやすい。しかも一番短いではないか…
- 組み込みクラスの書き変え
- map … 関数適用ではなくメッセージ送信版のmapを設けてはどうか
- def内def
- ?a … これは文字ではなく整数。最近知った。
- 多重代入 … 大好き。Haskellパターンマッチの役割の一部でもある
class String
def caesar(n)
self.split('').map {|c| c.shift n}.join
end
def shift(n)
def _shift(n, a, ch)
alphas = 26
(a + (ch - a + n) % alphas).chr
end
ch = self[0]
case ch
when *(?a..?z): _shift(n, ?a, ch)
when *(?A..?Z): _shift(n, ?A, ch)
else ch.chr
end
end
end
fname, n = ARGV[0], ARGV[1].to_i
print IO.read(fname).caesar(n)

色んな言語使いこなせてすごいよねぇ。
よくこんがらがらないことw
俺にはそんなに言語取得できませんわぁー。
eclipseの頭の中を見てみたいw
1行でやるとこうなるかぁ。つことで。s///のところな。1行ってのは。
perl
$_="ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
$shift=1;
s/([A-Za-z])/$_=ord$1,chr(0x41+($_&0x20)+(($_&~0x20)-0x41+$shift)%26)/eg;
print$_;
__END__
$shiftに負の値とかを指定したいなら、2行になるな。ようわ、
$shift=($shift%26+26)%26;
を加えると。
レスり忘れた。
>eclipseの頭の中を見てみたいw
つ@
>>acht
いろんな言語を使いこなせるわけではありませんよ。
言語は好きだからいろいろ見てはいますけど、知っているレベルです。
もちろんリファレンスは必須です。
実際には、手続き型の発展としてのオブジェクト指向と、関数型の2つだけを
抽象概念として理解してしまえば、ほとんどの言語はちょっとした適応に過ぎないと思います。
>>wiz
>s/([A-Za-z])/$_=ord$1,chr(0x41+($_&0x20)+(($_&~0x20)-0x41+$shift)%26)/eg;
残念ながらこういうコードは読む気がしません。
短く書けばいいってもんでもないでしょ。
人に読ませるようにわかりやすい記述をしてもなお短ければ素晴しいですけど。
までも私のコードもちょっとまだ冗長だなと思い縮めてみました。
どうもcase文のところがキモイんですが…
caesar = map . \n c ->
let a = case c of {_| elem c ['A'..'Z'] -> ord 'A';_| elem c ['a'..'z'] -> ord 'a';_ -> 0}
in if a /= 0 then chr (a + mod (ord c - a + n) 26) else c
def caesar(n)
self.split('').map do |ch| c = ch[0]
a = case c when *(?a..?z): ?a; when *(?A..?Z): ?A; else nil end
(a.nil? ? c : (a + (c - a + n) % 26)).chr
end .join
end
>人に読ませるようにわかりやすい記述をしてもなお短ければ素晴しいですけど。
(´Д`) ん~、世界観の違いが・・・
とりあえず読み方的には、
s/([A-Za-z])/$_=ord$1,chr(0x41+($_&0x20)+(($_&~0x20)-0x41+$shift)%26)/eg;
において、
[A-Za-z] - アルファベットの大文字小文字が処理対象
$_=ord$1, - まぁこれが読みにくいって事はないかなと・・・文字コードを求めてる。
chrの中身は、
0x41 -> 'A' - ASCII表で重要なのは暗記です
$_&0x20 - 大文字小文字判定の定石
$_&~0x20 - 大文字化の定石
rotate は定数を引いてから剰余を求めて定数を足す - 定石
つーことで非常に定石が詰まっているんだけど、まぁそんなの知るか
とか書かれると・・・な訳で。
文字数を数える定石とかもあるんだけど、まぁ定石なんて知ってる人は知ってる
つーだけのものなんでアレですかねぇ・・・
80286とか埋め込み系とかでないと上のは出てこないよね~・・・
まぁあえて蛇足しておくと、
if(('A' <= x) && (x <= 'Z'))
という条件式は複雑すぎるので、アレな方面だと
if((unsigned char)(x - 'A') < ('Z' - 'A'))
となるとか。下も十分読みやすいとなるかどうかな感じですかね。
とりあえず、具体的にはどの辺りが読みにくいのかを、参考までに
書いてくれると参考になるかなと。
忙しいからそんなのkakeるか、つー事なら無理にとは・・・
>$_&0x20 - 大文字小文字判定の定石
>$_&~0x20 - 大文字化の定石
主にこの2つがわかりませんでした。
なるほどそういうことか。
# $_を使い回すなよ…
# あとs//も知らない人にはさっぱりな構文だな
イディオム使うのはいいんだけど抽象化しようよ。
Ruby版にぱくってみた。
case文が消えてすっきりしました。
def caesar(n)
def a(c) (c & (?a - ?A)) + ?A end
def nth(c) (c &~ (?a - ?A)) - ?A end
alpha = (?A..?Z).to_a + (?a..?z).to_a
self.split('').map do |ch| c = ch[0]
(alpha.member?(c) ? (a(c) + (nth(c) + n) % 26) : c).chr
end .join
end
>主にこの2つがわかりませんでした。
まぁアレなテヒなので知らなくても問題ないけど知ってると得した気分に
なれるってことで。
># $_を使い回すなよ…
だってord($1)って何度も書くの面倒だし、変数名つけてもいいけど、
一応$_はテンポラリ変数な意味を持ってるような持っていないような、そんな
感じなので。
途中で役割変えたりはしてないので許してちょ。
あと、chrのところは書き順にも配慮があって、
chr(0x41+($_&0x20)+(($_&~0x20)-0x41+$shift)%26)
において、
chrの後に0x41+で始まるので'A'+と分かり、
($_&0x20)のところは大文字小文字フラグを足しこんでいるのと
[A-Za-z]より、大文字小文字を一度に扱いたいらしいことが分かり、
($_&~0x20)-0x41はもうアルファベットのインデックス化(A=0, B=1, ...)
の定石なので、インデックス化して$shiftだけずらすというのが分かり、
%26でぐるぐる回してる感じが分かるという仕組みです。
抽象化ってのはつまり0x41とかじゃ分かりにくいからord('A')にしろ
ってことですかね?
まぁできないこともないんだけど、いろいろ面倒なのよね。perlは。
定数の定義とかアレだし。どうやるのか忘れてる罠。
alpha_a = sub { ord('A'); }
とかやると定数だっけかな?subの前に&がいったっけか。(´Д`)
まーどうでも良いや。グーグル先生に聞けば分かるし。
ちなみにs///構文はなかなか気に入ってる今日この頃。
正規表現が非常に使いやすくて良し。な感じ。
>抽象化ってのはつまり0x41とかじゃ分かりにくいからord('A')にしろ
ってことですかね?
それもあるし操作に名前を付けるとかそういうことです。
効率化のフェーズで落とし込むのはいいんですが、その時にはコメント追加が必須になりますね。
コメントなしで意味が伝わればそれに越したことはないと思います。
Haskellのが一番わかりやすいw
↓これは、わかりにくかったけど。
シーザー暗号 (1)
2005年6月20日
http://eclipse.cspc.jp/perma/000075/
短くするなら、こんなのも
import System.Environment(getArgs)
import Char (isAlpha)
shift 'z' = 'a'
shift 'Z' = 'A'
shift ch |(isAlpha ch) = succ ch
| otherwise = ch
caesar n = map $ (!!((n`mod`26)+26)).(iterate shift)
main = do
[fname, ns] >= (putStrLn . caesar (read ns :: Int))
コードがくずれていたので再掲します。
import System.Environment(getArgs)
import Char (isAlpha)
shift 'z' = 'a'
shift 'Z' = 'A'
shift ch |(isAlpha ch) = succ ch
| otherwise = ch
caesar n = map $ (!!((n`mod`26)+26)).(iterate shift)
main = do
[fname, ns] <- getArgs
[readFile fname >>= (putStrLn . caesar (read ns :: Int))