专栏名称: 申龙斌的程序人生
分享可繁殖的知识与技能:GTD时间管理、读书心得、个人成长、财富自由之路
目录
相关文章推荐
OSC开源社区  ·  OWL:Manus通用智能体的完全开源复刻、 ... ·  2 天前  
程序员的那些事  ·  刚刚!DeepSeek 杀入全球榜单第 2 ... ·  3 天前  
OSC开源社区  ·  听说技术大V们都被"manus"喂饱了,求邀 ... ·  4 天前  
OSC开源社区  ·  LFOSSA女神节福利,激励女性绽放多元力量 ... ·  4 天前  
51好读  ›  专栏  ›  申龙斌的程序人生

用欧拉计划学习Rust编程(第22~25题)

申龙斌的程序人生  · 公众号  · 程序员  · 2019-09-07 06:37

正文

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

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

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

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

第1~6题 第7~12题 第13~16题 第17~21题

第22题

问题描述:

从文件中读取一堆名字,按字母顺序排序,求名字分总和。名字分 = 顺序号 * 名字中几个字母的序号和。

例如:COLIN,所有字符在字母表中的序号之和,3 + 15 + 12 + 9 + 14 = 53,COLIN名字排在第938个,该名字的得分为938 × 53 = 49714。

问题分解:

1)读文件,移除引号

2)把名字存储在Vec向量中

3)排序

4)求字符在字母表中的序号

5)求单词的分数

6)求总分

正式开始:

1)首先把文件读到一个字符串中。

use std::fs;
fn main() { let data = fs::read_to_string("names.txt") .expect("读文件失败"); println!("{}", data);}

名字中都带着引号,需要移除,可以利用函数式编程,还有filter()和collect()函数,一气呵成。filter()函数中的*c又是让人容易出错的地方。

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

2)每个名字是用逗号分开的,所以可以用split()函数,分解成向量。

let data2 = remove_quote(&data);let names: Vec = data2.split(",").collect();println!("{:?}", names);

3)向量有专门的排序函数,需要将变量定义为可修改的。

let mut names: Vec = data2.split(",").collect();names.sort();

4)字符在字母表中的顺序号,可以求find(),也可以用position()函数。

fn letter_number(ch: char) -> usize {    let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";    letters.chars().position(|c| c == ch).unwrap() + 1}

5)求一个单词的分数

fn word_score(word: &str) -> usize {    let mut score = 0;    for ch in word.chars() {        score += letter_number(ch);    }    score




    
}

6)现在可以求总分了,有一个非常有用的for循环的用法,可以既得到元素,还可以得到元素的索引号,利用enumerate()函数。

let mut score = 0;for (i, name) in names.iter().enumerate() {    let ws = word_score(name);    println!("{} {} {}", (i+1), name, ws);    score += ws * (i + 1);}println!("{}", score);

完整的main()代码:

let data = std::fs::read_to_string("names.txt").expect("读文件失败");let data2 = remove_quote(&data);let mut names: Vec = data2.split(",").collect();
names.sort();
let mut score = 0;for (i, name) in names.iter().enumerate() { let ws = word_score(name); println!("{} {} {}", (i + 1), name, ws); score += ws * (i + 1);}println!("{}", score);

语法点:

1)std::fs读文件

2)字符串的split()函数

3)排序函数sort()

4)字符串中查找一个字符的位置

5)enumerate()迭代器,可以产生序号和元素

第23题

问题描述:

富裕数是指因子之和大于自身的数,例如12的所有因子和,1 + 2 + 3 + 4 + 6 = 16, 因为16 > 12,所以12是富裕数。数学上已经证明,超过28123的数都可以分解为2个富裕数之和。求所有不能分解为两个富裕数之和的正整数的总和。

求解过程:

1)求所有因子(不包含自身)

2)判断是否为富裕数

3)判断是否可以分解为2个富裕数之和

4)求解最后的问题

第一步求因子,在第21题中已经求过,但这里发现它的一个BUG,对于4, 9, 16, 25这样的完全平方数,因子会多出来一个。

修改之后是这样:

fn proper_divisors(num: u32) -> Vec {    let mut v = { // 求一半的因子        let s = (num as f32).sqrt() as u32;        (1..=s).filter(|x| num % x == 0).collect::>()    };    let last = v.last().unwrap();    if last * last == num {        // 16的一半因子为1,2,4,另外只差一个8,即16 / 2        for i in (1..v.len()-1).rev() {            v.push(num / v[i]);        }    }    else {        // 12的一半因子为1,2,3,另外一半因子:4,6,分别对应于12/3,12/2        for i in (1..v.len()).rev() {//不要num自身,所以从1开始            v.push(num / v[i]);        }    }    v}

第二步判断是否为富裕数,逻辑简单。

fn is_abundant_number(num: u32) -> bool {    let proper_divisors_sum = proper_divisors(num).iter().sum::();    proper_divisors_sum > num }

第三步,进行分解判断时,出于性能考虑,需要将富裕数的结果缓存在一个数组中。

let mut abundant_numbers = vec![false; 28124];for i in 2usize..abundant_numbers.len() {    if is_abundant_number(i as u32) {        abundant_numbers[i] = true;    }}

判断是否可以分解为2个富裕数,只需暴力循环。

fn can_divide(abundant_numbers: &[bool], num: u32) -> bool {    for x in 1..=28123 {        let y = num - x;        if y <= 0 {break;}         if abundant_numbers[x as usize] && abundant_numbers[y as usize] {            // println!("{} = {} + {}", num, x, y);            return true;        }    }    return false;}

第四步,把不可分解的数字求和。

let mut sum = 0;for i in 1..=28123 {    if !can_divide(&abundant_numbers, i) {        sum += i;    }}println!("sum: {}", sum);

当然这求和的几行语句,也可以用函数式编程把它浓缩在一行:

println!("sum: {}",     (1..=28123).filter(|&x| !can_divide(&abundant_numbers, x))               .sum::());

语法知识点:

数组作为函数参数的写法:&[bool]

第24题

问题描述:

0,1,2,3,4,5,6,7,8和9,每个数字用且只用一次,称为全排列,按数值大小排序,求第一百万个数是多少?

例如,0,1和2按从小到大只有6种排列:012,021,102,120,201,210。

解法:

这是一道排列组合类的数学题,在百度文库中有一个PPT介绍得不错,链接:https://wk.baidu.com/view/5f4bacf79e31433239689339?pcf=2&fromShare=1&fr=copy©fr=copylinkpop

这道题可以利用其中的字典序的算法:

实现这个算法不太麻烦,只是需要细心一些。

fn next_perm(v: &mut Vec) {    let mut i = v.len() - 2;    while v[i] > v[i + 1] {        i -= 1;    }
let mut j = v.len() - 1; while i < j && v[i] > v[j] { j -= 1; }
swap(v, i, j);
i += 1; j = v.len() - 1; while i < j { swap(v, i, j); i += 1; j -= 1; }}
fn swap(v: &mut Vec, i: usize, j: usize) { let temp = v[i]; v[i] = v[j]; v[j] = temp;






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