Battery IncludedなPythonの魅力
宝くじで何億円も当たったら、アパート経営でも始めて、気が向いたらコードを書く生活をしたいと思っていますが、なかなかそうもいきません。
今回は「ひと山当てよう」と銘打ってみましたが、もちろんお金儲けができるわけではなく、数字選択式宝くじのシミュレーションをするプログラムを皆さんに作っていただきました。
全部で18人の方々からご応募いただき、少し惜しかったお2人を除いて、16人の方々は正解でした。基本的な問題だったとは言え、CodeIQ挑戦者のレベルの高さがわかります。
Pythonでの出題だったのは、もちろん私がPythonしか採点できないからですが、これを機会にPythonには便利なライブラリが同梱されている、いわゆる"battery included"なところを体験していただきたかったという気持ちもありました。
架空の売り上げ枚数を生成するためのポイント
売り上げの数については、設問で次のようにしました。
1 何口売れるか、20万~30万口という幅の中から、それらしい数字が選ばれるような仕組みを考えてみてください。
なんともあいまいな書き方ですが、ここから正規分布を連想してもらえるかを期待していました。結果は、4人の方々に期待どおりのコードを書いていただきました。
正規分布についてはWebにもくわしい情報がたくさんあるで、そちらを参照していただくとして、Pythonには正規分布に従う乱数を生成する関数が標準ライブラリに含まれています。
たとえば以下のように記述することで、平均が250,000、標準偏差が50,000/3.0の正規分布に従う乱数を1つ取得できます。
確率変数なので、このコードを何度も実行すれば違った値になりますが、99.7%は200,000から300,000の間に入ります。
これは、標準偏差をσとすると、±3σの範囲になります。それをわかりやすくするために、50,000/3.0と書いています。
中には、「そもそも売り上げが正規分布するのか?」という鋭い指摘をコメント行に入れていただいた解答者もいらっしゃいました。ごもっともかと思います。もちろん、200,000から300,000の範囲で等確率に生成される、一様な乱数でもかまいません。
randomモジュールには、正規分布のほかにも、いろいろな分布に従う乱数を取得できる関数が用意されています。ぜひ一度試してみてください。
当たり判定と結果をまとめるコードはどう書くか
今回の問題のために用意した架空の宝くじ「LOTO-G」は、1から40までの中から5つの数字を選び、全部当たると1等、4つ当たると2等、3つ当たると3等になる、というものでした。
架空の販売くじは、約25万あることになります。そのすべてについて、当たりを判定する必要があります。
これは、以下のようにするといいでしょう。
・5つの数字の組をset型にする
・当たりの番号5つと、当籤を確かめる5つの番号で、interactionメソッドを使い、いくつの数字が重なるかを調べる
18人の応募者のうち最多の6人がこの方法を採用していました。
当たり判定の後、当籤順位ごとに結果をまとめるコードが必要ですが、次のような働きをしてくれるCounterを使うと便利です。
これを使えば、以下のように短く書けます。
これで
とすると、1等の当籤が何本あるか、すぐにわかります。resultの中に5がなくても、KeyErrorにならず、0が返ってくるところが便利です。
collections.Counterについては、英語ですが以下の記事がわかりやすく参考になります。
大きな数字はわかりやすい表示に
今回は、売り上げや主催者の取り分など、大きな数字がたくさん出てくるので、表示のときに3桁ごとに区切ると、よりわかりやすくなります。18人中7人の方々が、この処理をしてくださいました。
数字のカンマ区切りは、適切なlocaleを設定して、locale.format関数を使うことで実現できます。その時、grouping引数にTrueを指定するのがポイントです。
今回のようにお金を扱っている場合、通貨に関するlocaleを使う方法もあります(使うと、数字の先頭に円マークがつきます)。もちろん国際化も簡単で、日本円の場合、JPYを付けることもできます。
端数の問題をどう処理するか
大きな数字に限りませんが、お金を数字として扱うときにもっと問題になることがあります。それは端数の問題です。
当籤準備金は、売り上げの60%に設定していました。通常、複数口出る2等の当籤者は、この1/3を山分けすることになっています。しかし、これは割り切れない可能性があり、その場合は端数が出ます。
18人の解答者の中でお1人だけ、このことについてコメント行で言及されている方がいらっしゃいましたが、基本的には端数を回避するコードを書かれた方はありませんでした。
端数をどうするのか、問題文には書いてありませんでしたが、末尾に掲載した解答例では主催者の懐にこっそり入れることにしました。
ここで便利なのが、組み込み関数のdivmodです。この関数は、1つ目の引数を2つ目の引数で割り、商と余りをこの順のタプルで返してくれます。
Pythonには便利な道具がたくさん
プログラミングには、ちょっとした想像力が必要です。日本語など、普通の言語で書かれたことを、forとかifとか、まったくあいまいさのない、厳密なロジックに落とし込む必要があるのがプログラミングです。
次々にアルゴリズムが浮かんでくる天才ハッカーは別として、我々普通のプログラマは便利な道具がたくさんある方が仕事が早く終わります。そんな普通のプログラマのために、Pythonはとても便利なプログラミング言語です。
ところで、皆さんのコードを実行するついでに、入力として私の好きな番号を買い続けましたが、残念ながらまったく当たりませんでした。でもきっと来週のロト6も買ってしまうんだろうな……。
解答例