50

新年彩蛋之中大奖

 5 years ago
source link: https://studygolang.com/articles/18149?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

—— 新年快乐,给每个有梦想的程序员

生成随机号

小概率事件也要做的一丝不苟,大家都是程序员,为啥要用别人家写的随机代码。嘎嘎!

双色球蓝号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 属性来达到目的, mapkey 存储生成的随机号, 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 。我们将生成的记录作为 KeyValue 不存储实际的值了。

对于 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")
}

生成的图片如下:

1460000018111120

总结

项目的代码在 boredom ,代码非常简单。祝大家早日中大奖!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK