用欧拉计划学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#HaskellObjective-COpen Inventor、程序人生...
---- GTD伴我实现人生目标
---- 区块链生存训练
---- 用欧拉计划学Rust编程
---- 申龙斌的读书笔记(2011-2019)
----
posted @ 2020-03-20 10:29  申龙斌的程序人生  阅读(449)  评论(0编辑  收藏  举报