2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

点字メーカープログラムに挑戦! | 行列でシンプルに実装

Last updated at Posted at 2021-12-22

この記事は、Rubyプログラミング問題にチャレンジ! -改訂版・チェリー本発売記念- Advent Calendar 2021 の22日目の記事です。

1. ルールとか 🔰

ルールや、他の方の回答はこちらにあります。

2. 成果物 🍒

ソースコード 📄 ( クリックで開きます )
lib/tenji_maker.rb
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. 実装アイデア 💡

下の図を見てもらうと分かる通り
点字は、母音の位置の点と、子音の位置の点の足し算になっています。

image.png

最初に思いつく方法として配列での実装がありましたが、今回は行列を扱うことが出来る Matrix を使用します。( Matrix では行列の足し算が簡単に出来るという点のみで採用したので、配列で実装しても、行列の足し算の実装があるくらいで、ほとんど変わりません。)

4. 解説 🧬

では、
今回の実装のメインの部分を解説していきましょう!
まず、こちらがソースコードです。( 下にGitHub のリンクもあるので、見やすい方で見て下さい )

lib/tenji_maker.rb
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.boinself.class.siin に母音や子音の行列データを保存しておきます。

sekf.class.boin
@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. 特殊な場合 (今回は、やゆよわを)

今回は特別な場合が、やゆよわを くらいだったので、決め打ちで行列として変数を持たせておいても良かったかもしれませんが、👇下の画像を愚直に実装してみました。

image.png

まず、メソッド 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も書ける ブロック プログラミング アプリです!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?