1、3天的成果
代码在 https://github.com/caiqingfeng/rustguru ,完成了poker算法里最核心的建表算法,共计有900多行代码,包含注释和测试。为什么说在学习过程中心情很愉悦,有一个很重要的原因,就是把C++代码转成rust代码,包括循环、递归、HashMap、Vector、测试等等这些都几乎是直觉就搞定,除了早期第一个函数在理解使用Rust的内存管理机制上稍微费了点力气,其它的都非常顺利,几乎算是一版搞定。当然过程中也碰到有些有意思的问题,这篇文章就趁着自己还记得,新鲜热乎着,放在这里跟大家分享。
2、for循环的改造
我们在C++/golang代码里,特别喜欢写类似如下的代码:
for (int i=12; i>=0; i--) {
std::vector s;
s.push_back(i);
set.push_back(s);
}
Rust不支持这样的语法,那只能用
for i in 0..13 {
let mut s: Vec = Vec::new();
s.push((12-i) as u64);
comb.push(s);
}
这样带来一个问题,因为我们是希望逆序遍历,在循环体里需要把i换成12-i,当算法比较复杂的时候,这种简单替换带来了一些很让人费解的问题。
更好的解决方案是用while来替换。
let mut i:i32 = 12;
while i>=0 {
let mut s: Vec = Vec::new();
s.push(i as u64);
comb.push(s);
i=i-1;
}
这里有一个特别需要注意的是,要在循环体的所有出口(continue处)都要对计数器进行自减的工作。
let mut z:i32 = y-1;
while z>=0 {
if z == i { z=z-1; continue;}
}
3、数据溢出
我们在C++/golang里对32位无符号整数进行乘法运算的时候,例如41*41*41*43*37*37=4057172507,结果其实已经超过了32位的最大表达能力2147483647,但在C++/golang里,溢出会自动处理成取余运算,在rust里运行就会报错误 overflow,所以在代码里,就大量的使用了41u64*37u64*31u64*29u64*19u64这样的操作,避免出现overflow的情况发生。
4、单元测试
可以看到单元测试代码非常符合直觉,基本上我把C++的测试代码拷贝过来,改成符合Rust语法,就一版编译通过。
5、为什么要单元测试
这里给一个例子,用单元测试发现的问题。
对比下图中C++代码,其中第217行对应101行,当时在转C++代码到rust代码的时候,先是把循环换成for i in 0..13,然后把i替换成12-i逆序来,后来发现这样逻辑很不直观,最后还是换成了while循环,把12-i又全替换成i,在这里就误伤了,但我们在运行单元测试代码的时候,马上就发现了这个问题。