はじめに
この記事は Rubyプログラミング問題にチャレンジ! -改訂版・チェリー本発売記念- Advent Calendar 2021 の23日目の記事となります。
プルリクエストのリンク
方針
- KISSの原則 に則り、問題のテストが通れば十分とする(変に拡張性を考えない)
- 点字の業務知識に沿ったコードを心がける
作戦
問題のREADME を眺めてみます。
点字1字について
- 母音と子音の組み合わせで表現できる
- や行、わ、んは例外
が見えてきます。
- 入力にアルファベットを取り点字1字を表すクラス
を用意して、
- 上記クラスを複数持つデータ構造
を題意の点字文字列に変換できれば十分であると考えました。
入力にアルファベットを取り点字1字を表すクラス
こんなクラスを考えます。
class Tenji
def initialize(str)
@str = str
end
end
母音と子音の組み合わせで表現できる、裏を返せば入力文字列を母音と子音に変換する必要があります。
- 「あ行」と「ん」は1文字
- それ以外は2文字
- あ行には子音がない
- んは通常子音
であることから入力文字列を母音と子音に変換するメソッドを次のように定義しました。
class Tenji
...
attr_reader :str
# 母音
def vowel
if str.length == 2
str[1]
elsif str == "N"
nil
elsif str.length == 1
str[0]
end
end
# 子音
def consonant
if str.length == 2
str[0]
elsif str == "N"
str[0]
elsif str.length == 1
nil
end
end
...
end
次に点字1字をどういうデータ構造で表すか考えます。
全視情協:点字とは - 点字のしくみ を見てみると、
- 母音は1,2,4
- 子音は3,5,6
で表すことがわかります。
データ構造として配列、ハッシュなどが考えられますがここではハッシュを採用します。
# あ
{
1 => "o", 4 => "-",
2 => "-", 5 => "-",
3 => "-", 6 => "-",
}
のように書くことでコードの見た目と点字の見た目を一致させることができるからです。点字は1始まりなので0始まりである配列で表現するのは毎回0を考慮する必要があるとも考えました。ハッシュを採用したことで
def a
{
1 => "o", 4 => "-",
2 => "-", 5 => "-",
3 => "-", 6 => "-",
}
end
def k
{
5 => "-",
3 => "-", 6 => "o",
}
end
と定義すると「か」が
a.merge(k)
# => => {1=>"o", 4=>"-", 2=>"-", 5=>"-", 3=>"-", 6=>"o"}
と表現できます。#send を使えば先程の母音/子音の表現と合わせて
send(vowel.downcase).merge(send(consonant.downcase))
で表せそうです。なのでひたすら母音と子音の小文字のメソッドを定義していきます。
- や行、わ、んは例外
を考えるとそれらは ya
wa
といったメソッドで表します。
def ya
{
1 => "-", 4 => "o",
2 => "-", 5 => "-",
3 => "o", 6 => "-",
}
end
def wa
{
1 => "-", 4 => "-",
2 => "-", 5 => "-",
3 => "o", 6 => "-",
}
end
また「ん」は子音がな行と重複するため定数で表現します。
N =
{
1 => "-", 4 => "-",
2 => "-", 5 => "o",
3 => "o", 6 => "o",
}
以上から入力文字列を点字のハッシュで表すのは下記のクラスでできそうです。
抜粋
class Tenji
attr_reader: str
def initialize(str)
@str = str
end
def to_h
if consonant.nil? # あ行
send(vowel.downcase)
elsif %w[K S T N H M R].include?(consonant) && vowel
send(vowel.downcase).merge(send(consonant.downcase))
elsif %w[Y W].include?(consonant)
send(str.downcase)
elsif %w[N].include?(consonant) && vowel.nil? # ん
N
end
end
...
end
テストも書いておきました。
https://github.com/JunichiIto/tenji-maker-challenge/blob/a1fa0e32fb1dd8cb55ca52da4f268a730e6a20b6/test/tenji_test.rb
網羅しつつケースを減らすために 直交表 という考えをベースに a, i, u, e, o, ka, si, tu, ne, ho...ya, yu, yo, wa, n のようなテストケースとなってます。
点字クラスを複数持つデータ構造
配列で良さそうです。下記のような形で表現することを考えます。
[Tenji.new("A"), Tenji.new("HI"), Tenji.new("RU")]
入力文字列を #split してやれば良さそうです。
tenjis = text.split(" ").map { |str| Tenji.new(str) }
求めたい文字列は A HI RU
の場合下記です。
"o- o- oo\n-- o- -o\n-- oo --\n"
なのでTenjiを下記のような配列に変換します。
[["o-", "o-", "oo"],
["--", "o-", "-o"],
["--", "oo", "--"]]
一般化するとこうですね。
tenji_matrix = tenjis.map(&:to_h).map do |tenji_hash|
[
tenji_hash[1] + tenji_hash[4],
tenji_hash[2] + tenji_hash[5],
tenji_hash[3] + tenji_hash[6],
]
end
文字列操作しやすいように下記のように変換します。
[["o-", "--", "--"],
["o-", "o-", "oo"],
["oo", "-o", "--"]]
行列としてみると上の転置行列とみなせるので #transpose が使えそうです。
tm = tenji_matrix.transpose
あとは各要素を " "
で繋げて、改行すれば良いから
tm.map { |arr| arr.join(" ") }.join("\n")
でできそうです。
以上まとめると下記となります。
require "tenji"
class TenjiMaker
def to_tenji(text)
tenjis = text.split(" ").map { |str| Tenji.new(str) }
tenji_matrix = tenjis.map(&:to_h).map do |tenji_hash|
[
tenji_hash[1] + tenji_hash[4],
tenji_hash[2] + tenji_hash[5],
tenji_hash[3] + tenji_hash[6],
]
end
tenji_matrix.transpose.map { |arr| arr.join(" ") }.join("\n")
end
end
伊藤さんにメッセージ
うだつの上がらないエクセル設計書/エビデンス系SEだったわたしが、2018年に職業プログラマへ転職しご飯を食べれているのはチェリー本のおかげです。改訂版も楽しく読ませていただきます(と言いつつまだ買っていないです)。