LoginSignup
3
3

More than 5 years have passed since last update.

[Ruby基礎学習] メッセージの復号

Posted at

はじめに

今回はメッセージの復号に関する問題だった。
前回のチェックサムのやつは問題文がなかなか理解できなかったけど、
今回はすぐに理解できたので、それだけでちょっと楽だった。

問題

あるメッセージがテキストストリームとして符号化されており、
それを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

最後に

  • プログラマの考え方がおもしろいほど身につく本 問題解決能力を鍛えよう!
3
3
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3