1.同余公式:
记得看书的时候看到一个定理:
定理:A-B 能被 N 整除,那么A 与 B 模 N 同余。记作 A ≡ B (mod N);大概意思是A (mod N) = B (mod N)。
1.1迫不及待的证明一下:
(A - B) % N = 0
那么我们可以假设 (A - B) / N = X
那么 A - B = NX
A = NX + B
= (NX + B) / N
= NX/N + B/N
那么根据公式我们可以得出 A / N 的余数为 B/N的余数
1.2 那么我反过来假设假设 A 与 B 模 N 同余,那么(A - B)一定可以被N整除。
证明:
A 与 B mod N 同余
那么我们可以得到:A 除以 N 商 y 余 z,B 除以 N 商 x 余 z;y和x 都为整数
那么A = yN + z
同理 B = xN + z
(A-B)/N = (yN + z - xN -z)/N
= (yN-xN)/N
= (y-x)N/N
= (y-x)
y-x等于正数。
1.3 那么我们在进一步去推论假设A mod N 的余数z 与 A mod N 同余
根据上面的定义,我们可以得到
A = yN + z
z = 0* N + z
(A - z)/N = (yN + z -z)/N
=y
很明显(A -z)是能被 N整除的,那么A 与 z 也是同余。
2.落实到地
楼主是在公司数据部门工作,且我的水平并不高,下面就以我实际工作中的一个列子,所以下面的方法和思路不确定会不会是最优解,只是提供一种新的思路。
任务需求:
从Mysql中拿出符合特征一批用户,具体不知道这批用户有多少个,然后ES中去匹配用户的相关消费行为,但是ES的terms有一个参数个数限制(由于公司并没有引入本数据仓库,和大数据框架,处理的方式都是用代码去拼接),我们假设这个参数的最大个数为limit 下面皆为go伪代码部分。
func queryReader[T any](agrs ...[]T)(io.reader){
.....
return ...
}
func QureyFromEs(buf io.reader){
....
}
func Query[T any](limit int,idSlice []T){
i := 0
for {
if i + limit >= len(idSlice) {
buf := queryReader([idSlice[:]...)
QueryFromEs(buf)
break
}
buf := queryReader(idSlice[:limit]...)
QueryFromEs(buf)
idSlice = idSlice[limit:]
}
}
咋看一下这段代码其实非常简洁利于阅读了,但是其实有两个问题;
idSlice = idSlice[limit:]这种复制重新赋值的方式内存开销,当数据非常多,几百万上千万是很有可能出现om。(如果我们不采用切片重新赋值的方式,当怎么去防止下标溢出。)再来改一次代码:
func queryReader[T any](agrs ...[]T)(io.reader){
.....
return ...
}
func QureyFromEs(buf io.reader){
....
}
func Query[T any](limit int,idSlice []T){
start := 0
end := i + limit
A = len(idSlice)
for { //这里依旧不能使用end <= A 的情况。大家可以自己去思考
if end => A && start < A {
buf := queryReader(idSpice[:])
QueryFromEs(buf)
break
}
buf := queryReader()
QueryFromEs(buf)
start := end
end := end + limit
}
}
实际牺牲了代码的可读性来解决了om的问题。
2.姑且不说这个if到底能不能准确的保证在切片取值的时候下标不溢出,当循环体业务代码的判断非常复杂时,代码的可阅读性就降低,会引入非常多的逻辑判断。且同样的函数引用的代码写了两次
2.1 根据今天的公式进行代码优化
我们根据上面的数学定理。假设len(idSlice) 为A ,limit 为N 那么,我们可以得到A % N = z,这个z就是一个余数。那么这个z就是不能被N整除的。既有 z % N = z,那么A 与 z 模 N就是同余,换句话说就是(A-z) % N = 0;那么我们根据这个定理改写一下代码,我们先去z个元素出来,剩下的元素数量就为A - z ,那么它总能被limit的整除。也就是以后的切片无论怎么取都不会出现下标溢出的情况。
func queryReader[T any](agrs ...[]T)(io.reader){
.....
return ...
}
func QureyFromEs(buf io.reader){
....
}
func func Query[T any](limit int,idSlice []T){
A := len(idSlice)
start := 0
end := A % limit
for end <= A { //这里为什么就可以了使用end <= A
buf := queryReader(idSpice[start:end])
QueryFromEs(buf)
start := end
end := end + limit
}
}
优化了一次的代码再也不用去判断下标溢出的问题。同时重新赋值时切片元素过大负值时导致的om问题也到得到解决。当然这个程序还是不够完美,因为没有考虑刚好被整除的情况。也是就是start 和 end 都等于0的情况。但是对于Es查询没有影响,对于其他的存储就需要大家留心一下了。