はじめに
今回はメッセージの復号に関する問題だった。
前回のチェックサムのやつは問題文がなかなか理解できなかったけど、
今回はすぐに理解できたので、それだけでちょっと楽だった。
問題
あるメッセージがテキストストリームとして符号化されており、
それを1文字ずつ読み込む。
ストリームの中身はカンマで区切られた整数値の羅列で、
個々の数値はC++のint型で扱える範囲の正の数である。
しかし、その数値がどの文字を表しているかは、
現在の復号モードによって変わる。
モードは3種類あり、大文字・小文字・記号のいずれかになる。
大文字モードでは、個々の数値がアルファベットの大文字を表す。
数値を27で割った余りがアルファベットの文字に対応する(1をAとする)。
たとえば、入力が143なら出力はHになる。
143を27で割った余りは8で、アルファベットの8番目の文字はHだからである。
小文字モードの場合も同じだが、アルファベットの小文字を表している。
数値を27で割った余りがアルファベットの文字に対応する(1をaとする)。
たとえば、入力が56なら出力はbになる。
56を27で割った余りは2で、アルファベットの2番めの文字はbだからである。
記号モードの場合は数値を9で割った余りを使う。
この余りを下の表にしたがって変換する。
たとえば、19は感嘆符になる。
19を9で割った余りは1だからである。
メッセージが始まった時点での復号モードは、大文字モードである。
剰余演算(モードによって、27あるいは9のいずれかを使う)の結果が0になるたびに、
復号モードが切り替わる。
現在のモードが大文字の場合は小文字モードへ、
現在のモードが小文字の場合は記号モードへ、
記号モードの場合は再び大文字モードに戻る。
解答
問題文にC++のint型の範囲がどうのとあるが、とりあえず無視することにした。
Rubyは整数はFixnumとBignumがあるらしい。
Fixnumに収まりきらなくなったら自動的にBignumになるみたいだ。
私の環境で調べてみたら、4611686018427387903
まではFixnumだったけれど、
4611686018427387904
からはBignumになった。
それはひとまず置いておいて、まずは問題を整理。
12,23,34
のような形で数値がカンマ区切りで存在し、
その数値の剰余が以下の表のように対応している。
大文字アルファベット
A | B | C | D | E | F | G | H | I | J | K | L | M | N |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
O | P | Q | R | S | T | U | V | W | X | Y | Z |
---|---|---|---|---|---|---|---|---|---|---|---|
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
小文字アルファベット
a | b | c | d | e | f | g | h | i | j | k | l | m | n |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
o | p | q | r | s | t | u | v | w | x | y | z |
---|---|---|---|---|---|---|---|---|---|---|---|
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
記号
! | ? | , | . | 半角スペース | ; | " | ' |
---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
文字種のモードは、剰余が「0」だと
大文字アルファベット → 小文字アルファベット → 記号 → 大文字アルファベット……
というように変化する。
21,27,28
の場合、以下のようになり、復号後のメッセージはUa
となる。
21 → 剰余が21 → U
27 → 剰余が0なので文字種のモードが大文字アルファベットから小文字アルファベットに切り替わる
28 → 剰余が1 → a
テストデータとして
33,54,21,68,38,42,45,69,34,28,46,62,90,27,28,81,31,54,32,46,21
を用意した。
復号すると、Funkorogashi!Desu
になるはずである。
条件を満たすように最初に書いたのが以下。
テストデータは正しく復号される。
でもcase文が多すぎだと思った。
将来アルファベットや記号だけじゃなくてその他の文字が加わった時に、
case文を3回直さないといけなくなって非常に拡張性に欠けるなぁと思って別の方法を探すことにした。
# アルファベットと記号
ALPHABET_L = [*'A'..'Z']
ALPHABET_S = [*'a'..'z']
MARKS = ["!", "?", ",", ".", " ", ";", "\"", "'"]
# モード一覧
MODE_ALPHABET_L = "1"
MODE_ALPHABET_S = "2"
MODE_MARKS = "3"
def decode_message(message)
message_array = message.split(",")
decoded_message = ""
mode = MODE_ALPHABET_L
numval = 27
message_array.length.times do |i|
value = message_array[i].to_i % numval
if value != 0
decoded_message << get_char(mode, value)
else
mode = check_mode(mode)
numval = check_numval(mode)
end
end
decoded_message
end
def check_mode(mode)
case mode
when MODE_ALPHABET_L then MODE_ALPHABET_S
when MODE_ALPHABET_S then MODE_MARKS
when MODE_MARKS then MODE_ALPHABET_L
end
end
def check_numval(mode)
case mode
when MODE_ALPHABET_L then 27
when MODE_ALPHABET_S then 27
when MODE_MARKS then 9
end
end
def get_char(mode, value)
case mode
when MODE_ALPHABET_L then ALPHABET_L[value - 1]
when MODE_ALPHABET_S then ALPHABET_S[value - 1]
when MODE_MARKS then MARKS[value - 1]
end
end
puts decode_message("33,54,21,68,38,42,45,69,34,28,46,62,90,27,28,81,31,54,32,46,21")
試行錯誤した結果こうなった。
最初よりはそれぞれの役割が分かれたので大分マシになった気がするけれどどうなんだろう。
case文3回直すよりはいいなぁと思う。
module Mode
ALPHABET_L = 1
ALPHABET_S = 2
MARKS = 3
end
module NumVal
ALPHABET_L = 27
ALPHABET_S = 27
MARKS = 9
end
module Char
ALPHABET_L = [*'A'..'Z']
ALPHABET_S = [*'a'..'z']
MARKS = ["!", "?", ",", ".", " ", ";", "\"", "'"]
end
class ModeBase
include Mode
include NumVal
include Char
# モードを返す
def get_mode
end
# 剰余計算に使う値を返す
def get_numval
end
# 剰余に対応する文字を返す
def get_char(val)
end
end
class AlphabetLarge < ModeBase
def get_mode
Mode::ALPHABET_L
end
def get_numval
NumVal::ALPHABET_L
end
def get_char(val)
Char::ALPHABET_L[val - 1]
end
end
class AlphabetSmall < ModeBase
def get_mode
Mode::ALPHABET_S
end
def get_numval
NumVal::ALPHABET_S
end
def get_char(val)
Char::ALPHABET_S[val - 1]
end
end
class Marks < ModeBase
def get_mode
Mode::MARKS
end
def get_numval
NumVal::MARKS
end
def get_char(val)
Char::MARKS[val - 1]
end
end
class DecodeMessage
def initialize()
@alphabet_l = AlphabetLarge.new
@alphabet_s = AlphabetSmall.new
@marks = Marks.new
end
def decode_message(code)
code_array = code.split(",")
decoded_message = ""
mode = @alphabet_l
code_array.length.times do |i|
value = code_array[i].to_i % mode.get_numval
if value != 0
decoded_message << mode.get_char(value)
else
mode = check_mode(mode)
end
end
decoded_message
end
private
def check_mode(mode)
case mode
when @alphabet_l then @alphabet_s
when @alphabet_s then @marks
when @marks then @alphabet_l
end
end
end
メッセージも正しく復号された。
decode_message = DecodeMessage.new
puts decode_message.decode_message("33,54,21,68,38,42,45,69,34,28,46,62,90,27,28,81,31,54,32,46,21")
# ⇛ Funkorogashi!Desu
もう一度全体を見直してたら、Modeモジュールとget_modeというメソッドは全く使ってないことがわかったので消した。
消した後が以下。
module NumVal
ALPHABET_L = 27
ALPHABET_S = 27
MARKS = 9
end
module Char
ALPHABET_L = [*'A'..'Z']
ALPHABET_S = [*'a'..'z']
MARKS = ["!", "?", ",", ".", " ", ";", "\"", "'"]
end
class ModeBase
include NumVal
include Char
# 剰余計算に使う値を返す
def get_numval
end
# 剰余に対応する文字を返す
def get_char(val)
end
end
class AlphabetLarge < ModeBase
def get_numval
NumVal::ALPHABET_L
end
def get_char(val)
Char::ALPHABET_L[val - 1]
end
end
class AlphabetSmall < ModeBase
def get_numval
NumVal::ALPHABET_S
end
def get_char(val)
Char::ALPHABET_S[val - 1]
end
end
class Marks < ModeBase
def get_numval
NumVal::MARKS
end
def get_char(val)
Char::MARKS[val - 1]
end
end
class DecodeMessage
def initialize()
@alphabet_l = AlphabetLarge.new
@alphabet_s = AlphabetSmall.new
@marks = Marks.new
end
def decode_message(code)
code_array = code.split(",")
decoded_message = ""
mode = @alphabet_l
code_array.length.times do |i|
value = code_array[i].to_i % mode.get_numval
if value != 0
decoded_message << mode.get_char(value)
else
mode = check_mode(mode)
end
end
decoded_message
end
private
def check_mode(mode)
case mode
when @alphabet_l then @alphabet_s
when @alphabet_s then @marks
when @marks then @alphabet_l
end
end
end
最後に
- プログラマの考え方がおもしろいほど身につく本 問題解決能力を鍛えよう!