新年彩蛋之中大獎
—— 新年快樂,給每個有夢想的程式設計師
生成隨機號
小概率事件也要做的一絲不苟,大家都是程式設計師,為啥要用別人家寫的隨機程式碼。嘎嘎!
雙色球藍號1-12、紅號1-33,非常簡單,只需保證生成的紅號不相互重複就可以,然後就是考慮如何做到真正的 隨機
。
還有一個問題就是如何儲存一組號碼。首先,分成紅區和藍區兩部分,最後一個號約定為藍號。另外,為了方便儲存,我們放棄了將每個數字用符號連線的方式,而是自定義了 34進位制
,用於保證每組號碼的長度都是7。
var redBall = map[int]rune{ 1:'1', 2:'2', 3:'3', //...... 31: 'V', 32: 'W', 33: 'X', } var redFlip = map[rune]int{ '1': 1, '2': 2, '3': 3, //... 'V': 31, 'W': 32, 'X': 33, }
我們提供一個編解碼的方法,用於將字串轉換為一組號碼。對應的,將一組號碼轉換為長度為7的字串。
隨機紅號範圍控制在 1-33
,藍號控制在 1-16
。所以,我們對當前納秒進行取餘,便可以保證資料的正確。對於去重部分,通過 map
屬性來達到目的, map
的 key
儲存生成的隨機號, value
儲存對應的編碼。因為 map
結構讀取資料時本身也是隨機的,所以在生成紅號和藍號的時候便多生成一部分,最後再取6個紅號,1個藍號。
type TwoColor struct { } //encode func (color *TwoColor) Encode(origin []int) string { runes := make([]rune, 0) for _, v := range origin { if elem, ok := redBall[v]; ok { runes = append(runes, elem) } return string(runes) } //decode func (color *TwoColor) Decode(origin string) []int { result := make([]int, 0) for _, v := range origin { if elem, ok := redFlip[v]; ok { result = append(result, elem) } } return result } //generate random numbers func (color *TwoColor) GenerateRandom() string { redResult := make(map[int]rune, 12) for len(redResult) < 12 { key := time.Now().Nanosecond()%33 + 1 redResult[key] = redBall[key] } blueBall := make(map[int]rune, 2) for len(blueBall) < 2 { key := time.Now().Nanosecond()%16 + 1 blueBall[key] = redBall[key] } index := 0 result := make([]rune, 7) for _, v := range redBall { index++ result = append(result, v) if index == 6 { break } } for _, v := range blueBall { result = append(result, v) break } return string(result) }
批量生成隨機數
批量生成不重複的隨機數,核心就是解決如何儲存的問題。每次都得跟之前生成的記錄做比較,保證當前生成的記錄是不重複的。
之所以有批量生成的需求,首先是為了使生成的記錄更隨機。其次,試想一下,如果你記憶體足夠大,生成 5000W
條記錄,官方開獎後,看看這 5000W
會不會中一注。如果這麼多都沒有中,那真是一件令人傷心的事情啊。
為了簡單方便,我們直接將生成的記錄儲存到本地記憶體中,這裡使用前面講到的 bigcache
。我們將生成的記錄作為 Key
, Value
不儲存實際的值了。
對於 ZSet
,我們這裡只想體現它的唯一屬性。只有當記錄不存在時才進行儲存。
type DBCenter struct { } func (db *DBCenter) ZSet(key string) { if db.Exists(key) { return } cache.Set(key, nil) } func (db *DBCenter) Exists(key string) bool { _, err := cache.Get(key) if err != nil { if _, ok := err.(*bigcache.EntryNotFoundError); ok { return false } } return true } func (db *DBCenter) Len() int { return cache.Len() }
批量生成的過程就是一個 for
迴圈的過程,並通過 Len
方法獲取當前生成的記錄數。
儲存開獎號碼
將每期的開獎號碼作為下一期的參考,是每個迷信中彩人的執著。
我們選擇將每期的中獎號碼儲存到檔案中,實在沒有必要使用關係型資料庫。在使用過程中,我們不會部署到多臺伺服器上。這個專案就是在個人電腦上執行設計的,單檔案儲存足夠了。
關於 write
方法 ,寫入的內容包括日期和中獎號碼,連線符使用了 SeparatorData
。在開啟檔案的模式中,我們指定了 O_APPEND
,將寫入追加到檔案末尾。這也導致最新的開獎號碼出現在檔案的末尾。當讀取最近幾期的開獎號碼時,需要定位開始讀取的位置。
const ( FilePath= "/Users/neojos/src/boredom/data/history.txt" SeparatorData = "\t" ) type FileData struct { } func (f *FileData) GetLatestHistory(limit int64) ([]string, error) { file, err := os.OpenFile(FilePath, os.O_CREATE|os.O_RDONLY, 0755) if err != nil { return nil, err } defer file.Close() info, err := file.Stat() if err != nil { return nil, err } rowLength := 0 scan := bufio.NewScanner(file) for scan.Scan() { rowLength = len(scan.Text()) + 1 //'\n' break } if err = scan.Err(); err != nil { return nil, err } result := make([]string, 0) file.Seek((info.Size()/int64(rowLength)-limit)*int64(rowLength), 0) scan = bufio.NewScanner(file) for scan.Scan() { tmp := strings.Split(scan.Text(), SeparatorData) result = append(result, tmp[len(tmp)-1]) } if err = scan.Err(); err != nil { return result, err } reverseResult := make([]string, limit) for i, v := range result { reverseResult[int(limit)-i-1] = v } return reverseResult, nil } func (f *FileData) Write(date, number string) error { file, err := os.OpenFile(FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0755) if err != nil { return err } defer file.Close() line := fmt.Sprintf("%s\t%s\n", date, number) _, err = file.WriteString(line) return err }
GetLatestHistory
用於獲取最近幾期的開獎號碼。因為每行寫入的長度相同,所以通過計算,可以得出當前檔案的行數,並將檔案的 offset
定位到指定的位置,從具體的 offset
開始讀取。另外,因為我們寫入的模式是 APPEND
,所以將獲取到的 slice
再做一次順序反轉,便得到最近幾期的開獎記錄了。
生成圖
通過 plot
來繪製一些簡易的圖形。將最近的開獎號碼作為資料來源,來觀察資料的走勢。雖然確實沒啥用處。
這段程式碼直接執行會 panic
,因為修改了 plot
中的部分程式碼。
type Curve struct { elements [][]int } func (curve *Curve) SetElem(input [][]int) { curve.elements = input } func (curve *Curve) GetBaseImg() error { pic, err := plot.New() if err != nil { return err } pic.X.Label.Text = "ball" pic.Y.Label.Text = "number" // Draw a grid behind the data pic.Add(plotter.NewGrid()) customTick := commaTicks{ CustomTicks: 7, } pic.X.Tick.Marker = customTick customTick.CustomTicks = 33 pic.Y.Tick.Marker = customTick picElem := make(map[string]plotter.XYer) for index, element := range curve.elements { points := plotter.XYs{} for k, v := range element { points = append(points, plotter.XY{X: float64(k + 1), Y: float64(v)}) } picElem[fmt.Sprintf("%d", index)] = points } plotutil.AddLinePoints(pic, picElem) return pic.Save(vgsvg.DefaultWidth, vgsvg.DefaultWidth, "base.png") }
生成的圖片如下:
總結
專案的程式碼在 boredom
,程式碼非常簡單。祝大家早日中大獎!