专栏名称: 申龙斌的程序人生
分享可繁殖的知识与技能:GTD时间管理、读书心得、个人成长、财富自由之路
目录
相关文章推荐
程序员小灰  ·  Manus,又一国产AI封神了,一码难求! ·  3 天前  
OSC开源社区  ·  三句话让老板直接给我主动放假 ·  5 天前  
OSC开源社区  ·  听说技术大V们都被"manus"喂饱了,求邀 ... ·  4 天前  
OSC开源社区  ·  LFOSSA女神节福利,激励女性绽放多元力量 ... ·  4 天前  
51好读  ›  专栏  ›  申龙斌的程序人生

通过欧拉计划学习Rust编程(第17~21题)

申龙斌的程序人生  · 公众号  · 程序员  · 2019-08-25 06:18

正文

最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,所以先补一下Rust的基础知识。学习了一段时间,发现Rust的学习曲线非常陡峭,不过仍有快速入门的办法。

学习任何一项技能最怕没有反馈,尤其是学英语、学编程的时候,一定要“用”,学习编程时有一个非常有用的网站,它就是“欧拉计划”,网址: https://projecteuler.net

这个网站提供了几百道由易到难的数学问题,你可以用任何办法去解决它,当然主要还得靠编程,编程语言不限,论坛里已经有Java、C#、Python、Lisp、Haskell等各种解法,当然如果你直接用google搜索答案就没任何乐趣了。

学习Rust最好先把基本的语法和特性看过一遍,然后就可以动手解题了,解题的过程就是学习、试错、再学习、掌握和巩固的过程,学习进度会大大加快。

第17题

问题描述:

1到1000用英文单词写下来,求总字符个数(空格和连字符不算),例如:342,英文单词是:three hundred and forty-two。

问题分解:
  • 数字转换成英文单词
    • 1到19的拼写

    • 20到99的拼写

    • 100到999的拼写

    • 1000的拼写

  • 单词中去掉空格和连字符

  • 求字符总数

1到19的拼写比较特殊,需要分别对待,而超过20的数,可以利用递归调用。这里可以学到String的语法知识点。Rust中的字符串有点烦人,list[n].to_string()、"one thousand".to_string()的这种写法让人非常不适应。除了String之外,还有字符串切片(slice)、字符常量,需要看基础教程慢慢理解。
fn english_number(n: usize) -> String {
let list0_9 = vec![
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine",
];
if n <= 9 {
return list0_9[n].to_string();
}
if n <= 19 {
let list = vec![
"ten", "eleven", "twelve", "thirteen", "fourteen",
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen",
];
return list[n - 10].to_string();
}
if n <= 99 {
let a: usize = n / 10; // 十位
let b: usize = n % 10;
let list = vec![
"", "", "twenty", "thirty", "forty",
"fifty", "sixty", "seventy", "eighty", "ninety"
];
let str = list[a].to_string();
if b > 0 {
return str + "-" + &english_number(b);
}
return str;
}
if n <= 999 {
let a: usize = n / 100; // 百位
let b: usize = n % 100;
let str = list0_9[a].to_string() + " hundred";
if b > 0 {
return str + " and " + &english_number(b);
}
return str;
}
if n == 1000 {
return "one thousand".to_string();
}
return "unknown".to_string();
}

从字符串里移除特定的字符,要利用函数式编程,还有filter()和collect()函数,一气呵成。filter()函数中的*c又是让人容易写错的地方。

fn remove_space(s: &str) -> String {
s.chars().filter(|c| *c != ' ' && *c != '-').collect()
}

主程序就比较容易了,求和即可。

let




    
 mut sum = 0;
for n in 1..=1000 {
let s = remove_space(&english_number(n));
sum += s.len();
// println!("{}: {} {}", n, english_number(n), s.len());
}
println!("{}", sum);

第18题

问题描述:

从堆成三角的数字中,找到一条路径,使其和最大,求和。一个节点的下一个点只能是下一层的左节点或右节点。

为了节省内存空间,用一维数组表示这些数,需要准确地计算出各个索引位置的行号,为了方便地计算出左、右子节点,最上一层的行号为1。

let w = [                                75, // row 1                              95, 64,  // row 2                            17, 47, 82,  // row 3                          18, 35, 87, 10,                        20, 04, 82, 47, 65,                      19, 01, 23, 75, 03, 34,                    88, 02, 77, 73, 07, 63, 67,                  99, 65, 04, 28, 06, 16, 70, 92,                41, 41, 26, 56, 83, 40, 80, 70, 33,              41, 48, 72, 33, 47, 32, 37, 16, 94, 29,            53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14,          70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57,        91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48,      63, 66, 04, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31,    04, 62, 98, 27, 23, 09, 70, 98, 73, 93, 38, 53, 60, 04, 23,];

在数组中的第n个数,行号是多少?利用了第r行一定有r个数的性质。

fn row(n: usize) -> usize {
let mut s = 0;
for r in 1.. {
s += r;
if s > n {return r;}
}
return 0;
}

根据上面行号的性质,可以得出节点n的下一层的左节点的编号是 n + r,右节点是 n + r + 1。

求路径的和的时候可以利用递归,终止条件是遇到最底一层的时候,由于原题只让求路径长度,这里没有记下来所走路径的编号。

fn max_path_weight(w: &[u32], n: usize) -> u32 {
if n >= w.len() {return 0;} //越界判断
let r = row(n);
let bottom_row = row(w.len() - 1);
if r == bottom_row { // 递归的退出条件
return w[n];
}
let left = max_path_weight(w, n + r);
let right = max_path_weight(w, n + r + 1);
let max = if left > right {left} else {right};
return w[n] + max;
}

主程序调用只需要一行,数组的总层数不多,复杂度不高,没再做进一步的性能优化。

println!("{}", max_path_weight(&w, 0));

第19题

问题描述:

求20世纪(1901年1月1日到2000年12月31日)有多少个月的一号是星期日?

本题当然可以利用闰年的性质,只用数学公式就能算出来,这里用编程办法,熟悉一下Rust中如何处理日期和时间。

关于日期的库用chrono,网上有些资料比较老,建议直接参考官网上的帮助,写得非常详细,少走一些弯路。

在https://docs.rs 网站上搜索chrono即可。

use chrono::prelude::*;
use time::Duration;

代码简单粗暴,每次加一天,用到Duration;判断星期几用到weekday(),判断几号用了day(),逻辑很简单。

let mut count = 0;
let mut d = Utc.ymd(1901, 1, 1);
while d <= Utc.ymd(2000, 12, 31) {
if d.weekday() == Weekday::Sun && d.day() == 1 {
println!("{}", d);
count += 1;
}
d = d + Duration::days(1);
}
println!("{}", count);

第20题

问题描述:

求100的阶乘中所有数字之和。

本题与第16题非常相似,稍微修改就出来,不解释。

let mut prod = BigUint::from(1 as u64);
for i in 1






请到「今天看啥」查看全文