この記事は、Rubyプログラミング問題にチャレンジ! -改訂版・チェリー本発売記念- Advent Calendar 2021 の22日目の記事です。
1. ルールとか 🔰
ルールや、他の方の回答はこちらにあります。
2. 成果物 🍒
ソースコード 📄 ( クリックで開きます )
require 'matrix'
# 180度回転
def rotate180(matrix)
Matrix.rows(matrix.to_a.reverse.map(&:reverse))
end
# 上下反転
def reverse(matrix)
Matrix.rows(matrix.to_a.reverse)
end
# 一番下に下げる
def down(tenji_to_a)
return Matrix.rows(tenji_to_a) unless tenji_to_a[2].sum.zero?
down([
[0, 0],
tenji_to_a[0],
tenji_to_a[1]
])
end
class TenjiMaker
class << self
attr_accessor :boin, :siin
end
@boin = {
'A' => Matrix[[1, 0], [0, 0], [0, 0]],
'I' => Matrix[[1, 0], [1, 0], [0, 0]],
'U' => Matrix[[1, 1], [0, 0], [0, 0]],
'E' => Matrix[[1, 1], [1, 0], [0, 0]],
'O' => Matrix[[0, 1], [1, 0], [0, 0]]
}
@boin['N'] = rotate180(@boin['E'])
@siin = {
'K' => rotate180(@boin['A']),
'S' => rotate180(@boin['I']),
'T' => rotate180(@boin['O']),
'N' => reverse(@boin['A']),
'H' => rotate180(@boin['U']),
'M' => rotate180(@boin['E']),
# 'Y'
'R' => Matrix[[0, 0], [0, 1], [0, 0]]
# 'W'
}
def exceptional(bo, si)
if si == 'Y'
down(self.class.boin[bo].to_a) + Matrix[[0,1], [0,0], [0,0]]
elsif si == 'W'
down(self.class.boin[bo].to_a)
else
nil
end
end
def to_tenji(text)
ary = []
text.split.each do |romaji|
bo = romaji.split('')[-1]
si = romaji.split('')[-2]
# 例外的な場合 (今回は、やゆよわを)
ex = exceptional(bo, si)
if ex
ary << ex
next
end
# 通常 (母音 + 子音 or 子音のみ)
if si
ary << self.class.boin[bo] + self.class.siin[si]
else
ary << self.class.boin[bo]
end
end
# 表示
output = ''
text_num = text.split.length
3.times do |y|
text_num.times do |x|
output << (ary[x].element(y, 0).zero? ? '-' : 'o')
output << (ary[x].element(y, 1).zero? ? '-' : 'o')
output << ' '
end
output = output.chop + "\n"
end
output.chomp
end
end
3. 実装アイデア 💡
下の図を見てもらうと分かる通り
点字は、母音の位置の点と、子音の位置の点の足し算になっています。
最初に思いつく方法として配列での実装がありましたが、今回は行列を扱うことが出来る Matrix
を使用します。( Matrix
では行列の足し算が簡単に出来るという点のみで採用したので、配列で実装しても、行列の足し算の実装があるくらいで、ほとんど変わりません。)
4. 解説 🧬
では、
今回の実装のメインの部分を解説していきましょう!
まず、こちらがソースコードです。( 下にGitHub のリンクもあるので、見やすい方で見て下さい )
def to_tenji(text)
ary = []
text.split.each do |romaji|
bo = romaji.split('')[-1]
si = romaji.split('')[-2]
# 例外的な場合 (今回は、やゆよわを)
ex = exceptional(bo, si)
if ex
ary << ex
next
end
# 通常 (母音 + 子音 or 子音のみ)
if si
ary << self.class.boin[bo] + self.class.siin[si]
else
ary << self.class.boin[bo]
end
end
# 表示
output = ''
text_num = text.split.length
3.times do |y|
text_num.times do |x|
output << (ary[x].element(y, 0).zero? ? '-' : 'o')
output << (ary[x].element(y, 1).zero? ? '-' : 'o')
output << ' '
end
output = output.chop + "\n"
end
output.chomp
end
https://github.com/stonesaw/tenji-maker-challenge/blob/main/lib/tenji_maker.rb#L82
4.1. ローマ字ごとに区切る
まず、to_tenji
メソッドの引数 text
には
'A HI RU'
や 'HI YO KO'
など、スペース区切りのローマ字が渡されます。
なので、
text.split.each do |romaji|
# ...
end
とすることで、変数 romaji
に、'A'
, 'HI'
, 'RU'
などの文字列が入って、ローマ字ごとに操作ができるようになります。
4.2. 母音と子音で区切る
次は、こちらのコードです。
bo = romaji.split('')[-1]
si = romaji.split('')[-2]
.split('')
は、文字列を1文字ずつに分解して、配列を返します。
例えば、'KA'
だと... ['K', 'A']
、 'U'
だと... ['U']
といった具合になります。
そして、ちょっとしたテクニックなんですが
split
で分割してできた配列の 1番後ろを取ると、母音を
split
で分割してできた配列の 後ろから2番目を取ると、子音を
取ることができます。
配列の後ろから ([-1]
, [-2]
) 取ってあげることで、
母音だけの場合 (AIUEO
)でも、正しく取得することが出来ます。
4.3. 行列の足し算
そして次は、
通常 (母音 + 子音 or 子音のみ) の場合か、
特殊な場合 (今回は、やゆよわを) に分かれます。
4.3-a. 普通の場合 (母音 + 子音 or 子音のみ)
実際のコードと順番が前後しますが、簡単なので普通の場合から解説していきます。
まず、self.class.boin
や self.class.siin
に母音や子音の行列データを保存しておきます。
@boin = {
'A' => Matrix[[1, 0], [0, 0], [0, 0]],
'I' => Matrix[[1, 0], [1, 0], [0, 0]],
...
}
与えられたローマ字に、子音があるときには、子音 + 母音
母音のみのときには、母音そのまま
を 配列 ary
に保存しておきます。
if si
ary << self.class.boin[bo] + self.class.siin[si]
else
ary << self.class.boin[bo]
end
4.3-b. 特殊な場合 (今回は、やゆよわを)
今回は特別な場合が、やゆよわを
くらいだったので、決め打ちで行列として変数を持たせておいても良かったかもしれませんが、👇下の画像を愚直に実装してみました。
まず、メソッド down
は点字を1番下へ移動させるためのメソッドで、再帰を使って実装しています。
def down(tenji_to_a)
return Matrix.rows(tenji_to_a) unless tenji_to_a[2].sum.zero?
down([
[0, 0],
tenji_to_a[0],
tenji_to_a[1]
])
end
また、拡張しやすいように、特別な場合を扱うメソッド exceptioanl
を作成しました。
def exceptional(bo, si)
if si == 'Y'
down(self.class.boin[bo].to_a) + Matrix[[0,1], [0,0], [0,0]]
elsif si == 'W'
down(self.class.boin[bo].to_a)
else
nil
end
end
メソッド exceptional
は、特別な場合だったら Matrix
を返して、特別な場合でなかったら nil
を返すようにしています。
ex = exceptional(bo, si)
if ex
ary << ex
next
end
4.4. 表示する
最後は、いい感じに表示させれば完成です。
# ary = [
# Matrix[...],
# Matrix[...],
# ...
# ]
output = ''
text_num = text.split.length
3.times do |y|
text_num.times do |x|
output << (ary[x].element(y, 0).zero? ? '-' : 'o')
output << (ary[x].element(y, 1).zero? ? '-' : 'o')
output << ' '
end
output = output.chop + "\n"
end
output.chomp # メソッドの最後 ( return output.chomp と同じ )
5. 感想 📈
自分は、まともに記事を書くのは初めてで、書き方・まとめ方にとても苦労しました。
また、最近はJavaScript系を触っていたので、いろいろ調べながら書いていたら、以外と時間がかかってしまいました...
@jnchito さん とても良い企画をありがとうございました! 記事を書く機会にもなりました!
自分も、Matz江市民として、Rubyist として、Rubyを盛り上げていきたいです!
少し宣伝みたいになってしまうのですが、
今自分が参加している Smalruby というプロジェクトもぜひよろしくお願いします!小中学生を対象にした、Rubyも書ける ブロック プログラミング アプリです!