タイトル通りのものを作りました。
ここで使えます。
決めうちじゃないので、なかなか同じ名前はでない……はず。
背景(1):
「世界樹」とかプレーしているとカタカナの名前を入力する機会が多いです。
が、いい名前なんてそう簡単に思いつきません。
こういうときにいい感じの名前ジェネレータが欲しくなります。
ゲームとかについてる名前ジェネレータは大きく2タイプあるような気がします。
・完全ランダム:正直使い物にならない。
・決めうち:パターンがあんまりない。FFTみたいな例外はある。
ということで、作りましょう。
背景(2):
うちの子まとめβという素晴らしいデータセットがあるのに、
使わないのはもったいない。
結果:
とりあえず一度回してみましょう。
フォルナ アクア ヒトリ ルシェッティ ハイル
ギリス アキック ヴェラ コロ アルドラッサ
イヴ ヒルト ナナコ ジア イオドラト
ガオ レナス リベッツ ルヴァリ カイキ
次は完全にランダムに生成した場合です。
(具体的には print ('ア'..'ン').to_a.sample(3).join + "\n" @ ruby)
ョハル エゾヨ トクヱ オィポ ギモニ
ナクワ ンヲユ ヤラェ ロベギ マジバ
ペクツ ホィレ ナジダ ハォウ カヨヰ
ラワゥ ヨャゥ スミゥ スドナ ウロヨ
ちょっとランダムの方の結果が極端に悪い気もしますが、
上の結果の方が実戦で使えそうな気がします。
以下は、微妙にテクニカルなので興味ない方はここまででも大丈夫です。
こちらからジェネレータでおあそびください。
考察:
完全ランダムがいまいちな原因としては、
あるべきでない場所にある文字がある->文頭の「 ョ」、 「ハ」の後ろの「ォ」
がまず考えられます。
とはいえ、そういうわけでもないものもどこか今ひとつな感じがあります。
おそらく、名前には使いやすい文字というものがあるのではなかろうかと思います。
実際、出現頻度にそれなりの差がありそうなことは去年の記事でも書きました。
また、完全に推測なのですが、
後ろに続く文字は前の文字の影響を受けているような気がします。
例えば、アの後ろにはギはこなそうだけど、ガの後のギは自然な気がするというように。
ということで、
・あるべきでない場所の文字がでないようにする
・使いやすい文字が出やすいようにする
・前の文字との繋がりやすさを考慮する
の3点を踏まえて実装を考えます。
実装方針:
各文字及び出現位置ごとに、次の文字の出現確率を求めます。
例えば[1文字目,ア->イ, 0.01], [2文字目,ア->ガ, 0.005],[1文字目,ョ->ガ, 0.00]のように。
また、1文字目で出現する確率、その文字で名前が終わる確率も併せて求めます。
そうすると、
1文字目を決める->1文字目から2文字目につながる確率がわかる->
2文字目を決める->2文字目から3文字目につながる確率がわかる-> ...
のように連鎖的に名前を生成することができます。
これによって
・あるべきでない場所の文字がでないようにする->その位置の確率を低くする
・使いやすい文字が出やすいようにする->使いやすい文字は確率が高くなるようにする
・前の文字との繋がりやすさを考慮する->前の文字と後ろの文字のペアで確率を決める
という感じで上の要件がクリアできます。
今回は「うちの子まとめβ」のデータを活用します。
ここに登録されたキャラクターの名前を取得し、
例えばアの後にどれだけイが続くのかということを実際に数え上げます。
これをすべての文字の組み合わせに対して行うことで確率を決定します。
しのごの言わずにソースを貼れという話もあると思いますので、貼っときます。
なんかハッシュ周りがかなり酷いことになってしまいました…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- encoding: utf-8 -*- | |
require 'json' | |
require 'kconv' | |
dirs = Dir.glob('/Users/miyatsuki/uchinoko/chara\?id\=*'); | |
nameArr = Array.new(); | |
dirs.each{|f| | |
html = File.open(f).read.toutf8 | |
hash = JSON.parse(html) | |
nameArr.push('s' + hash['name'] + 'e') if hash['name'] =~ /^[\p{katakana}ー-]+$/ | |
} | |
nameMat = Array.new(); | |
weightedMat = Array.new(); | |
for i in 0 .. 15 | |
nameMat[i] = {}; | |
weightedMat[i] = {}; | |
end | |
nameArr.each{|n| | |
for i in 0 .. (n.length-2) do | |
if nameMat[i].key?(n[i]) then | |
if nameMat[i][n[i]].key?(n[i+1]) then | |
nameMat[i][n[i]][n[i+1]] += 1; | |
weightedMat[i][n[i]][n[i+1]] += 1; | |
else | |
nameMat[i][n[i]][n[i+1]] = 1; | |
weightedMat[i][n[i]][n[i+1]] = 1; | |
end | |
else | |
nameMat[i][n[i]] = {n[i+1] => 1.0}; | |
weightedMat[i][n[i]] = {n[i+1] => 1.0}; | |
end | |
end | |
} | |
nameMat.each_with_index{|id, i| | |
id.each{|s| | |
sSum = 0; | |
s[1].each{|e| | |
sSum += e[1]; | |
} | |
acc = 0; | |
s[1].each{|e| | |
weightedMat[i][s[0]][e[0]] /= sSum | |
weightedMat[i][s[0]][e[0]] += acc | |
acc = weightedMat[i][s[0]][e[0]] | |
} | |
} | |
} | |
for i in 0 .. 20 | |
outS = "s"; | |
while outS[-1] != "e" do | |
nextNum = rand; | |
tgtArr = weightedMat[outS.length-1][outS[-1]].to_a; | |
counter = 0; | |
for counter in 0 .. tgtArr.length - 2 | |
break if tgtArr[counter][1] > nextNum | |
end | |
outS = outS + tgtArr[counter][0] | |
end | |
print outS[1, outS.length-2] + "\n" | |
end | |
print("-----\n") | |
for i in 0 .. 20 | |
print ('ア'..'ン').to_a.sample(3).join + "\n" | |
end | |
謝辞
・うちの子まとめβ開発者のしおりもさんにバグ取りに付き合っていただきました。
ありがとうございました。
以上です。
ジェネレータはこちら。