見出し画像

Ruby2.7を使って整数→クウェンヤ数字の変換を実装してみる

こんばんは bosyu 開発メンバーの @lulu-ulul です。
bosyu Advent Calendar 2019 の 14日目の記事になります!

本当は別のネタで記事書くつもりだったのですが、用意した学習データが悪かったのかSVMで上手く分類できなくてお蔵入りになりました。。。

先日Steamのセールで、発売からしばらく所謂おま値で買う気が削がれていた Shadow of War というゲームがおま値解除されていて購入しました。せっかくなので原作世界のネタで何かしてみようと思います。

クウェンヤ語とは

指輪物語(ロード・オブ・ザ・リング)・ホビット・シルマリルの物語 等の作者として知られる J・R・R トールキン氏が創作した、氏の著作にも登場する人工言語です。

参考サイト
クウェンヤ - 中つ国Wik / Quenya- the Ancient Tongue

言語学者なだけあって、クウェンヤ全体は構文や規約等多くてちょっと重いですねー。 

クウェンヤ数字

参考サイト
クウェンヤ#数字 / Quenya- the Ancient Tongue

軽く実装してみるにはちょうどいい感じの規約の量だし法則性も在って良さそうですね。

概要

・12進数だよ
・数字は右から表記していくよ
・読む時は左から発音するよ
・負数や小数は考えないよ(時代設定的にまだ一般的でないかも?)

本来は Parslet 等使ってパーサーを用意すると思うのですが、 Ruby2.7の Numbered Parameters の仕様も固まったので使いながら書いてみたいと思います。
(他も使いたかったけど使い所がなかった…)

方針

1. 十進数→十二進数変換
2. 内部で 十二進数字→クウェンヤ(テングワール)の対応文字に変換

進数変換は String#to_i(n)  Integer.to_s(n) を使えば楽ちんですね

def decimal_to_duodecimal(decimal)
  decimal.to_s(12) # 十二進数の文字列に変換
end

def duodecimal_to_quenya(duodecimal)
  duodecimal.split('').map { DECIMAL_QUENYA_MAP[_1] }
end

さっくりいけました


class Quenya < Numeric
  DECIMAL_QUENYA_MAP = {
    '0' => 'ð',
    '1' => 'ñ',
    '2' => 'ò',
    '3' => 'ó',
    '4' => 'ô',
    '5' => 'õ',
    '6' => 'ö',
    '7' => '÷',
    '8' => 'ø',
    '9' => 'ù',
    'a' => 'ú',
    'b' => 'û'
  }.freeze

  def initialize(decimal)
    @quenya = duodecimal_to_quenya(decimal_to_duodecimal(decimal)).reverse
  end

  def to_s
    @quenya.join
  end
 
  private

  def decimal_to_duodecimal(decimal)
    decimal.to_s(12)
  end

  def duodecimal_to_quenya(duodecimal)
    duodecimal.split('').map { DECIMAL_QUENYA_MAP[_1] }
  end
end
0> quenya = Quenya.new(8848)
0> quenya.to_s
=> "ôõñõ"

0> quenya = Quenya.new(333)
0> quenya.to_s
=> "ùóò"


Wikipediaの表記例の変換がちゃんとできていますねー (参照: クウェンヤ#数字 )
333 →  ùóò
8848ôõñõ

NumberdParameters使ってみましたが、パラメータ一つなら個人のスクリプトレベルなら可読性犠牲にしても記述速いので使っても良いかもって感想です。

クウェンヤ数詞を出力できる様にする

これだけだとあっさりなので クウェンヤ数詞 も出力できる様にしてみます。
(英語で言うと twelve や thirty million みたいなやつです)

クウェンヤ#数字 を改めて確認すると以下の条件で出力すれば良さそうですね

・左から読んでいく
・一の位と十二の位以降でロジックが異なる
・十二の位以降は冪数ごとの名前に十二進数数字に対応したPrefixをつける
     (ex. 20: yu + rasta ,  200: yu + tuxa )
・自然数の場合、0の桁は読まない

方針

1. 冪数ごとの名前の対応表を用意する
2. 一の位から順に配列に詰めているので、十二の位以降と別途出して最後にくっつける
3. 0の値の位を読まない様なロジックを仕込む

複数パラメータの NumberedParametersを使うために  .map.with_index 使ったくらいで特に手の混んだことはしてないですね。

値が0の位に対応するためにボッチ演算子利用のため .+ 表記してるくらいです。

numeral メソッドと命名しましたが定数名も含め英語力とセンスがなさすぎる気がしますので誰か鍛えて下さい。

class Quenya < Numeric
  DECIMAL_QUENYA_MAP = {
   '0' => 'ð',
   '1' => 'ñ',
   '2' => 'ò',
   '3' => 'ó',
   '4' => 'ô',
   '5' => 'õ',
   '6' => 'ö',
   '7' => '÷',
   '8' => 'ø',
   '9' => 'ù',
   'a' => 'ú',
   'b' => 'û'
  }.freeze

  NUMERAL_NAMES_MAP = {
    'ñ' => 'min',
    'ò' => 'atta',
    'ó' => 'neldë',
    'ô' => 'canta',
    'õ' => 'lempë',
    'ö' => 'enquë',
    '÷' => 'otso',
    'ø' => 'toldo',
    'ù' => 'nertë',
    'ú' => 'quain',
    'û' => 'minquë'
  }.freeze

  PREFIX_NAMES = {
    'ð' => nil,
    'ñ' => '',
    'ò' => 'yu',
    'ó' => 'nele',
    'ô' => 'cana',
    'õ' => 'lepe',
    'ö' => 'ene',
    '÷' => 'oto',
    'ø' => 'tolo',
    'ù' => 'nete',
    'ú' => 'quai',
    'û' => 'minquai'
  }.freeze

  EXPONENTIALS_NAMES = [
    'rasta', 'tuxa', 'mencë', 'rastamencë'
  ].freeze

  def initialize(decimal)
    @quenya = duodecimal_to_quenya(decimal_to_duodecimal(decimal)).reverse
  end

  def to_s
    @quenya.join
  end

  def numeral
    @quenya[1..-1].map.with_index { PREFIX_NAMES[_1]&.+(EXPONENTIALS_NAMES[_2]) }
        .unshift(NUMERAL_NAMES_MAP[@quenya.first])
        .compact.join(' ')
  end

  private

  def decimal_to_duodecimal(decimal)
    decimal.to_s(12)
  end

  def duodecimal_to_quenya(duodecimal)
    duodecimal.split('').map { DECIMAL_QUENYA_MAP[_1] }
  end
end

複数になると NumberedParameters はつらくなってきますね。 脳内の変換コストが発生するのでちゃんと命名したい気がします。


実行結果

# クウェンヤ数字変換
0> puts [50, 90, 333, 1080, 8848].map { Quenya.new(_1).to_s }
òô
ö÷
ùóò
ðö÷
ôõñõ
=> nil


# 数詞
0> puts [50, 90, 333, 1080, 8848].map { Quenya.new(_1).numeral }
atta canarasta
enquë otorasta
nertë nelerasta yutuxa
enerasta ototuxa
canta leperasta tuxa lepemencë
=> nil 

・50 → òô  | atta canarasta
・90 → ö÷ | enquë otorasta
・333 → ùóò | nertë nelerasta yutuxa
・1080 → ðö÷ | enerasta ototuxa
・8848 → ôõñõ | canta leperasta tuxa lepemmencë
    (参照: クウェンヤ#数字 )

完璧ですね!優勝!!!1!!!!

 まとめ

・数字に限れば人工言語の変換思ったより実装大変じゃなかった
・三十二進数までなら to_s to_i 変換で進数変換が楽すぎた
・NumberedParametersはパラメータが一つまでかつ保守考えなくていいなら良さそう
・クウェンヤ自体の変換は大変過ぎて資料のボリューム見た瞬間に諦めました
・冬休みは Shadow of War 遊ぶぞー!


この記事が気に入ったらサポートをしてみませんか?