状態遷移

状態遷移を使って簡単な(適当な)言語を進めるプログラム書きました。

# encoding: cp932

STATE_TRANSITION_TABLE = {
  "Q1" => ["Q1","Q2","Q3"],
  "Q2" => ["Q2","A1","Q4"],
  "Q3" => ["Q3","Q4","Q5"],
  "Q4" => ["Q4","A2","Q6"],
  "Q5" => ["Q5","Q6","A4"],
  "Q6" => ["Q6","A3","A5"],
}
MESSAGE = {
  "Q1" => "静(1)と動(2)ならどちらを選ぶ",
  "Q2" => "コーヒー党(1)?それとも紅茶当(2)?",
  "Q3" => "記号(1)と言語(2)どちらを選ぶ?",
  "Q4" => "カレーは大好き?もちろん(1) それほど(2)",
  "Q5" => "規律(1)と自由(2)どちらが重要?",
  "Q6" => "古いものに勝ちを見出すタイプ?はい(1) いいえ(2)",
  "A1" => "次はjavaを学習しましょう",
  "A2" => "Haskellがあなたを待っています",
  "A3" => "次はLispへ進みましょう",
  "A4" => "やっぱりRubyですね",
  "A5" => "Python"
}

state = "Q1"

while state[0] == "Q"
  puts(MESSAGE[state])
  state = STATE_TRANSITION_TABLE[state][gets().to_i] ||
          STATE_TRANSITION_TABLE[state][0]
end

puts(MESSAGE[state])

ここで指示通りに実行していくと、問題なく状態遷移してくれます。指示通りなら。例えば、実行結果を見ると

静(1)と動(2)ならどちらを選ぶ
6
静(1)と動(2)ならどちらを選ぶ
-1
記号(1)と言語(2)どちらを選ぶ?

状態Q1から始まります。1つめの入力は6です。でも、6なんて指示してないので状態は遷移せず同じ質問が繰り返されています。次は-1と入力。すると質問が「記号(1)と言語(2)どちらを選ぶ?」となりました。これは状態Q3です。STATE_TRANSITION_TABLEを見ると、 "Q1" => ["Q1","Q2","Q3"] です。-1を入力したことで、本来2の入力があった時の遷移状態Q3へ移動してしまってます。これは問題。

なので、-1など負の値を受け付けないように修正しました。puts(MESSAGE[state])の下に以下を追加しました。

  puts(MESSAGE[state])
  i = gets().to_i()
  if i<0 || i>= STATE_TRANSITION_TABLE[state].size
    i=0
  end

状態遷移の利点は、後に状態の追加や変更に対して配列の部分を修正するだけでいいこと。

hashの格納順番

ハッシュ表を作る際、配列を使って実装した。

HASH_SIZE = 11
TABLE = Array.new(HASH_SIZE)

def set(key,val)
  #ハッシュをして、ハッシュ表にデータを格納
end

set("hello","world")
set("今日は","赤ちゃん")
set("竹やぶ","焼けた")
set("テレビ","ラジオ")
set("ruby","red")
set("sapphire","blue")
set("121","11")
set("富士","オーム")
set("","見頃")

これを実行すると

[["hello", "world"],
 ["ruby", "red"],
 ["121", "11"],
 ["人", "見頃"],
 ["今日は", "赤ちゃん"],
 ["富士", "オーム"],
 ["竹やぶ", "焼けた"],
 ["テレビ", "ラジオ"],
 ["sapphire", "blue"]]

設定順通りにはデータが格納されていない。


今まで使っていたhashだと

table = {}
table["hello"] ="world"
table["今日は"] ="赤ちゃん"
table["竹やぶ"] ="焼けた"
table["テレビ"] ="ラジオ"
table["ruby"] ="red"
table["sapphire"] ="blue"
table["121"] ="11"
table["富士"] ="オーム"
table[""] ="見頃"

実行すると

{"hello"=>"world",
 "今日は"=>"赤ちゃん",
 "竹やぶ"=>"焼けた",
 "テレビ"=>"ラジオ",
 "ruby"=>"red",
 "sapphire"=>"blue",
 "121"=>"11",
 "富士"=>"オーム",
 ""=>"見頃"}

となり、設定順に格納されている。キーを基にハッシュ関数に書けると設定順にはならないので、別の配列などで順番を保持するものを作っておくこと。

ensure使い慣れん

例外処理でbeginとrescueを使っていたのですが、ensureを使いなれてないことがわかりました。ensureは例外の有無にかかわらずbegin式の最後に記述する処理を実行するもの。

今回は暗号処理をしたものには必ずresetメソッドを呼び出すために使いました。

def encrypt(file, pass)
  enc = OpenSSL::Cipher::AES256.new("CBC")
  enc.encrypt()
  enc.pkcs5_keyivgen(pass)
  begin
    File.open(file, "rb") do |fin|
      File.open("#{file}.sec", "wb") do |fout|
        while buff = fin.read(8000)
          fout.write(enc.update(buff))
        end
        fout.write(enc.final())
      end
    end
  ensure
    enc.reset()
  end
end

File.openでエラーが出た場合は、本文のほうで例外処理をしてあります。

    begin
      encrypt(arg, pass)
      puts("#{arg}を暗号化したファイル#{arg}.secを作成しました。")
    rescue
      puts("#{arg}の暗号化に失敗しました。")
    end

Rubyとコンパイル

コンパイルについて会話中に出てきたので、おさらいしてみた。

Ruby1.8はインタープリタ方式で構文木が生成された時点で実行。コンパイルはない。
javaの場合、構文木生成後にコンパイルバイトコードを生成)して実行プログラムを生成するコンパイラ方式。実行する時はVMを使う。
Ruby1.9の場合、構文木生成後コンパイルしてVMで実行する。実行プログラムは作らない。

暗号化と複合化

ライブラリopensslを使ってファイルを暗号化・複合化するプログラムを書きました。

暗号化

  enc = OpenSSL::Cipher::AES256.new("CBC")
  enc.encrypt()
  enc.pkcs5_keyivgen(pass)
  File.open(file, "rb") do |fin|
    File.open("#{file}.sec", "wb") do |fout|
      while buff = fin.read(8000)
        fout.write(enc.update(buff))
      end
      fout.write(enc.final())
    end
  end
  enc.reset()
  1. 暗号オブジェクトを生成
  2. encrypt()で暗号化の準備
  3. pkc5_keyivgen(pass)でパスワードからivとキーを生成
  4. read(8000)で8000文字のbuffブロックを作る
  5. update(buff)で暗号化
  6. ブロックに残っているデータを暗号化
  7. reset()でivとキーなど複合の為に必要な情報を消去

以上の順で暗号化を行う。

複合化

  enc = OpenSSL::Cipher::AES256.new("CBC")
  enc.decrypt()
  enc.pkcs5_keyivgen(pass)
  File.open(file, "rb") do |fin|
    File.open("#{file}.plain", "wb") do |fout|
      while buff = fin.read(8000)
        fout.write(enc.update(buff))
      end
      fout.write(enc.final())
    end
  end
  enc.reset()

暗号化と違う点は

  • decrypt()で複合化の準備

一点のみなので、とても楽だった。

暗号

データを特定のサイズを持ったブロック単位に分割し、パスワード(キー)を元にしたデータを適用して暗号文を求める。複数のブロックを扱う手法があり、CBCやECB等のモードがある。

CBCモードの場合

  • IVと最初のブロックの排他論理和をとり、キーと一緒に暗号化関数にかけて暗号化データを生成。
  • できた暗号化データと次のブロックの排他論理和をとり、キーと一緒に暗号化関数にかけて暗号化データを作成する。
  • 以降、ブロックが尽きるまで繰り返す。ブロック長に足りないデータは用意されているパディングデータを用いてブロック長に合わせる。


AESを利用して、引数で指定したファイルをコンソールから入力したパスワードで暗号化するプログラムを書く。
>>>編集中