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

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

# 問題

```
あるメッセージがテキストストリームとして符号化されており、
それを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回直さないといけなくなって非常に拡張性に欠けるなぁと思って別の方法を探すことにした。

```rb
# アルファベットと記号
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回直すよりはいいなぁと思う。

```rb
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
```

```rb
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
```

```rb
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
```

メッセージも正しく復号された。

```rb
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というメソッドは全く使ってないことがわかったので消した。
消した後が以下。

```rb
module NumVal
	ALPHABET_L = 27
	ALPHABET_S = 27
	MARKS = 9
end

module Char
	ALPHABET_L = [*'A'..'Z']
	ALPHABET_S = [*'a'..'z']
	MARKS = ["!", "?", ",", ".", " ", ";", "\"", "'"]
end
```

```rb
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
```

```rb
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
```

# 最後に

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