いきさつ
ACALL株式会社では、slackで抽選をするbotがあって、会議のファシリテーターとか
誰か担当を決めるときにそのbotを使って決めています。
Go + AWS LambdaでSlackの社員抽選botを作った話
https://blog.acall.jp/2019/11/develop-slack-lottery-bot/
このbotの精度がどうも怪しい。
私と弊社CTOがやたら当選する気がする!
最初は「まあプログラムのランダム性ってそんなもんだよね。ハハハ」なんて言ってたもんですが、
なかなかの頻度で当選するので、これはもしやバグが寝てるのでは・・・・と思い、精度を検証してみました。
前提
- goのバージョンは1.14rc1を使用
検証内容
抽出処理の確認
まず、抽選botのランダム処理は下記のようになっています。
func lotteryOneUserFromUsers(userIDs []string) string {
rand.Seed(time.Now().UnixNano())
userID := userIDs[rand.Intn(len(userIDs))]
return userID
}
userIDsには抽選対象のユーザーが入っているわけですが、この処理自体はシンプルで怪しいところはない。
というわけで、この処理を複数回実行して確率を算出します。
確認用コード
下記のようなコードを用意しました。
package main
import (
"math/rand"
"time"
"fmt"
"strconv"
)
func main() {
userIDs := []string{"CTO","EM","山田","鈴木","藤田","田中","中島","北川","西山","川口","遠藤"}
var lottery_cnt map[string]int = make(map[string]int)
const LOOP_CNT int = 1000
//初期化
for _, s := range userIDs {
lottery_cnt[s] = 0
}
var userID string
for i := 0; i < LOOP_CNT; i++ {
userID = lotteryOneUserFromUsers(userIDs)
lottery_cnt[userID] += 1
}
fmt.Println("抽選実施回数は" + strconv.Itoa(LOOP_CNT) + "回です")
fmt.Println("----------------------------")
sum := 0
for k, v := range lottery_cnt {
fmt.Println(k + " : " + strconv.FormatFloat((float64(v)/float64(LOOP_CNT)*100.0), 'f', 1, 64) + "% (" + strconv.Itoa(v) + "回)")
sum += v
}
}
// 抽選処理実体
func lotteryOneUserFromUsers(userIDs []string) string {
rand.Seed(time.Now().UnixNano())
userID := userIDs[rand.Intn(len(userIDs))]
return userID
}
LOOP_CNTで抽選する回数を設定しています。
いざ検証
さて、上記のコードを使ってまずは1,000回の抽選で検証してみます。
~/d/s/lottery ❯❯❯ go run probability.go
抽選実施回数は1000回です
----------------------------
山田 : 7.5% (75回)
鈴木 : 10.8% (108回)
西山 : 7.3% (73回)
遠藤 : 8.8% (88回)
中島 : 10.0% (100回)
北川 : 9.9% (99回)
川口 : 9.9% (99回)
CTO : 7.8% (78回)
EM : 10.1% (101回)
藤田 : 9.8% (98回)
田中 : 8.1% (81回)
ほうほう・・・それなりのゆらぎがあります。
ちなみにEMが私です。
理論上は回数を重ねれば重なるほど確率は均一になるはずなので、回数を増やします。
次は1万回でやってみます。
~/d/s/lottery ❯❯❯ go run probability.go
抽選実施回数は10000回です
----------------------------
遠藤 : 8.6% (856回)
EM : 9.4% (936回)
山田 : 9.2% (921回)
鈴木 : 9.1% (914回)
藤田 : 9.1% (908回)
中島 : 9.6% (960回)
川口 : 9.3% (927回)
CTO : 8.9% (889回)
田中 : 9.0% (900回)
北川 : 9.2% (921回)
西山 : 8.7% (868回)
前回よりも均一化されてきました。ほぼ9%前後です。
では一気に、50万件まで増やしてみます。
~/d/s/lottery ❯❯❯ go run probability.go
抽選実施回数は500000回です
----------------------------
北川 : 9.1% (45357回)
西山 : 9.1% (45277回)
川口 : 9.1% (45562回)
CTO : 9.0% (45103回)
山田 : 9.2% (45825回)
藤田 : 9.1% (45574回)
田中 : 9.1% (45283回)
EM : 9.2% (45765回)
鈴木 : 9.1% (45417回)
中島 : 9.1% (45630回)
遠藤 : 9.0% (45207回)
ほぼゆらぎはなくなりましたね!
50万回やれば問題ないと言えそうです。
が!
会社の抽選BOTで50万回もやるわけはない。
実際に弊社Slackの抽選bot利用回数を調べると163回の抽選が行われていました。
というわけで163回で検証してみます。
~/d/s/lottery ❯❯❯ go run probability.go
抽選実施回数は163回です
----------------------------
西山 : 9.2% (15回)
川口 : 12.9% (21回)
藤田 : 7.4% (12回)
中島 : 10.4% (17回)
北川 : 10.4% (17回)
鈴木 : 7.4% (12回)
田中 : 9.2% (15回)
遠藤 : 6.1% (10回)
CTO : 7.4% (12回)
EM : 10.4% (17回)
山田 : 9.2% (15回)
ふーむ・・・・多い人で12%。前後2〜3%程度のゆらぎですね。
母数が少ないとけっこうバラけます。
まとめ
slack上でのbot実行回数の総回数もすくないし、当選確率が均一化されないのは仕方ない。
よく当選する気がするのは気のせいかもしれない。気のせいでしょうきっと。
これからもこのbotは運用されるので、確率が均一化されていくのを期待します!