当前位置 博文首页 > 雪山飞猪:教你用一个整数存储每月打卡记录

    雪山飞猪:教你用一个整数存储每月打卡记录

    作者:雪山飞猪 时间:2021-06-29 19:03

    目录
    • 需求
    • 常规思路
    • 高级思路
    • Go语言实现
    • 思路延伸

    需求

    有个需求,要存储当月的所有打卡记录,日历

    比如以下是我的打卡记录

    对应到JSON的数据是这样的

    {
        "day1":1,
        "day2":1,
        "day3":0,
        "day4":1,
        "day5":0,
        "day6":0,
        "day7":1,
        "day8":0,
        "day9":0,
        "day10":1,
        "day11":0,
        "day12":0,
        "day13":1,
        "day14":0,
        "day15":1,
        "day16":0,
        "day17":1,
        "day18":0,
        "day19":0,
        "day20":0,
        "day21":0,
        "day22":0,
        "day23":0,
        "day24":0,
        "day25":0,
        "day26":0,
        "day27":0,
        "day28":1,
        "day29":0,
        "day30":0,
        "day31":0
    }
    

    1代表打卡,0代表没打卡

    常规思路

    建立31个字段,存储每天的打卡记录,在数据库中是这样的,打卡的天就存储为1。

    比如如下的表

    这样的实现主要有以下缺点

    1. 浪费空间。只是为了存储一个0和1,建立了31个字段
    2. 不利于扩展。如果要追加打卡记录,得追加字段

    高级思路

    我们知道二进制只有0和1,非常适合这样的场景,只用一个32位的整型的数字,就可以存储上面这个31天的所有打卡

    用二进制数表现是这样的

    1101001001001010100000000001000
    

    对应到二进制的整数是1764048904

    也就是说,其实我们只需要用一个二进制的数字,就可以表示31天的打卡状态

    所以,我们需要一个这样的功能

    1. 传递一个月的打卡状态,返回一个对应的整数
    2. 传递一个整数,返回一个月的打卡状态

    Go语言实现

    //签到列表-->整数
    func GetNumByList(list []int, max int) int {
        n := 0
        for i := 0; i < max; i++ {
            if list[i] == 1 {
                //将指定的二进制位设置为1
                n = n | (1 << i)
            }
        }
        return n
    }
    
    //整数-->签到列表
    func GetListByNum(n int, max int) []int {
        res := make([]int, max)
        for i := 0; i < max; i++ {
            if n&(1<<i) == (1 << i) {
                //二进制位为1的设置已打卡
                res[i] = 1
            }
        }
        return res
    }
    

    我们来测试一下

    func main() {
        max := 31
        list := []int{1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}
        fmt.Println("原始打卡记录:")
        fmt.Println(list)
    
        fmt.Println("打卡记录转数字:")
        num := GetNumByList(list, max)
    
        fmt.Println(num)
    
        fmt.Println("数字还原打卡记录:")
        fmt.Println(GetListByNum(num, max))
    
    }
    

    运行后的结果输出

    这样,我们就非常地方便用一个字段存储了所有的打卡状态。

    这里主要用到了位运算,下面贴上GO语言的位操作

    << [ 左移 ]
    1 << 2 == 4
    输出 0100 ,相比右移更常见,移位后空缺的部分全部填0
    >> [ 右移 ]
    10 >> 2 == 2
    输出 0010
    x ^ y [ 异或 ]
    10 ^ 2 == 8
    操作的结果是如果某位不同则该位为1, 否则该位为0
    x | y [ 或 ]
    10 | 2 == 10
    两个相应的二进位中只要有一个为1, 该位的结果值为1
    x & y [ 与 ]
    10 & 2 == 2
    两个相应的二进位都为1, 该位的结果值才为1,否则为0
    ^x [ 取反 ]
    ^2 == -3
    减1取反 补码
    

    思路延伸

    这样的思路可以应用在需要存储选中状态的需求中
    比如我刚接到一个这样的需求,直播抽奖功能,需要存储奖品类型

    像这样的场景就非常适合用一个字段存储所有的选中状态,并且当奖品类型添加的时候,还有很好的扩展性。

    bk