※ 本当に稼ぎたい人以外は見ないでください

2021年4月9日アドベントカレンダー, 未分類

はじめに

この記事はシス研アドベントカレンダー4日目の参加記事です.

皆さん,お金に困ってはいませんか?

僕はめちゃくちゃ困っています.

というわけで本記事では,マルコフ連鎖を用いてナンバーズ3の当選番号を予想していきます.

Go言語で書いていますが,ライブラリはcsv読み込みと文字列操作にしか使っていないので他の言語でも実装できます.暇な人は一緒にコード書いてね!

マルコフ連鎖とは

これは,走れメロスの一節です.

この図からわかるように,「邪智暴虐」という単語にフォーカスすると次に来る単語は「の」のみです.つまり,「邪智暴虐」の次には100%の確率で「の」が現れます.
次に,「の」にフォーカスすると「王」と「牧人」が来ることがわかります.このとき,「の」の次に来るのは50%の確率で「王」であり,50%の確率で「牧人」が来ることがわかります.

この手法を用いて,n回目の数字からn+1回目の数字を予測していきます.

実際にコードを書いてみよう

  1. CSVファイルをダウンロードする
  2. CSVファイルを読み込んでみる
  3. とりあえず各数字の割合を出してみる
  4. マルコフ連鎖で確率を計算してみる

0. CSVファイルをダウンロードする

mk-modeというサイトからcsvファイルをダウンロードしてきましょう.
本記事ではナンバーズ3を使用しますが,ナンバーズくじであれば桁数が増えても問題なく動くと思います.しらんけど.

CSVを開くと,

No,抽選日,当選数字,口数[ストレート],口数[ボックス],口数[セット・ストレート],口数[セット・ボックス],口数[ミニ],金額[ストレート],金額[ボックス],金額[セット・ストレート],金額[セット・ボックス],金額[ミニ],販売実績
1,1994/10/07,191,0,0,0,0,0,119800,39900,79800,19900,11900,0
2,1994/10/14,988,0,0,0,0,0,333500,111100,222300,55500,33300,0
3,1994/10/21,194,0,0,0,0,0,71700,11900,41800,5900,7100,0

というようにデータが並んでいますが,一行目のNo,抽選日,当選数字(以下略)という行を削除してください.

1. CSVファイルを読み込んでみる

package main
import (
    "encoding/csv"
    "fmt"
    "os"
)
func main() {
    file, err := os.Open("./NUMBERS3_ALL.csv")
    if err != nil {
        panic(err)
    }
    reader := csv.NewReader(file)
    var line []string
    for {
        line, err = reader.Read()
        if err != nil {
            break
        }
        fmt.Println(line)
    }
}

“os" packageでcsvファイルを開き,それを"encoding/csv" packageで配列に変換し,"fmt" packageで出力しています.

[1 1994/10/07 191 0 0 0 0 0 119800 39900 79800 19900 11900 0]
[2 1994/10/14 988 0 0 0 0 0 333500 111100 222300 55500 33300 0]
[3 1994/10/21 194 0 0 0 0 0 71700 11900 41800 5900 7100 0]
[4 1994/10/28 105 0 0 0 0 0 57200 9500 33300 4700 5700 0]
[5 1994/11/04 592 0 0 0 0 0 113400 18900 66100 9400 11300 0]
[6 1994/11/11 792 0 0 0 0 0 102600 17100 59800 8500 10200 0]
[7 1994/11/18 708 0 0 0 0 0 149100 24800 86900 12400 14900 0]

このように,CSVを読み込むことができましたね.
しかし,当選番号以外のデータは今回使用しないので,当選番号のみを取り出すように書き換えます.
さらに,取り出した当選番号を一桁ずつに分割していきます.

package main
import (
    "encoding/csv"
    "fmt"
    "os"
)
func main() {
    file, err := os.Open("./NUMBERS3_ALL.csv")
    if err != nil {
        panic(err)
    }
    reader := csv.NewReader(file)
    var line []string
    for {
        line, err = reader.Read()
        if err != nil {
            break
        }
-       fmt.Println(line)
+       fmt.Println(line[2])
+       fmt.Println(strings.Split(line[2], ""))
    }
}
191
[1 9 1]
988
[9 8 8]
194
[1 9 4]

扱いやすい形になりましたね.今後は,配列になったデータを扱います.

2. とりあえず各数字の割合を出してみる

さて,データを読み込むことができたところで,実際にそのデータを利用してみましょう.
まずは,各数字の割合を出してみます.

package main
import (
    "encoding/csv"
    "fmt"
    "os"
    "strings"
)
func main() {
    file, err := os.Open("./NUMBERS3_ALL.csv")
    if err != nil {
        panic(err)
    }
    reader := csv.NewReader(file)
    var line []string
    numbersArray := make([][]string, 0)
    for {
        line, err = reader.Read()
        if err != nil {
            break
        }
        num := strings.Split(line[2], "")
        numbersArray = append(numbersArray, num)
    }
    p := getPercentage(numbersArray)
    fmt.Println(p)
}
func getPercentage(data [][]string) map[string]float64 {
    counts := countData(data)
    prob := calcPercent(counts, len(data))
    return prob
}
func countData(data [][]string) map[string]float64 {
    counts := map[string]float64{}
    for _, d := range data {
        for _, index := range d {
            counts[index]++
        }
    }
    return counts
}
func calcPercent(counts map[string]float64, length int) map[string]float64 {
    for key := range counts {
        counts[key] /= float64(length * 3)
    }
    return counts
}
map[0:0.09887455070414236 1:0.09987625950150256 2:0.10199752519003005 3:0.10117258853338047 4:0.09887455070414236 5:0.09457309528018384 6:0.09857993046962465 7:0.09999410759530965 8:0.10129043662718755 9:0.1047669553944965]

意外とばらつきがあることがわかりましたね.
というわけで,確率の高かった’9’と’8’と…えーと…

この出力されたデータ,めっちゃ見にくいですよね.なので出力するための関数を追記します.

これはおまけなので,別に見にくくない人は飛ばしても大丈夫です.


func printPercent(p map[string]float64) { keys := []string{} for key := range p { keys = append(keys, key) } for i := 0; i < len(keys); i++ { for j := 0; j < len(keys)-i-1; j++ { low, _ := strconv.Atoi(keys[j]) high, _ := strconv.Atoi(keys[j+1]) if low > high { tmp := keys[j] keys[j] = keys[j+1] keys[j+1] = tmp } } } for _, key := range keys { fmt.Println(key, p[key]) } }
0 0.09887455070414236
1 0.09987625950150256
2 0.10199752519003005
3 0.10117258853338047
4 0.09887455070414236
5 0.09457309528018384
6 0.09857993046962465
7 0.09999410759530965
8 0.10129043662718755
9 0.1047669553944965

かなり見やすくなりましたね.

この結果をもとに,’9’,’2’,’8’を買って見みることにします.

3. マルコフ連鎖で確率を計算してみる

おまたせしました.ここからが本編です.

冒頭で紹介したように,マルコフ連鎖を用いて次回の番号を予想してみましょう.

package main
import (
    "encoding/csv"
    "fmt"
    "os"
    "strconv"
    "strings"
)
func main() {
    file, err := os.Open("./NUMBERS3_ALL.csv")
    if err != nil {
        panic(err)
    }
    reader := csv.NewReader(file)
    var line []string
    numbersArray := make([][]string, 0)
    for {
        line, err = reader.Read()
        if err != nil {
            break
        }
        num := strings.Split(line[2], "")
        numbersArray = append(numbersArray, num)
    }
    m := markov(numbersArray)
    markovKeys := []string{}
    for key := range m {
        markovKeys = append(markovKeys, key)
    }
    for i := 0; i < len(markovKeys); i++ {
        for j := 0; j < len(markovKeys)-i-1; j++ {
            low, _ := strconv.Atoi(markovKeys[j])
            high, _ := strconv.Atoi(markovKeys[j+1])
            if low > high {
                tmp := markovKeys[j]
                markovKeys[j] = markovKeys[j+1]
                markovKeys[j+1] = tmp
            }
        }
    }
    for _, key := range markovKeys {
        printMarkov(m[key], key)
    }
    fmt.Println(numbersArray[len(numbersArray)-1])
}
func markov(data [][]string) map[string](map[string]float64) {
    markovCount := countMarkov(data)
    markovProb := calcMarkov(markovCount)
    return markovProb
}
func countMarkov(data [][]string) map[string](map[string]float64) {
    markovCount := map[string](map[string]float64){}
    for i := 0; i < len(data)-1; i++ {
        for _, key := range data[i] {
            if _, ok := markovCount[key]; !ok {
                markovCount[key] = map[string]float64{}
            }
            for j := 0; j < len(data[i]); j++ {
                markovCount[key][data[i+1][j]] += 1.0
            }
        }
    }
    return markovCount
}
func calcMarkov(markovCount map[string](map[string]float64)) map[string](map[string]float64) {
    for k, c := range markovCount {
        sum := 0.0
        for key := range c {
            sum += markovCount[k][key]
        }
        for key := range c {
            markovCount[k][key] /= sum
        }
    }
    return markovCount
}
func printMarkov(p map[string]float64, k string) {
    fmt.Println("====", k, "====")
    keys := []string{}
    for key := range p {
        keys = append(keys, key)
    }
    for i := 0; i < len(keys); i++ {
        for j := 0; j < len(keys)-i-1; j++ {
            low, _ := strconv.Atoi(keys[j])
            high, _ := strconv.Atoi(keys[j+1])
            if low > high {
                tmp := keys[j]
                keys[j] = keys[j+1]
                keys[j+1] = tmp
            }
        }
    }
    for _, key := range keys {
        fmt.Println(key, p[key])
    }
    fmt.Println("===========")
}
==== 0 ====
0 0.09654350417163289
1 0.09614620580055622
2 0.10627731426301153
3 0.10508541914978149
4 0.09952324195470799
5 0.10170838299562972
6 0.09296781883194279
7 0.09237187127532777
8 0.09932459276916965
9 0.11005164878823996
===========
==== 1 ====
0 0.10167158308751229
1 0.09970501474926254
2 0.1024582104228122
3 0.10068829891838742
4 0.09262536873156342
5 0.09872173058013765
6 0.09223205506391347
7 0.1056047197640118
8 0.10029498525073746
9 0.10599803343166175
===========
(略)
[5 4 8]

最後の番号が’5 4 8’なので,次に来る数字は’9 8 1’と予想できました.

というわけで,9 8 1を買ってみようと思います.

コード解説


func countMarkov(data [][]string) map[string](map[string]float64) { markovCount := map[string](map[string]float64){} for i := 0; i < len(data)-1; i++ { for _, key := range data[i] { if _, ok := markovCount[key]; !ok { markovCount[key] = map[string]float64{} } for j := 0; j < len(data[i]); j++ { markovCount[key][data[i+1][j]] += 1.0 } } } return markovCount }

countMarkov関数は,numbersArrayを受け取り各数字の出現回数を計測します.
回数の計測をfloat64で行っているのは,次で説明する関数で割合に変換するためです.


func calcMarkov(markovCount map[string](map[string]float64)) map[string](map[string]float64) { for k, c := range markovCount { sum := 0.0 for key := range c { sum += markovCount[k][key] } for key := range c { markovCount[k][key] /= sum } } return markovCount }

calcMarkovでは,countMarkov関数の結果を受け取り各数字の次に出た数字の回数を足し合わせたもので割り,割合を出します.

おまけ:実際に買いに行ってみよう

ここからは半分日記みたいなものなので,読まなくてもいいです.

記事を書いた日と購入日が違うため,記事内で購入すると言っていた番号と違ってます.ごめんね!

ちなみにめちゃくちゃハズレでした.買わなくてよかった.

最近はネットで宝くじが購入できるらしいです.
便利ですね〜

無事買えたので,抽選結果を待ちましょう.

抽選結果

うわ〜〜〜〜〜〜〜〜〜〜!!!!!!!!!!!!!!!!!

なけなしの600円が!!!!!!!!!!!!!!!!!!

まとめ

いかがでしたか?

みなさんも宝くじを買ってみては?

2021年4月9日アドベントカレンダー, 未分類

Posted by ふくだ