月度归档: 2019 年 10 月

Web开发学习手记(二):gin框架

Web开发学习手记(二):gin框架

前言

冰岩作坊的各种产品都与互联网相关,不可避免地会用到Go语言的网络框架,其中,gin就是一个使用广泛的框架。

新人任务中,我用gin完成了若干实例的开发,学习了gin的一些使用方法。这篇文章整理了我接触过的部分功能。由于使用的场景有限,这里只列举了有限的几个内容。

Router

Router,即路由器(不是一个小盒子那个),功能是监听某个端口的HTTP请求,并根据请求的URL用不同的Handler处理。

在gin中,可以通过gin.Default()获得一个Router,它的类型是gin.Engine。你可以将Handler和Middleware添加到这个Router上,使它按照规则处理请求。

对于Handler,你可以通过Router的GET、POST等方法绑定对于某一类型请求的Handler函数,并指定绑定的URL。

对于Middleware,你可以通过Router的Use方法绑定一个Middleware函数。一般在使用Middleware的时候,常常使用Group对某一URL的请求分类,在绑定Middleware的同时绑定Handler。

Handler

Handler,即处理函数,功能是处理指定的请求,并根据请求给出相应的响应。

在gin中,Handler应当是一个接受一个*gin.Context类型参数的函数。使用JSON进行C/S通信是一种很常用的方法,你可以通过gin.ContextBindJSON方法将请求体中的JSON内容解析进指定结构体中。结构体内的变量需要设置json反射标签。以下给出一个使用例。

type ReqType struct {
    var Data int `json:"data"`
}

func main() {
    router := gin.Default()
    router.POST("/", func(context *gin.Context) {
        var req ReqType
        err := context.BindJSON(&req)
        if err != nil {
            fmt.Println(err.Error())
            return
        }
        fmt.Println(req.Data)
    })
    _ = router.Run()
}

gin也可以很方便地使用JSON作出响应。gin.H是一个对map的简写,你可以用它简单地创建一个map,并用context.JSON方法作出响应。以下给出一个使用例。

type ReqType struct {
    var Data int `json:"data"`
}

func main() {
    router := gin.Default()
    router.POST("/", func(context *gin.Context) {
        var req ReqType
        err := context.BindJSON(&req)
        if err != nil {
            fmt.Println(err.Error())
            return
        }
        context.JSON(http.StatusOK, gin.H{
            "message": req.Data,
        })
    })
    _ = router.Run()
}

Handler还可以处理其他类型的请求,如GET、OPTIONS等,由于我没有接触过这些请求的用法,这里便留空了。

Middlerware

Middleware,即中间件,功能是在请求发给Handler之前对请求进行一些处理,比如验证权限。在我的实践中,我实现了一个验证JWT的Middleware,用于API的认证。

gin中提供了router.Group这一方法,可以对Router的某一URL进行多个操作,例如绑定Middleware和绑定Handler。可以先用router.Group获取到一个RouterGroup,然后用group.Use绑定一个Middleware。

Middleware的实现是一个接受一个*gin.Context类型的参数的函数。在这个函数中,你可以分析请求,如果需要拒绝请求,使用context.Abort方法,如果请求可以继续处理,使用context.Next方法。以下是一个使用Middleware对API做简单验证的例子。

type ReqType struct {
    var Pass int `json:"pass"`
}

func main() {
    router := gin.Default()
    
    api := router.Group("/api")
    api.Use(func(context *gin.Context) {
        var req ReqType
        err := context.BindJSON(&req)
        if err != nil || req.Pass != "password" {
            context.JSON(http.StatusUnauthorized, gin.H{
                "error": "error!",
            })
            context.Abort()
            return
        }
        context.Next()
    })
    api.POST("/", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "message": "welcome",
        })
    })

    _ = router.Run()
}
Popcount问题及算法

Popcount问题及算法

问题描述

在使用状态压缩或树状数组(Binary Indexed Tree)的时候,经常涉及位运算的操作。其中一类问题被称为Popcount问题:对于一个无符号整数$x$,求二进制表示下的$x$(即$(x)_2$)中$1$的个数。

如:$(2)_{10}=(10)_2$,则$x=2$时答案为$1$;$(255)_{10}=(11111111)_2$,则$x=255$时答案为$8$。

算法分析

很暴力的暴力 $O(\log x)$

直接将$x$以二进制形式转换为字符串,按照字符串的方法处理即可。

// Code by KSkun, 2019/10
int popcount(unsigned int x) {
    char str[32];
    memset(str, 0, sizeof(str));
    itoa(x, str, 2); // 以二进制转换x至字符串str
    int counter = 0;
    for (int i = 0; i < 32 && str[i] != 0; i++) {
        if (str[i] == '1') counter++;
    }
    return counter;
}

不那么傻的暴力 $O(\log x)$

直接用位运算处理上面那个暴力的流程不好吗?

// Code by KSkun, 2019/10
int popcount(unsigned int x) {
    int counter = 0;
    while (x > 0) {
        counter += (x & 1);
        x >>= 1;
    }
    return counter;
}

不那么暴力的暴力 最差$O(\log x)$

我们想起了在树状数组中常用的lowbit函数,可以利用那个每次找到一个1然后把它删掉,循环进行这个操作直至所有的1都被统计了一遍。

// Code by KSkun, 2019/10
int popcount(unsigned int x) {
    int counter = 0;
    while (x > 0) {
        counter++;
        x -= (x & -x);
    }
    return counter;
}

更好的算法:Shift and Add $O(\log \log x)$

这里可以采用类似分治的考虑方法。

我们将数字$x$按二进制拆分为2位为一组的形式,例如,令$x=(\overline{abcdefgh})_2$,拆分为$\overline{ab \ cd \ ef \ gh}$。

对于每一组的两个位,例如最高位一组的$a$和$b$,它们的取值非0即1,只需要把这两位上的值简单相加即可得到这两位的统计结果。由于$1+1=2=(10)_2$,最多也只需占用2位来存储结果,因此不妨用这两位之前的空间来存储结果即可。对于“将相邻两位加起来,并存储在原来的这两位的位置上”这一操作,我们可以先对原数$x$与$(01010101)_2$做按位与运算,再用原数右移一位的结果与$(01010101)_2$做按位与运算,再将两个结果加起来即得到这一步的结果。

以最开始的$x=(\overline{abcdefgh})_2$为例,首先做与运算得到$x_1=\overline{0b0d0f0h}$,然后右移一位做与运算得到$x_2=\overline{0a0c0e0g}$,这里设相加的结果为$x’=x_1+x_2=\overline{ABCDEFGH}$。

接下来,我们需要将$\overline{AB \ CD \ EF \ GH}$中每一组位置中的数值相加,即$\overline{AB} + \overline{CD} + \overline{EF} + \overline{GH}$,这里也可以用上面类似的方法计算,只是需要两位两位地移动。即:$\overline{00CD00GH}+\overline{00AB00EF}=\overline{A’B’C’D’ \ E’F’G’H’}$。接下来再进行一次$\overline{0000E’F’G’H’}+\overline{0000A’B’C’D’}$就可以得到结果了。

这种方法每次对相邻的两组做二分的分治,逐层合并结果(将相邻两组相加),最后得到结果。这是一种很巧妙的分治方法。

由于unsigned int本身的长度有限,可以直接以长度为总长度,就有了下面这种很tricky的实现。

// Code from http://leexh.com/blog/2014/10/25/popcount-problem/
// Author: lixinhe
// Adjusted to unsigned int
const unsigned int m1 = 0x55555555; //binary: 0101...
const unsigned int m2 = 0x33333333; //binary: 00110011..
const unsigned int m4 = 0x0f0f0f0f; //binary:  4 zeros,  4 ones ...
const unsigned int m8 = 0x00ff00ff; //binary:  8 zeros,  8 ones ...
const unsigned int m16 = 0x0000ffff; //binary: 16 zeros, 16 ones ...

int popcount(unsigned int x) {
    x = (x & m1) + ((x >> 1) & m1);
    x = (x & m2) + ((x >> 2) & m2);
    x = (x & m4) + ((x >> 4) & m4);
    x = (x & m8) + ((x >> 8) & m8);
    x = (x & m16) + ((x >> 16) & m16);
    x = (x & m32) + ((x >> 32) & m32);
    return x;
}

参考资料