概要
今回のやることは、入力された文字を点字で返すことです。
例えばA HI RU
が入力されたとき、次のように返すように。
\begin{matrix}
o & - \\
- & - \\
- & - \\
\end{matrix}
\qquad
\begin{matrix}
o & - \\
o & - \\
o & o \\
\end{matrix}
\qquad
\begin{matrix}
o & o \\
- & o \\
- & - \\
\end{matrix}
点字の規則性
点字には規則性があり、2×3の点のうち次のように上段と中段左の3点で母音を表現できます。
A = \begin{matrix}
o & - \\
- & - \\
- & - \\
\end{matrix}
\qquad
I = \begin{matrix}
o & - \\
o & - \\
- & - \\
\end{matrix}
\qquad
U = \begin{matrix}
o & o \\
- & - \\
- & - \\
\end{matrix}
\qquad
E = \begin{matrix}
o & o \\
o & - \\
- & - \\
\end{matrix}
\qquad
O = \begin{matrix}
- & o \\
o & - \\
- & - \\
\end{matrix}
そして、残りの中段右と下段の3点で子音を表現できます。
K = \begin{matrix}
- & - \\
- & - \\
- & o \\
\end{matrix}
\qquad
S = \begin{matrix}
- & - \\
- & o \\
- & o \\
\end{matrix}
\qquad
T = \begin{matrix}
- & - \\
- & o \\
o & - \\
\end{matrix}
\qquad \\
N = \begin{matrix}
- & - \\
- & - \\
o & - \\
\end{matrix}
\qquad
H = \begin{matrix}
- & - \\
- & - \\
o & o \\
\end{matrix}
\qquad
M = \begin{matrix}
- & - \\
- & o \\
o & o \\
\end{matrix}
\qquad
R = \begin{matrix}
- & - \\
- & o \\
- & - \\
\end{matrix}
\qquad
例えばHI
の時は子音と母音を組み合わせて次のようになります。
HI = \begin{matrix}
o & - \\
o & - \\
o & o \\
\end{matrix}
\qquad \\
小さい文字(ッ
, ャ
, ュ
, ョ
)を含む文字や、ヲ
など、規則に当てはまらないものもあるのですが、今回は考慮しなくていいのでありがたく無視します。
そして、YA, YU, YO, WA, N
だけ考慮が必要なので例外として定義することとします。定義はロジックのほうで記載します。
ロジックの解説
今回の条件では入力の1音1音が1つの点字に対応します。機械的に処理するために入力に対してそれぞれどこを塗ればいいのかというのを数値化しておきましょう。
点字の6つ点のエリアは塗るか塗らないかの2通りなので0, 1
の2進数(bit)で表現できます。
ファイルのパーミッション設定の時にrwxr-xr-x
にするのに755
と指定したりしますよね。数値だけでどこに権限を持たせるかわかります。この仕組みを使って数値だけでどこに色を塗るかを判断するようにします。
数値は次のように定義することとします。
\begin{matrix}
2^{0} & 2^{1} \\
2^{2} & 2^{3} \\
2^{4} & 2^{5} \\
\end{matrix}
\qquad
\begin{matrix}
1 & 2 \\
4 & 8 \\
16 & 32 \\
\end{matrix}
\qquad \\
例えばHI
の時は53
となります。
HI \quad = \quad
\begin{matrix}
o & - \\
o & - \\
o & o \\
\end{matrix} \quad
= \quad 53
この53
を2進数110101
にすればなんとなく形が見えてきます。
流れはこんな感じ
- STEP1: 入力に対する数値を定義する
-
A HI RU
=>[1, 53, 11]
-
- STEP2: 数値を2進数にして点字配列を作る
-
[1, 53, 11]
=>['000001', '110101', '001011']
=>['100000', '101011', '110100']
-
- STEP3: 点字配列を点字の形で返す
STEP1: 入力に対する数値を定義する
上に書いた数値をもとに、1音1音と数値との対応を定義しておきます。
母音のみの場合と子音と母音を組み合わせた場合と、例外のヤユヨワン
を定義します。
BOIN = { 'A' => 1, 'I' => 5, 'U' => 3, 'E' => 7, 'O' => 6 }.freeze
SHIIN = { 'K' => 32, 'S' => 40, 'T' => 24, 'N' =>16, 'H' => 48, 'M' => 56, 'R' => 8 }.freeze
EXCEPTION = { 'YA' => 18, 'YU' => 50, 'YO' => 26, 'WA' => 16, 'N' => 56 }.freeze
COMBI = SHIIN.map { |k1, v1| BOIN.map { |k2, v2| [k1+k2, v1+v2] } }.flatten(1).to_h.freeze
# { 'A' => 1, 'I' => 5, ..., 'KA' => 33, 'KI' => 37, ..., 'N' => 56 }
WORD_NUM_DICT = BOIN.merge(COMBI, EXCEPTION).freeze
> puts num = WORD_NUM_DICT["HI"]
=> 53
STEP2: 数値を2進数にして点字配列を作る
数値を2進数にするにはnum.to_(2)
を使います。
しかし、このままでは1
に対して1
が出力されて不十分です。点字は6つの点でできているため000001
となるよう0
埋めする必要があります。sprintf('%06d', num.to_s(2))
で6bits分使って表現できるようにしておきましょう。
そして、点字の並びを配列に見立てたとき、左上から右下に向かって桁が増える形に定義していました。
\begin{matrix}
2^{0} & 2^{1} \\
2^{2} & 2^{3} \\
2^{4} & 2^{5} \\
\end{matrix}
\quad = [2^{0}, 2^{1}, 2^{2}, 2^{3}, 2^{4}, 2^{5}]
000001
は左から右に向かって桁が上がっていくので、100000
となるようreverse
で反転しておきましょう。
> puts sprintf('%06d', 1.to_s(2)).reverse
=> '100000'
次にこの結果を点字文字列に変換します。
文字列をchars
で一文字ずつの配列にして0,1
を-, o
に変換します。
> puts '101011'.chars.map { _1.to_i.zero? ? '-' : 'o' }.join
=> 'o-o-oo'
ここまでの処理をループで組み合わせるとA HI RU
の入力に対して['o-----', 'o-o-oo', 'oo-o--']
で出力されるところまでできるはずです。
STEP3: 点字配列を点字の形で返す
あとは点字の形で返すだけですが、文字列で返すときは1行ずつとなるため、縦に2×3
で並ぶ点字ブロックごとに表現するには、配列の各要素を2文字分(2bits分)ずつ出力して改行、というのを繰り返す必要があります。
['o-----', 'o-o-oo', 'oo-o--']
は次のようになります。
\begin{matrix}
o & - \\
- & - \\
- & - \\
\end{matrix}
\qquad
\begin{matrix}
o & - \\
o & - \\
o & o \\
\end{matrix}
\qquad
\begin{matrix}
o & o \\
- & o \\
- & - \\
\end{matrix}
まずは2文字ずつ分割。
> puts ['o-----', 'o-o-oo', 'oo-o--'].map { _1.scan(/.{1,#{2}}/) }
[
['o-', '--', '--'],
['o-', 'o-', 'oo'],
['oo', '-o', '--']
]
次に、transpose
を使って行と列を入れ替えます。以下のように変換されます。
[ [
['o-', '--', '--'], ['o-', 'o-', 'oo'],
['o-', 'o-', 'oo'], => ['--', 'o-', '-o'],
['oo', '-o', '--'] ['--', 'oo', '--']
].transpose ]
あとは、配列を文字列にしていい感じに成形して完成です。
> ['o-----', 'o-o-oo', 'oo-o--'].map { _1.scan(/.{1,#{2}}/) }.transpose.map { "#{_1.join(' ')}\n" }.join.chomp
=> o- o- oo
-- o- -o
-- oo --
完成
各処理をメソッドに切り出して全部まとめるとこんな感じ
class TenjiMaker
BOIN = { 'A' => 1, 'I' => 5, 'U' => 3, 'E' => 7, 'O' => 6 }.freeze
SHIIN = { 'K' => 32, 'S' => 40, 'T' => 24, 'N' =>16, 'H' => 48, 'M' => 56, 'R' => 8 }.freeze
EXCEPTION = { 'YA' => 18, 'YU' => 50, 'YO' => 26, 'WA' => 16, 'N' => 56 }.freeze
COMBI = SHIIN.map { |k1, v1| BOIN.map { |k2, v2| [k1+k2, v1+v2] } }.flatten(1).to_h.freeze
# { 'A' => 1, 'I' => 5, ..., 'KA' => 33, 'KI' => 37, ..., 'N' => 56 }
WORD_NUM_DICT = BOIN.merge(COMBI, EXCEPTION).freeze
def to_tenji(text)
tenji_list = text.split(' ').map do |word|
six_bits = convert_word_to_six_bits word
convert_bits_to_tenji six_bits
end
convert_tenji_lsit_to_tenji_template tenji_list
end
private
# ex. 'A' => '100000'
def convert_word_to_six_bits word
sprintf('%06d', WORD_NUM_DICT[word].to_s(2)).reverse
end
# ex. '100000' => 'o-----'
def convert_bits_to_tenji bits_s
bits_s.chars.map { _1.to_i.zero? ? '-' : 'o' }.join
end
def convert_tenji_lsit_to_tenji_template tenji_list
tenji_list.map { _1.scan(/.{1,#{2}}/) }.transpose.map { "#{_1.join(' ')}\n" }.join.chomp
end
end