用欧拉计划学Rust编程(第61题)
由于研究Libra等数字货币编程技术的需要,学习了一段时间的Rust编程,一不小心刷题上瘾。我把解决63道问题的过程记录了下来,写成了一本《用欧拉计划学 Rust 编程》PDF电子书,请随意下载。
链接:https://pan.baidu.com/s/1NRfTwAcUFH-QS8jMwo6pqw
提取码:qfha
“欧拉计划”的网址:
https://projecteuler.net
英文如果不过关,可以到中文翻译的网站:
http://pe-cn.github.io/
这个网站提供了几百道由易到难的数学问题,你可以用任何办法去解决它,当然主要还得靠编程,编程语言不限,论坛里已经有Java、C#、Python、Lisp、Haskell等各种解法,当然如果你直接用google搜索答案就没任何乐趣了。
这次解答的是第61题:
https://projecteuler.net/problem=61
题目描述:
循环的多边形数
三角形数、正方形数、五边形数、六边形数、七边形数和八边形数统称为多边形数。它们分别由如下的公式给出:
三角形数 | P3,n=n(n+1)/2 | 1, 3, 6, 10, 15, … |
正方形数 | P4,n=n2 | 1, 4, 9, 16, 25, … |
五边形数 | P5,n=n(3n−1)/2 | 1, 5, 12, 22, 35, … |
六边形数 | P6,n=n(2n−1) | 1, 6, 15, 28, 45, … |
七边形数 | P7,n=n(5n−3)/2 | 1, 7, 18, 34, 55, … |
八边形数 | P8,n=n(3n−2) | 1, 8, 21, 40, 65, … |
由三个4位数8128、2882、8281构成的有序集有如下三个有趣的性质。
- 这个集合是循环的,每个数的后两位是后一个数的前两位(最后一个数的后两位也是第一个数的前两位)。
- 每种多边形数——三角形数(P3,127=8128)、正方形数(P4,91=8281)和五边形数(P5,44=2882)——在其中各有一个代表。
- 这是唯一一组满足上述性质的4位数有序集。
存在唯一一个包含六个4位数的有序循环集,每种多边形数——三角形数、正方形数、五边形数、六边形数、七边形数和八边形数——在其中各有一个代表。求这个集合的元素和。
〓
〓
〓
〓
请
先
不
要
直
接
看
答
案
,
最
好
自
己
先
尝
试
一
下
。
解题过程:
把复杂的问题分解为一个一个的简单问题。
第一步: 先把所有四位数求出来
利用map()、filter()和take_while()等函数,可以用一行语句将数组生成。
let p3: Vec<u32> = (1..)
.map(|n| n * (n + 1) / 2)
.filter(|&n| n > 1000)
.take_while(|&n| n <= 9999)
.collect();
let p4: Vec<u32> = (1..)
.map(|n| n * n)
.filter(|&n| n > 1000)
.take_while(|&n| n <= 9999)
.collect();
如果这样生成六种多边形数,会有大量的重复代码,可以闭包作为函数的参数,统一写一个函数,这样代码非常简练。
let p3 = gen_poly_numbers(1000, 9999, |n| n * (n + 1) / 2);
let p4 = gen_poly_numbers(1000, 9999, |n| n * n);
let p5 = gen_poly_numbers(1000, 9999, |n| n * (3 * n - 1) / 2);
let p6 = gen_poly_numbers(1000, 9999, |n| n * (2 * n - 1));
let p7 = gen_poly_numbers(1000, 9999, |n| n * (5 * n - 3) / 2);
let p8 = gen_poly_numbers(1000, 9999, |n| n * (3 * n - 2));
fn gen_poly_numbers(start: u32, end: u32, f: fn(u32) -> u32) -> Vec<u32> {
(1..).map(f)
.filter(|&n| n >= start)
.take_while(|&n| n <= end)
.collect()
}
这里的一个语法知识点是f: fn(u32) -> u32的写法,把一个函数作为参数传递给另一个函数。
第二步: 递归算法
现在仍不要直接奔向最后的问题,先从简单的情况入手,用题目中给出的三个多边形数[8128, 8281, 2882],实现一个递归算法,验证算法的正确性。
算法描述:
1)从p3中的取一个数 for n in p3
2)假设这个数是8128,记录到found数组中,我们下一步的任务是从[p4, p5]数组中寻找28开头,81结尾的两个数,伪代码:find([p4, p5], 28, 81, [8128])
3)先在p4中找,再在p5中找 for cur_list in lists
4)在数组中寻找以28开头的数 for n in cur_list
5)假设是在p5中找到了2882,此时found数组变为[8128, 2882],递归调用,在[p4]中查找以82开头,81结尾的一个数 find([p4], 82, 81, [8128, 2882])
5.1)在p4里会找到一个以82开头的数,即8281,更新found,再调用 find([], 81, 81 [8128, 2882, 8281]
5.2)从这里可以发现递归函数的退出机制,待查的数组已经为空,要找的开头与结尾正好相等。
递归函数的源代码:
fn find(lists: &[&[u32]], start: u32, end: u32, found: &[u32]) {
if lists.is_empty() && start == end {
let result = found.iter().sum::<u32>();
println!("{:?} {}", found, result);
}
for (i, &cur_list) in lists.iter().enumerate() {
for &n in cur_list {
if head(n) == start {
let mut lists_copy = lists.to_vec();
lists_copy.remove(i);
let mut found_copy = found.to_vec();
found_copy.push(n);
find(&lists_copy, tail(n), end, &found_copy);
}
}
}
}
lists.copy.remove(i)的含义,假设当前搜索的数组为[p4, p5, p6, p7, p8],如果在p5中找到了满足条件的数,后续的搜索范围将是[p4, p6, p7, p8],这里的i记住数组中的位置,remove(i)将删除p5。
主程序中的代码:
for n in p3 {
find(&[&p4, &p5], n % 100, n / 100, &[n]);
}
第三步:解决最终的问题
搜索3个数的代码测试通过后,再搜索6个数。
for n in p3 {
find(&[&p4, &p5, &p6, &p7, &p8], n % 100, n / 100, &[n]);
}
由于p8中的数据元素较少,先搜索p8可能会稍快一点。
--- END ---
我把解决这些问题的过程记录了下来,写成了一本《用欧拉计划学 Rust 编程》PDF电子书,请随意下载。
链接:https://pan.baidu.com/s/1NRfTwAcUFH-QS8jMwo6pqw
提取码:qfha
由于欧拉计划不让发布100题之外的解题步骤,否则封号,所以最新PDF不再公开,请加我微信(SLOFSLB)索要最新的PDF电子书。
----==== Email: slofslb (GTD) qq.com 请将(GTD)换成@ ====----
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
作者:申龙斌的程序人生
---- 魔方、桥牌、象棋、游戏人生...
---- BASIC、C++、JAVA、C#、Haskell、Objective-C、Open Inventor、程序人生...
---- GTD伴我实现人生目标
---- 区块链生存训练
---- 用欧拉计划学Rust编程
---- 申龙斌的读书笔记(2011-2019)
----