「点字」と「2進法」ッ！この世にこれほど相性のいいものがあるだろうかッ!?

# この記事について

　この記事は、Qiita Advent Calendar 2021[「Rubyプログラミング問題にチャレンジ！ －改訂版・チェリー本発売記念－」](https://qiita.com/advent-calendar/2021/ruby-challenge)の 2 日目の記事です。何も考えずに空いているところに入れたら改訂版チェリー本の発売日でした。記念すべき日を私ごときが取ってしまい申し訳ありません…

[点字メーカープログラム PR (Qiita Advent Calendar 2021 12/02) by nodai2hITC · Pull Request #2 · JunichiIto/tenji-maker-challenge](https://github.com/JunichiIto/tenji-maker-challenge/pull/2)

# 方針

　まずタイトル（はいそうですジョジョ4部です）にある通り、点の有無で情報を表す「点字」は、「2進法」と非常に相性がいいのではないでしょうか。点の有無をそのまま 1・0 として置き換えることで、点字の 1 文字 1 文字を 2 進法の数として表すことができます。

![点字と２進法.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/6288cef3-c984-965b-3e28-96718c17024a.png)

　そこで、直接ローマ字を点字に変換するのではなく、まずは点字に対応する 2 進法の数値（の配列）に変換し、それを点字に変換するという 2 段階を踏んで変換をしようと考えました。

# ローマ字を「点字に対応する 2 進法の数値」に変換する

　まず、与えられたローマ字を点字に対応する 2 進法の数値に変換する処理です。一番シンプルな方法は以下のように、音節と それに対応する 2 進法の数値の間の変換テーブルを用意するものでしょう。Ruby には[ 2 進数値リテラル](https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#num) がありますから、それを使えば点字との対応関係を見やすい形でテーブルを作成できます。

```ruby
  TRANS_TABLE = {
    'A'  => 0b100000,
    'I'  => 0b110000,
    'U'  => 0b100100,
    'E'  => 0b110100,
    'O'  => 0b010100,
    'KA' => 0b100001,
  # 以下略
  }
```

　しかしこの方法では、いわゆる 50 音（実際は濁音等もあるのでそれ以上）すべてに対して対応する数を書く必要があります。これはなかなか面倒な作業です。

　ところで点字は基本的に、ある意味ローマ字のように子音と母音の組み合わせで作られています。例えば下のように、「き(KI)」は「か行(K)」の⑥点と「い(I)」の①②点との組み合わせで作ることができます。

![点字の合成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/760ba7a5-4522-99ac-d861-e24e3cbc49a7.png)

　…これは、`|` によるビット論理和演算で実現できるのでは？[^1]

[^1]: 子音と母音で点の重複は無いので、実際のところ `|` でなくても `+` や `^` でも行けるはずです。

```ruby
#↓か行(K)    ↓い(I)      ↓き(KI)
0b000001 | 0b110000 == 0b110001
```

　というわけで、この方法では変換できない特殊なパターン（`やゆよわをんっー。`）のみ例外的な処理をする必要がありますが、それ以外はビット演算を使ってやっていきます。

　この方法の面白いところは、拗音です。点字の拗音はひらがなやローマ字での書き方とはかなり異なる（無理やりひらがなで説明するなら、「ぎゃ」「ぎゅ」「ぎょ」が「★゛か」「★゛く」「★゛こ」みたいなのになる。）にも関わらず、この方法を使えば自然に扱うことができるのです。

```ruby
#      ↓ G                ↓ Y         ↓ A             ↓ぎゃ
0b000010_000001 | 0b000100_000000 | 0b100000 == 0b000110_100001 
```

## 実装解説

```ruby
binaries = text.split.map do |syllable|
  EXCEPTION_TABLE[syllable] || syllable.chars.map { |char| TRANS_TABLE[char] }.inject(:|)
end
binaries.map { _1 > 0b111111 ? [_1 >> 6, _1 & 0b111111] : _1 }.flatten
```

　まず、与えられたローマ字文字列を [String#split](https://docs.ruby-lang.org/ja/latest/method/String/i/split.html) で空白区切りで分割し、その各音節部分 `syllable` が例外テーブル `EXCETION_TABLE` にあればその値を使います。

　例外テーブルに無かった場合はその音節を [String#chars](https://docs.ruby-lang.org/ja/latest/method/String/i/chars.html) で 1 文字ごとに分解し、変換テーブル `TRANS_TABLE` で変換し、`inject(:|)` で論理和をとります。（参考：[Enumerable#inject](https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/inject.html)）ところで `(:|)` の部分が可愛いと思うんですがどうでしょう。

![変換処理1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/154867ab-2586-3e3b-1d30-3751248d84af.png)

　最後に、1 音が点字では 2 文字になる場合があるので、その場合（`0b111111` すなわち `63` より大きい数が得られた時）は `[_1 >> 6, _1 & 0b111111]` で上位・下位 6 ビットずつに分割し、最後に [Array#flatten](https://docs.ruby-lang.org/ja/latest/method/Array/i/flatten.html) で平坦化しています。

![変換処理2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/2432b2b3-8a68-76ce-7438-8401ff959ce1.png)

# 「点字に対応する 2 進法の数値」を、点字に変換する

　この処理は、さらに「1つの『点字に対応する 2 進法の数値』を点字に変換する」「点字の 1 文字  1 文字の配列を、1 つの文字列に連結する」という処理に分かれています。

## 1つの「点字に対応する 2 進法の数値」を点字に変換する：実装解説

　解説のために改行を入れています。

```ruby
format("%06b", binary)
  .tr("01", "-o")
  .chars.each_slice(3).to_a
  .transpose
  .map(&:join).join("\n")
```

1. まず [Kernel.#format](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/format.html) を使って、数値を 6 桁の 2 進文字列に変換します。
2. さらに [String#tr](https://docs.ruby-lang.org/ja/latest/method/String/i/tr.html) を使って、「`0`・`1`」を「`-`・`o`」に変換します。
3. それを String#chars で 1 文字ごとに分解した配列を作り、`each_slice(3).to_a` （参考：[Enumerable#each_slice](https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/each_slice.html)） でさらに 3 文字ごとに分けます。
4. [Array#tranpose](https://docs.ruby-lang.org/ja/latest/method/Array/i/transpose.html) で、行と列を入れ替えます。
5. [Array#join](https://docs.ruby-lang.org/ja/latest/method/Array/i/join.html) で、内側の配列はそのまま、外側の配列は `"\n"` を間に挟んで連結します。

![変換処理3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/337338c2-a373-afbe-04e3-45034c30b465.png)

## 点字の 1 文字  1 文字の配列を、1 つの文字列に連結する：実装解説

　解説のため、少し実際のコードとは変更しています。

```ruby
tenji_array.map { _1.lines(chomp: true) }
           .transpose
           .map { _1.join(" ") }.join("\n")
```

1. 点字文字列を、[String#lines](https://docs.ruby-lang.org/ja/latest/method/String/i/lines.html) で行ごとに分解します。
2. Array#transpose で、行と列を入れ替えます。
3. それぞれの行では `join(" ")` で空白を間に挟んで連結し、行間には `join("\n")` で改行を挟んで連結します。

![変換処理4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/c977ecae-9ad1-2bc6-d45d-2f21b1907e25.png)

# おまけ

　ところで点字は 6 つの点からなるので、可能な点の組み合わせは $ 2^6 = 64 $ 通りです。[^2]

[^2]: もちろん実際には使われないパターンもあるので 64 通りより少なくなりますが。

　64 と言えば…そう、**[BASE64](https://ja.wikipedia.org/wiki/Base64)ですね**！「点字」と「BASE64」ッ！この世にこれほど相性のいいものがあるだろうかッ!?

　というわけで、点字に対応した BASE64 文字列を生成する機能もつけました。一体どのような利用価値があるのか皆目見当がつきませんが！

```ruby
TenjiMaker.new.to_base64('A HI RU') # => g5m
```

# 補足

　[「仕様を簡単にするため、以下のローマ字は対応不要です。」](https://github.com/JunichiIto/tenji-maker-challenge#%E5%A4%89%E6%8F%9B%E5%AF%BE%E8%B1%A1%E3%81%A8%E3%81%AA%E3%82%8B%E3%83%AD%E3%83%BC%E3%83%9E%E5%AD%97%E3%81%AA%E3%82%89%E3%81%AA%E3%81%84%E3%83%AD%E3%83%BC%E3%83%9E%E5%AD%97)とのことでしたが、一通り不要と言われたローマ字にも対応しました。ただし促音「っ」についてはどのような入力を想定すればよいのかわからなかった[^3]ので、`XTU` を「っ」扱いにさせてもらいました。また、長音・句点は `-` `.` が入力されるものとしました。

[^3]: ローマ字では「っ」は後ろの子音を重ねることで表現しますが、今回のプログラムでは音節ごとに空白で区切られる仕様なので。例えば「らっこ」は `RA KKO`か？ `RA K KO`か？…とりあえずローマ字入力では `XTU` で「っ」になるので、それに倣うことにしました。

　なお、異常系への考慮はしていません。

# 伊藤さんにメッセージ

　プロを目指しているわけではなかったので実はチェリー本を買っていなかったのですが、評判を聞くと趣味レベルの私のような人間にも充分役立ちそうですし、私がプロを目指す人に教える機会もあると思うので、改訂版が出るのを機にやはり買うことにしました。というわけで本日発売ですね！買ってきます！

# 12/02 追記

私なりに工夫したつもりだったのですが、初日の @cielavenir さんの[記事](https://qiita.com/cielavenir/items/441631f6796af95fe0ea)を見たら結構似たようなことをしていました。（まずは 0～63 の数値にするところとか、`transpose` 使ってるところとか。）

同じものを作る以上、どうしても必然的に似てしまうのかもしれませんね。…**必然！そうコーラを飲んだらゲップが出るっていうくらい必然じゃッ！**