シーザー暗号 (2)

| | コメント(10)

なんとなく以前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)

コメント(10)

acht :

色んな言語使いこなせてすごいよねぇ。
よくこんがらがらないことw
俺にはそんなに言語取得できませんわぁー。
eclipseの頭の中を見てみたいw

wiz@びみょう :

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;
を加えると。

wiz@びみょう :

レスり忘れた。

>eclipseの頭の中を見てみたいw
つ@

eclipse :

>>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

wiz@疲労か・・・ :

>人に読ませるようにわかりやすい記述をしてもなお短ければ素晴しいですけど。
(´Д`) ん~、世界観の違いが・・・

とりあえず読み方的には、

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るか、つー事なら無理にとは・・・

eclipse :

>$_&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

wiz@始めなきゃ :

>主にこの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///構文はなかなか気に入ってる今日この頃。
正規表現が非常に使いやすくて良し。な感じ。

eclipse :

>抽象化ってのはつまり0x41とかじゃ分かりにくいからord('A')にしろ
ってことですかね?

それもあるし操作に名前を付けるとかそういうことです。
効率化のフェーズで落とし込むのはいいんですが、その時にはコメント追加が必須になりますね。
コメントなしで意味が伝わればそれに越したことはないと思います。

Haskell勉強中 :

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))

Haskell勉強中 :

コードがくずれていたので再掲します。

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))

このブログ記事について

このページは、yuchが2005年10月 6日 00:26に書いたブログ記事です。

ひとつ前のブログ記事は「Programmer's Baton」です。

次のブログ記事は「趣味プロとか」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 4.01