'SAKURA' -> 'HZPFIZ' な暗号文を復号する


熱血!平成教育学院の今夜の放送の問題より。復号(暗号化も同じ)のためのスクリプトSqueakSmalltalk を使ったいくつかの方法で。

■対応する復号文字の ASCII コードを計算で求めて #collect:
| codeString |
codeString := 'HZPFIZ'.

^ codeString collect: [:chr |
   | asciiValue |
   asciiValue := $Z asciiValue - chr asciiValue + $A asciiValue.
   Character value: asciiValue]
=> 'SAKURA'
■あらかじめ作成したテーブルから復号文字を探して #collect:
| codeString tableDict alphabet |
codeString := 'HZPFIZ'.

tableDict := Dictionary new.
alphabet := Character alphabet asUppercase.
alphabet with: alphabet reversed do: [:key :val | tableDict at: key put: val].

^ codeString collect: [:chr | tableDict at: chr]
■ #translateWith: を用いて一括変換
| codeString tableArray alphabet start |
codeString := 'HZPFIZ'.

tableArray := Character allByteCharacters.
alphabet := Character alphabet asUppercase.
tableArray replaceFrom: (start := tableArray indexOf: $A) to: start+25 with: alphabet reversed.

^ codeString translateWith: tableArray
■ #translateWith: 版で、用いるテーブルの情報を必要最小限にして
| codeString tableArray |
codeString := 'HZPFIZ'.

tableArray := (Array new: $A asciiValue), Character alphabet asUppercase reversed.

^ codeString translateWith: tableArray


ここまでシンプルにできると、ワンライナで書いてもさほどの違和感はない。

'HZPFIZ' translateWith: (Array new: $A asciiValue), ($A to: $Z) reversed
=> 'SAKURA'


さらに短く。

'MVPPVGHF' translateWith: (Array new: 65), ($A to: $Z) reversed
=> 'NEKKETSU'


これで最短?

'HJFVZP' translateWith: (1 to: 65), ($A to: $Z) reversed   " => 'SQUEAK' "

こういうのは Perl and/or 正規表現が得意そう…なのですが、残念ながら私のほぼ無に等しい Perl 力ではワンライナで書くことはできませんでした。



コメント欄で nurse さんが Perl の tr/// 、s/// を使った例を教えてくださいました。ありがとうございます。

echo HZPFIZ | perl -pe 'tr/A-Z/ZYXWVUTSRQPONMLKJIHGFEDCBA/'
echo HZPFIZ | perl -pe 's/[A-Z]/chr 155-ord$&/ge'


前者については、Smalltalk の #reversed や Ruby の reverse のようなものが Perl にもあれば、もう一段、抽象度が上げられそうです。(…とは言っても、変数を使ったり、eval しなくてはいけなくなるので、一歩進んで二歩下がるwって感じでしょうか。って、ありますね。reverse 。Perl にも。当然。w)



さらに tociyuki さんが、このエントリーを受けて、PerlRuby でいろいろな方法で書いてくださっています。ありがとうございます。


私は Ruby で書いた次のものが理想像としてイメージしているのに近かったです。Range にもうちょっと融通が利いて、to_a しなくてよければよりベターなのですが…。

'HZPFIZ'.tr!("A-Z", ("A".."Z").to_a.reverse.join)

Perl のほうは、これがイメージに近いです。前述のとおり、eval が邪魔くさいですが…(^_^;)。

echo HZPFIZ | perl -pe 'eval qq{tr/A-Z/@{reverse A..Z}/};'

と書いたら、新しいコメントで、shinichiro_h さんRuby の [*"A".."Z"] という表現を教えていただきました。すばらしい。ただ、たしかに、表記上の冗長さが減る代わりに、新たな表現方法の解釈をしないといけなくなるので、総じて分かりやすさの面では一進一退…かもしれませんね。

'HZPFIZ'.tr!("A-Z", [*"A".."Z"].reverse!.join)

kounoike さんから、Golfed 'SAKURA' => 'HZPFIZ' | Typemiss.net にて、Perl ゴルファーの心得に添えて Perl の別解などをいただきました。ありがとうございます。(言われて気づいたのですが、上の SqueakSmalltalk の例は、ルール1を前提にしちゃっていますから PerlRuby の例と同列に扱ってはいけなかったですね…(^_^;)。まあ、ハンデだと思ってご勘弁を。)


で、いただいた別解はこちら。

#!perl
print+(A..CL)[-ord]for<>=~/./g


一瞬、なぜこれでうまく動くのか分かりませんでした。w SqueakSmalltalk で書くと、このような内容でしょうか。

'HZPFIZ' collect: [:chr |
   ($A to: (Character value: $A asciiValue + $Z asciiValue)) atWrap: chr asciiValue negated]

まとめ(とりあえず…。あと順不同。)

SqueakSmalltalk
'HJFVZP' translateWith: (1 to: 65), ($A to: $Z) reversed

(1 to: 65), が意味不明 and/or #translateWith: に渡すテーブルの構造を知らないとダメなのがダサい(ソース嫁?ってのもねぇ… orz)。

Perl
echo HZPFIZ | perl -pe 'eval qq{tr/A-Z/@{reverse A..Z}/};'

イメージどおりだが、いかんせん展開に eval 必須なのが玉に瑕。

Ruby
'HZPFIZ'.tr!("A-Z", [*"A".."Z"].reverse!.join)
'HZPFIZ'.tr!("A-Z", ("A".."Z").to_a.reverse.join)

to_a or not to_a ...。(あるいは、Range のバ〜カ ;-p)w あと、Smalltalk みたいに、String が Array 型なら(あるいは、Array を String の代わりに使えたら)よかったのに。