sironekotoroの日記

Perl で楽をしたい

Perl入学式 2018 in東京 秋開講 第3回 ピザ会の課題

Perl入学式 in 東京では各回の講義終了後にピザ会(ピザ&ジュース代は参加者負担)を開催しており、そこで受講者さんと雑談などをしております。
その中で、id:xtetsuji さんから課題が出てそれをコードで解決する、という試みを行っています。

今回のお題:Googleトレンドを非公式APIから取得し、各日のランキングをとり、前日と順位の変動があればそれが分かるようにする

というもの・・・だった気がします(曖昧
しかし、うちの実力不足で当日できたのは

  • 非公式APIから特定の日のデータを取得する
  • JSON形式のデータをPerlのデータ構造(リファレンス)に変換する
  • 関連する記事のタイトルを表示する(!?)

まででした。

最後の「関連する記事のタイトルを表示する(!?)」なんですが、当時は完全に勘違いしており、検索語を取ることができませんでした。
結果、無念のタイムアップとなったのでした。

作ってみたが・・・

2019年01月01日〜18日までで、連続した日に現れる検索ワードは「純烈」が2019年01月10日と翌2019年01月11日・・・のみ!

これは、完全に同じ単語が連続した日に現れた場合、という条件でしか抽出できなかったのが原因。 例えば、正月なので箱根駅伝についての話題は当然出てきていますが、

と、検索ワードとしては同一ではないので、別の検索語と判断してしまったわけです。

これらの語を「同じ意味をもつ語」として、「箱根駅伝」が3日連続して検索語に登場した、と判断するには文字列を分解して他の検索語とのマッチ率を見て同一の話題の単語か?を判断する
・・・って工程が必要になるんかなぁ?
ちょっと今の自分にはハードル高そうです・・・

Googleトレンドを非公式APIから取得し、各日のランキングをとり、以前に出現してたらその日と順位も表示する

このままでは悔しいので、同じ検索ワードが過去にランキング入りした場合、前回登場した日付と順位を表示する、ということにしてみます。

例えば同じ検索ワードが連続していない日に出てくることがあります。

  • 錦織圭
    • 2019年01月05日 5位
    • 2019年01月15日 5位
    • 2019年01月17日 5位
  • 地震
    • 2019年01月03日 1位
    • 2019年01月08日 4位
    • 2019年01月14日 4位
    • 2019年01月18日 3位

こういったデータの場合、例えば 01月08日に

04 地震 [前回出現:2019年01月03日 1位]

といったような表示が出るようにしてみました。

bitbucket.org

一応、プログラム本文にコメントをいっぱい書いてみたんですが、これ、うち自身も3ヶ月後に見て「何言ってるんだこの人・・・」ってなるパターンな気がします。
うわ、わたしのコメント力低すぎ・・・?

ちょっとだけ解説

時系列順に表示するだけであれば、日付をkeyとして、valueには順位順に検索ワードを格納した配列リファレンスを入れるデータ構造でいけます。
my %date_query; # date をkeyとするデータ構造 です。

20181231 => [ '紅白歌合戦 2018' ,  'メイウェザー' , '米津玄師' ,  '紅白' ... ];
20190101 => [ '箱根駅伝' ,  'MHPS' , 'マリウス' ,  '2019年運勢' ... ];

しかし、このデータ構造だけでは、検索ワードが過去のいつ出現したかを調べるときに大変です。

  • 1日前のハッシュkeyを把握する
  • valueデリファレンスし、検索ワードが配列リファレンスに格納されているかを調べる
  • 1日前で見つからなければ、さらに前日に遡り調べる

これを繰り返すのは厳しいです。100日とか計算するとして、100日目の検索ワードが1日目に出現していた場合、過去99日遡って探すことになります。
すぐ見つかれば良いですが、見つからなかったら・・・?
計算量も多そう。

ということで、検索語そのものをkeyとするデータ構造を作ります。
valueには2つのリファレンスを入れます。

  • 日付をkey、その日付の順位(ranking)と何回目の出現か(count)を格納したハッシュリファレンス
  • 文字列 history をkey、検索ワードが登場した日時を格納した配列リファレンス

さらに、history という文字列をkeyとしてvalueに配列リファレンスを格納。
この配列リファレンスに1つ以上の要素がある場合には、過去複数回出現している判断。
末尾から2番目にある要素を直前の出現日付として last_time に記録します。

my %query_date; # query をkeyとするデータ構造

"純烈" => {
           'history' => [
                          20190110,
                          20190111
                        ],
           '20190110' => {
                           'ranking' => 0,
                           'count' => 1
                         },
           '20190111' => {
                           'count' => 2,
                           'last_time' => 20190110,
                           'ranking' => 1
                         },

これにより、検索ワードと日付をkeyとして last_time があればそれが前回の出現日という処理をしています。

1つのデータ構造では難しいなぁ、と思ったら同じデータが入っていてもkeyを変えた構造のデータを作ってみるという感じです。
うちが最近勉強しているSQLだと同じレコードに別名をつけて副問い合わせをする、みたいな感じですかね。

もっと実装力やデータ構造力が高ければ、もっと簡単になりそうな気がするんですが、気がするだけでその具体的な道筋が見えない・・・ってのが今のうちの現在地です。
頑張ろう。

※第4回でやる「サブルーチン」を導入すると、コードの見栄えがもう少しスッキリするはず。

おまけ

このゲーム、1回遊ぶのに500円かかり、もらえる額はサイコロの目の数 × 100 なので、5 か 6 を出さないとペイしない。
サイコロを振って 5 か 6 が出る確率は 1/3なので、ボーナス抜きでは普通に負ける。

で、ボーナスが出る確率は

  • 1が3回連続で出る: 1/6 × 1/6 × 1/6 = 1/216
  • 4,5,6のいずれかが3回連続で出る: 3/6 × 3/6 × 3/6 = 27/216 = 1/8

うーん、具体的な計算してないけど、負け(2/3)を覆せるだけの確率ではなさそうだなぁ

bitbucket.org

試行回数: 10000
ボーナス回数: 440
最終損益: -700700

追記

サイコロの6の目がでないよー、との指摘をいただき、修正したら勝率が変わった!
(bitbucketにあげたコードは修正済みです)