Rubyで乱数のシードを設定しないでランダムな数値を取得できるかRubyのコードを調査した
概要
Rubyはなぜ乱数のシードを入れないで乱数を生成できるのかRubyの言語自体のソースコードを軽く読んで調査した。
乱数を生成するクラスは乱数のシードを引数に入れないと、乱数のシードを生成するメソッドを呼び出す、その乱数を生成するロジック内ではCPUクロック数やOSの乱数生成を呼び出して利用して乱数のシードを生成していた。
乱数のシードがUNIX timeだけに依存したりしていないので、高アクセスなサービスや並列化したバッチ処理などで同時刻に乱数を生成してもシード値が同じでバラけないってことはなさそうだ。
はじめに
Rubyのランダムな数値を生成する場合はこのような書き方をする。
r=Random.new r.rand(10) => 5 引数に整数を入れると0-9までのランダムな数値(Integer)を出力する 引数を少数を入れるとFloat型でランダムな数値を出力する r.rand(5.5) => 1.81617029321715
randメソッドの仕様
Method: Random.rand — Documentation for core (2.7.0)
Rubyで乱数を生成するは、擬似乱数列生成器にメルセンヌ・ツイスタを利用している。擬似乱数発生装置は乱数のシードと呼ばれる初期値が必要だ。(シードが不要な擬似乱数発生装置もあるかもしれないが今回の主題ではない)
コンパイラを利用するプログラミング言語では、乱数を生成する際には乱数のシードを用意するものが多くインタプリタの場合はRubyのように乱数のシードを使わなくても乱数を生成することができるものが多い印象がある。
Ruby以外の他の言語の乱数生成ロジック
乱数のシードが必要な例としてGo言語の場合
package main import ( "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().Unix()) number:= rand.Int() fmt.Println(number) }
乱数のシードが不要な例としてJavaScriptの場合
const number = Math.random() console.log(number)
このJSのMath.random()の定義を見るとJavaScriptでは乱数のシードを入れられない逆にシードを入れさせないっていう仕様も珍しい
The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.
乱数のシードが不要な言語の場合、乱数のシードはどうしているのかRubyを例に調べてみる
Randomのコンストラクタの仕様を見ると Method: Random#initialize — Documentation for core (2.7.0)
Random.new_seedでランダムなシードの値を生成してインスタンス化しているしている。
Random.new_seedを実行してみるとこのような結果が返ってくる
Random.new_seed => 115032730400174366788466674494640623225
乱数のシード生成するのに乱数を生成している? ではその乱数を生成するシードはどう生成するというのだろうか?おそらく擬似乱数発生装置を利用しない方法で乱数を生成しているはずだ。
この対象のRubyのコード、Ruby本体のコードなのでC言語のコードで、Random.new_seedのコードは以下のように作られている。これを1行ずつ読み解く
Method: Random.new_seed — Documentation for core (2.7.0)
static VALUE random_seed(VALUE _) { VALUE v; uint32_t buf[DEFAULT_SEED_CNT+1]; fill_random_seed(buf, DEFAULT_SEED_CNT); v = make_seed_value(buf, DEFAULT_SEED_CNT); explicit_bzero(buf, DEFAULT_SEED_LEN); return v; }
1行目のVALUE v;
は6行目return v
で関数の返り値としているので生成されてたランダムな数値が格納される変数。
2行目uint32_t buf[DEFAULT_SEED_CNT+1];
はDEFAULT_SEED_CNTより1大きいサイズの配列を宣言、固定値DEFAULT_SEED_CNTのサイズは4
3行目fill_random_seed(buf, DEFAULT_SEED_CNT);
fill_random_seedで乱数のシードを作るための乱数のシードを作ってる。
このメソッドのロジックをざっくり読むと配列のサイズ4のseedの各要素にfill_random_bytes
でランダムなバイト数値を入れ、その上にCPU時間からナノ秒やマイクロ秒を取得してXORで上書きしている用に見える。
fill_random_bytesではちらっと見る限りLinuxのシステムコールで乱数を読んでるように見えが、今回は深追いしていない
static void fill_random_seed(uint32_t *seed, size_t cnt) { static int n = 0; #if defined HAVE_CLOCK_GETTIME struct timespec tv; #elif defined HAVE_GETTIMEOFDAY struct timeval tv; #endif size_t len = cnt * sizeof(*seed); memset(seed, 0, len); fill_random_bytes(seed, len, FALSE); #if defined HAVE_CLOCK_GETTIME clock_gettime(CLOCK_REALTIME, &tv); seed[0] ^= tv.tv_nsec; #elif defined HAVE_GETTIMEOFDAY gettimeofday(&tv, 0); seed[0] ^= tv.tv_usec; #endif seed[1] ^= (uint32_t)tv.tv_sec; #if SIZEOF_TIME_T > SIZEOF_INT seed[0] ^= (uint32_t)((time_t)tv.tv_sec >> SIZEOF_INT * CHAR_BIT); #endif seed[2] ^= getpid() ^ (n++ << 16); seed[3] ^= (uint32_t)(VALUE)&seed; #if SIZEOF_VOIDP > SIZEOF_INT seed[2] ^= (uint32_t)((VALUE)&seed >> SIZEOF_INT * CHAR_BIT); #endif }
4行目v = make_seed_value(buf, DEFAULT_SEED_CNT);
でbufに格納されている乱数のシードから乱数を生成している
make_seed_valueではbufに入っている配列の数値をrb_integer_unpackで結合して乱数を生成していると思う。
static VALUE make_seed_value(uint32_t *ptr, size_t len) { VALUE seed; if (ptr[len-1] <= 1) { /* set leading-zero-guard */ ptr[len++] = 1; } seed = rb_integer_unpack(ptr, len, sizeof(uint32_t), 0, INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER); return seed; }
上記のロジックをまとめると、random_seedのロジックは配列を用意して、その配列にCPU時間など使ってランダムな数値を格納したあとに、配列を結合して乱数のシードを作っていると言える
まとめ
「C」の茶の国から「T」の茶の国へと、本当に違う種類の国へ行くことができるのかデータ分析
イントロダクション
バックパッカーのバイブル沢木耕太郎の深夜特急で、ハナモチ氏という人物がお茶について語っているシーンが有る。
手元にある文庫版の第5巻を確認するとこんな一節である。
「彼らはTで始まるチャイを飲んでいる。でも、僕たちはCのチャイを飲んでいるのさ」
中略
私はトルコからギリシャに入ることで、アジアからヨーロッパへ、イスラム教圏からキリスト教圏へ、茶の国からコーヒーの国へ、「C」の茶の国から「T」の茶の国へと、違う種類の国へ来てしまっていたのだ。
というようにユーラシア大陸を東から西に行くとお茶の名前がChaからTeに変わるのだ。
北京語でお茶のことをチャーと発音するように、中国から陸路で伝わった茶はトルコまでCha系の発音で呼ばれることになる。
ではTeの方はというと、オランダがお茶を輸入した福建省あたりのビン南語ビン南語ではお茶のことをテーと発音するようで、そのオランダが海路で運んでいたため、オランダの植民地やヨーロッパ諸国ではお茶のことをティーやテーなどの発音表現する。
というわけでこのどこの国がCha系の国で、どこの国がTe系のなのか、テクノロジーを駆使して調べてみた。
調査方法
Googleスプレッドシートの翻訳関数
Googleスプレッドシートの関数に、GOOGLETRANSLATEというものがあり、日本語から英語へなど翻訳を行ってくれる。
例えばこんな風にセルにかいてあげると
=GOOGLETRANSLATE("tea","en","ja")
お茶と出力される
この関数の変数はそれぞれ
- 訳したい文字
- 翻訳元の言語2文字の略称
- 翻訳先の言語2文字の略称
となっている。つまり各言語のこの2文字のコードを片っ端から調べて、英語のteaから翻訳してやれば良いのだ。
この機能を知ったきっかけはサンミンさん@gijigaeのこのツイート
グーグルシートで簡単翻訳🇯🇵🇬🇧。スプレッドシートのセルに、関数を入れるだけです。入れるのはこんな関数。
— Sangmin @ChoimiraiSchool (@gijigae) 2019年4月2日
▼英語を日本語に訳す場合
=googletranslate(訳したいセル, "en", "ja")
▼日本語を英語に訳す場合
=googletranslate(訳したいセル, "ja", "en")
文章を訳すことも出来ます☝️。 pic.twitter.com/aysuzBAyVX
このツイートを見て各言語ごとにChaかTeか調べることができると思ったのがこの調査を始めたきっかけである。
ISO 639-1 言語を2文字表現
さきほどの2文字のコードは何かというと、国際規格のISO 639-1であるようだ。 このISO 639-1のリストをWikipediaから引っ張ってきた。
そして翻訳した結果はこちら docs.google.com
表の中で#VALUE! となっているのはGoogle翻訳が対応していない。すべてのISO 639-1で定義されている言語にGoogle翻訳は対応していないようだ。
この結果ISO 639-1の言語184言語中、101言語の翻訳できた。
というかGoogle翻訳って101言語対応してるの?すごない!!
ここまでの結果により翻訳に対応していない言語をフィルタリングできる。
Google翻訳ページ
Google翻訳のページでは、メジャーな言語はその翻訳とともに発音を再生してくれる機能が存在する。
またGoogle翻訳のページのURLは、ISO 639-1コードと翻訳したい文字をURLに含めることで動的に生成できる。
このようにセルを設定する
=concatenate("https://translate.google.com/?hl=ja&op=translate&sl=en&tl=", D77,"&text=tea")
するとGoogle翻訳のリンクを作ることができる
https://translate.google.com/?hl=ja&op=translate&sl=en&tl=ja&text=tea
forvo.com
forvo.comは各地の言語の単語の発音を登録できるサービスでありこちらもURLを動的に生成できる。
こちらもこのようにセルを設定する
=concatenate("https://forvo.com/word/",E77 ,"/#", D77)
するとforvo.comのリンクを作ることができる
https://forvo.com/word/お茶/#ja
それぞれの音声をすべて耳で聞いてChaかTeかを振り分け
それぞれの音声をGoogle翻訳で発音を再生できなかった場合はfevor.comで音声を確認した。 聞いた音声を元に下記のカラムを追加した。どちらにも音声がないものは今回は対象外とした。
- 聞いた音声にフリガナ
- ChaかTeかを分類
- 音声がチャに近い音で発音されるか、テに近い音で発音されるかそれぞれ分けた
- 話されている国をラベリング
- 各言語が話されている国がどこかを荒く調べて、それぞれ独立したデータとした
※ 話されている国のラベリングは、南北のアメリカ大陸で話される英語やスペイン語、ポルトガル語については茶の伝来について分布を見る際にノイズになるので英語はイギリス、スペイン語はスペイン語、ポルトガル語はポルトガルだけにしてある。
このあたりはデータ分析と言うよりは根性である。
振り分けした一例
結果
データを地図上にプロットすると以下のように、Chaの国、Teの国、 ChaでもTeでもないその他の国を作成した
Chaの国とTeの国
青がChaの国、赤がTeの国
グラフの濃淡は、対象の言語の種類が複数あると濃くなる
ChaでもTeでもないその他の国
考察
ChaやTeについて
冒頭の深夜特急の引用文のように、トルコでChaの国が終わり、ギリシャからTeの国々になるのがデータをプロットした地図から分かる。
ヨーロッパはだいたいTe系統で、その他の国々はだいたいCha系統。
しかし東ヨーロッパの国々は結構Chaの国であった。これは意外でイメージだとギリシャでバッツリTeの国だと思っていた。
南インドやその近くのスリランカではなされるタミル語はお茶のことをテニーと発音し、インドはChaの国でありTeの国でもあることがわかった。
Teの国は海路で茶が伝わったとされるが、インドネシアや、南アフリカ、ナミビアはかつてオランダの植民地だった影響でTeの国なのだと思われる。先程のタミル語が話されるスリランカもオランダの植民地だったのでTeの国であるのだろう。
Teの国だと思っていたヨーロッパの中でもポルトガルはChaの国
ポルトガル語はChaの国でお茶のことをチャと発音する。
ポルトガルは植民地のマカオから茶を輸入していたので、広東語の茶とおそらく同じ音で伝わったものだと思われる。 (これは友達の @Y_Hirano が教えてくれた。多謝)
ウィキペディアの茶の項目にも書かれている(出典は無いが) ja.wikipedia.org
Chaの国で話されるお茶の発音は大体チャイ
各言語の音を直接自分の耳で聞いてフリガナを振っていくと、Chaの国86ヶ国中、32ヶ国がチャイと発音し37%の国がお茶のことをチャイと発音する。
特に東西問わずアジア圏でチャイが多く、アジアのカフェやレストランではチャイと言うとお茶が出てくる可能性が高いのでぜひチャレンジしてみてほしい。
お茶をチャイと発音する国一覧
ChaでもTeでもない国が存在する
ChaでもTeでもない国がいくつかあった。
ポーランド語はのお茶を表すherbata(ハルバータ)はラテン語herba theaが語源らしい herbata - ウィクショナリー日本語版
そのラテン語herba theaはそれぞれ
- heabaが草 herba - Wiktionary
- theaが茶 thea - Wiktionary
リトアニア語のお茶を表すarbata(アルバータ)はリトアニアはポーランドに隣接しているので文化圏が近いためポーランド語がなまったものだと思われる。
ソマリア語のお茶を表すshaahはfevor.comにもGoogle翻訳にも存在しなかったので特別にyoutubeで検索してみるとソマリア語で喋っている動画にshaahを発音しているものを見つけた。
https://www.youtube.com/watch?v=BAAt03W2z7g , https://www.youtube.com/watch?v=1lz87j-pKwM
これら2つの動画からどうやらshaahはシャーと発音するらしい。
今回はアラビア語の茶、”シャーイ”はChaの国と分けている。字面で見るとChaじゃないじゃないかと思うだろうが、音を聞くとTeかChaかどちらかで分類するとCha寄りだと判断した。
アラビア語のシャーイがなまってソマリア語はシャーであろう。広義の意味ではChaの国としても良いかも知れない。
その他、今回の調査でわかったこと
- アラビア語が話される国がたくさんある。英語、中国語、スペイン語は世界の話させている人口カバー率が高いが、アラビア語はその次ぐらいだろうか
- Google翻訳はインドの方言がたくさん対応している
- Google翻訳は英語、ウェールズ語、アイルランド語、スコットランド語とイギリスの4つの国の言語に対応している
- Google翻訳が対応している言語は、話される人口が数千万人以上いる言語と仮定すると、インドやインドネシアの言葉が多く対応されれているのもの納得である
- というかGoogle翻訳すごない?
じゃーねー。ロケットビーバーイ
給湯器が家の外にある北限の町はどこなのか長年の疑問をGoogleストリートビューで調査
給湯器が家の外にある北限の町はどこなのか人生で一度は気になったことはあるよね?オレはある
オレの故郷の北海道は室内の洗面所にあり、住んだことのある東京や神奈川、茨城でも給湯器は外にあった。
おそらく積雪量も関係しているだろうが、雪の少ない太平洋側を対象に仙台からと青森からの南北から絞り込んで調査していった。
調査方法はGoogleストリートビューを駆使してなんの変哲もない住宅街を確認していくという根気のいる作業をしていった。
以降は探しながらツイッターに書いていたものをそのまま乗せるのでライブ感を楽しんでもらいたい。
給湯器を室内に置く南限はどこなのか問題。
— コンユウ (@kon_yu) 2020年1月23日
北海道は室内の洗面所にある
まずは南から仙台。
仙台給湯器あり。前に聞いた話だと仙台のばあちゃんの家は中にあるけど、そいつの家の給湯器は外にあったような気がするって話。 https://t.co/xm7vXC8mxj
次に盛岡
盛岡でも外に給湯器があるのを発見した。 https://t.co/GHkAVYp4na
今度は逆に青森市から、無いのを探すのはけっこう大変だ、探すのを止め時がわからない
青森市まで行くと、給湯器が屋外になさそうだ。 https://t.co/Zot9UYlENl
だんだんストリートビューで給湯器を探すコツがわかってきたぞ 一軒家の給湯器はだいたい正面にので道路沿いの画像のストリートビューでは探しにくいが、アパートなどの集合住宅は正面のドア横辺りにある
だんだんストリートビューで給湯器を探すコツがわかってきたぞ
— コンユウ (@kon_yu) 2020年1月23日
一軒家の給湯器はだいたい正面にので道路沿いの画像のストリートビューでは探しにくいが、アパートなどの集合住宅は正面のドア横辺りにある
っていう謎のコツを掴んできた。
八戸でもなかった。(外)盛岡 => x > (内)八戸 絞れてきた。 https://t.co/csoizwXVQz
二戸にもなかった。もう少し南下する
田舎のほうが家が家離れているのでストリートビューから家の裏の方を探しやすい https://t.co/QS1z0vClp2
八幡市にも給湯器は外にはなさそうだ。 っていうか外に置く灯油のタンクがあるところは灯油ストーブを焚くぐらい寒いから給湯器は外になさそう https://t.co/bkxuP6ym2v
八幡市の東にある岩手町にもなさそう https://t.co/TZ04CSvptv
やっぱも盛岡には給湯器がある https://t.co/EUlkHfVsME
盛岡には外に給湯器があった。
結論: 今回の調査では給湯器が家の外にある北限は岩手県盛岡市!!
この結論を盛岡仮説として学会で発表したいと思う次第である。