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 遊ぶぞー!