Pythonの組み込み実装のROT13を読む。

 アルファベットを任意の文字数ずらした暗号をシーザー暗号という。

シーザー暗号の中で13文字ずらしたものをとくにROT13と呼ぶ。

PythonではこのROT13が言語に組み込み実装として存在しており、thisモジュールはこのROT13でエンコードされている。

>>> print "hogefuga".decode("rot13")
ubtrshtn
>>> print "hogefuga".encode("rot13")
ubtrshtn
>>>

自明ではあるが上の出力でROT13においてはデコードとエンコードが同じ処理であることも確認できる。

組み込み実装として用意されているROT13のソースコードはthis.pyを見れば確認できる。

http://svn.python.org/view/python/trunk/Lib/this.py?view=markup

23 d = {}
24 for c in (65, 97):
25     for i in range(26):
26         d[chr(i+c)] = chr((i+13) % 26 + c)
27 
28 print "".join([d.get(c, c) for c in s])

23行目で空の辞書オブジェクトを生成している。

24行目ではついつい、

for c in (65, 97):

の部分を

for c in range(65, 97):

に空目してしまいがちだが

range()関数は使っていないので注意が必要。

65と97はASCII文字コードにおけるAaである。

chr()関数はASCII文字コードにおける整数を文字に変換する関数で、逆の処理をするのがord()関数。

  • chr()
>>> for c in (65, 97):
...   for i in range(26):
...     print chr(i+c),
...
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z
>>>
  • ord()
>>> for c in ('A', 'a'):
...   for i in range(26):
...     print ord(c)+i,
...
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
>>>

26行目の

d[chr(i+c)] = chr((i+13) % 26 + c)

d['A'] = 'N'

d['B'] = 'O'

d['C'] = 'P'

...

のようなことをしており{'A':'N', 'B':'O', 'C':'P' ...(以下略)}という辞書を生成している。

13というのがずらす文字数で0から25の整数である変数iに13を足したものの剰余をとり65もしくは97を足してchr()関数を実行することで、うまくシーザー暗号を表現している。

この13をnにすることでより汎用性の高いプログラムになりそうである。

最後のprint "".join([d.get(c, c) for c in s])だが、

"".join()はリストから文字列への変換操作である。

>>> "".join(['h', 'o', 'g', 'e'])
'hoge'

リストは内包表記を使っている。d.get(c, c)はd[c]があればその値を返しなければcをそのまま返すという意味である。

>>> d = {'A':'N', 'B':'O',}
>>> d.get('A')
'N'
>>> d.get('N')
>>> d.get('N', 'N')
'N'

引数に文字列sと整数nをとり、文字列sをn文字シフトした文字列を返す関数caesar()は次のように書ける。

def caesar(s, n):
  d = {}
  for c in (65, 97):
    for i in range(26):
      d[chr(i+c)] = chr((i+n) % 26 + c)

  return "".join([d.get(c, c) for c in s])

例えばAOJのこの問題(http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0017)の仕様に合わせるのであれば、

#!/usr/bin/env python2
# coding: utf-8

def caesar(s, n):
  d = {}
  for c in (65, 97):
    for i in range(26):
      d[chr(i+c)] = chr((i+n) % 26 + c)

  return "".join([d.get(c, c) for c in s])

while True:
  try:
    encrypt_txt = raw_input()
    for i in range(26):
      t = caesar(encrypt_txt, i)
      if  "the" in t or "this" in t or "that" in t:
        print t

  except EOFError:
    break

こんな風に使える。