はじめに
初記事です。お手柔らかにヨロです。
本記事は、伊藤淳一氏主催のアドベントカレンダーに参加した記録です。
プログラムはこちら
環境構築
面倒なので画像とか貼ってないけど、気が向けばおいおい貼る。
①VSCodeを起動すると、「gitリポジトリからのクローン」というメニューからcloneできる(初めて使った)
②左下の色がついた部分を押すと、「Reopen in Container」と出てくるので、ここからruby -> 3-bullseyeのように進めると、ruby3.0環境もゲットできる
③上の設定中にgitをインストールしておけば、そのコンテナのターミナルからgit pushするだけで更新も可能
VSCodeは便利だなあ。ということで本編行きます。
調査
せっかくなのでなるべくプログラミングらしい解決をしたい。
まずはサイトを見て点字の構造を紐解くと、今回のターゲットになる文字は下のルールで網羅できる。
①ヤ行、ワ行、ンを除けば、左上の3つが母音を、右下の三つが子音を表す(以後それぞれ母音成分、子音成分と呼ぶ)
②ヤ行、ワ行は、母音をできるだけ(!?)下に並行移動し、ヤ行の場合はさらに右上の一点を凸にする
③ンは子音成分が全て凸(=マ行の子音)
このルールをプログラミング的に解釈した時、一目でわかる困った点が二つある。
困った点1: 並行移動が固定値じゃない!
まずは、②のルールにある「できるだけ」という言葉。
ヤ行にある母音はa,u,oの三種類なのだが、このうち前者二つは凸の場所が一列に収まるため、下に2つ並行移動する。
一方oだけは、凸の場所が真ん中列にも存在するため、一つしか並行移動しない。
このように厳密な数字で決めにくいシステムをプログラミングで統一的にスパッと解決するのは、なかなか困難な気がする。
移動を全て一つだけにするとか、点対象に反転するとかで統一した動作があればいいのだが。。。
今回はここは別々の例外処理ではなくできるだけ統一的に扱うように工夫した。
困った点2: ンがナ行の子音と一致しない!
ンはローマ字でnと1文字の表記をするが、これはある意味子音のみの文字と言える。
ここで、nを子音として用いるナ行の子音成分と同じ構造であれば、統一的に扱えるはずだったが、なんとまさかの、ナ行ではなくマ行(m)と同じ。
なぜ惜しいところでずれてしまったのか、、、と悲しんでいても始まらないので、一例しかないし例外処理で対応しよう。
構成
調査も済んだので、構成を考える。
基本構成
まずはとりあえず、①のルールを軸に、点字の各点に2の累乗の値を当てはめ、6bitの数字と対応づけることにした。
当てはめ方は下のような感じ
8 32
16 2
4 1
順番はぐちゃぐちゃなように見えるが、一応後述の都合があって綺麗にできなかったという理由もある。
これによって、ルール①に該当する母音と子音は、単純な足し算だけで一意な点字と対応する数字を得られることになる。
数字と点字の変換は1bitずつ読み出して対応関係から位置を決めれば実現できそうだ。
例外処理
次に、②、③のルールについて考える。
③に関しては1文字だけの話なので、n1文字だけの入力に対応する数字だった場合m1文字に対応する数字に変換する例外処理を書くことにした。
②はどうにかして①と似たような実装(母音と子音に割り当てられた値を足し算した数字と点字を対応させる)で乗り切りたい。
しかし、普通の方法ではすでに子音側の3bitの情報は使い尽くされている。どうしよう。。。
そこで今回は、yやwの場合は足す値を非常に大きくして、どの母音相手でも6bitではオーバーフローするようにした。
その上で、7bit目の値によって数字と点字の対応表を切り替えて実装した。
この時、7bit目を除いた6bitが各文字の点字となるべくシンプルに対応づけられるようにy,wの値と母音成分の値を調整した。
その結果、yに59、wに57を対応づけることで、次のように綺麗に区別できた。
ya: 8+59=67=64+2+1
yu: 24+59=83=64+16+2+1
yo: 48+59=107=64+32+8+2+1
wa: 8+57=65=64+1
(wo: 48+57=105=64+32+8+1)
上の値を点字に変換する対応表は、下のようなシンプルなもので済むようにできた。
なお、32はシンプルな対応表実現のため対応なしとしている。(表左上のxも同様)
x 2
4 8
1 16
実装
まずは基本構成の実装から。
テキストから各ひらがなを表す文字列を切り出す
text.downcase.split(" ")
普通に空白でsplitした。
文字列から数字へ変換
CharToNum = {
"a" => 8, "i" => 24, "u" => 40, "e" => 56, "o" => 48,
"k" => 1, "s" => 3, "t" => 6, "n" => 4, "h" => 5, "m" => 7, "y" => 59, "r" => 2, "w" => 57,
}
string.chars.inject(0) { |r, i| r + CharToNum[i] }
文字と数字の対応表をハッシュ定数TextToNumで定義し、文字列を1文字ずつ変換して足し合わせた。
数字から点字を表す配列に変換
Format = [5, 3, 4, 0, 2, 1]
array = [0,0,0,0,0,0]
Format.each do |f|
array[f] = 1 if num & 1 == 1
num = num >> 1
end
数字と点字位置の対応表を配列定数Formatで定義し、1bitずつ読み出すと同時にFormatから得た対応する点字位置にフラグを書き込んだ。
rubyでは1をtrueと呼んでくれないので、==1を書くのが見栄え悪くてちょっと嫌だった。
点字を表す配列から点字に変換
tenjis = array.each_slice(2).map {|a| a.inject(""){ |r, i| r + (i == 1 ? "o" : "-")}}
二つずつ取り出して、それぞれフラグを文字に変換して結合することで、3列の文字にして返した。
点字から点字列に変換
tenjis.transpose.map{|t| t.join(" ")}.join("\n")
この時点ではtenjisは一つのひらがなにつき3列の点字がある配列となっている。
このままでは点字を横に並べることができないので工夫が必要。
今回の実装では、transposeで行と列を反転させ、点字の3列を維持したまま横に結合して、最後に各行を結合することで、横並びを実現した。