通过欧拉计划学习Rust编程(第22~25题)
最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,所以先补一下Rust的基础知识。学习了一段时间,发现Rust的学习曲线非常陡峭,不过仍有快速入门的办法。
学习任何一项技能最怕没有反馈,尤其是学英语、学编程的时候,一定要“用”,学习编程时有一个非常有用的网站,它就是“欧拉计划”,网址: https://projecteuler.net
这个网站提供了几百道由易到难的数学问题,你可以用任何办法去解决它,当然主要还得靠编程,编程语言不限,论坛里已经有Java、C#、Python、Lisp、Haskell等各种解法,当然如果你直接用google搜索答案就没任何乐趣了。
学习Rust最好先把基本的语法和特性看过一遍,然后就可以动手解题了,解题的过程就是学习、试错、再学习、掌握和巩固的过程,学习进度会大大加快。
第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<&str> = data2.split(",").collect();
println!("{:?}", names);
3)向量有专门的排序函数,需要将变量定义为可修改的。
let mut names: Vec<&str> = 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<&str> = 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<u32> {
let mut v = { // 求一半的因子
let s = (num as f32).sqrt() as u32;
(1..=s).filter(|x| num % x == 0).collect::<Vec<u32>>()
};
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::<u32>();
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::<u32>()
);
语法知识点:
- 数组作为函数参数的写法:&[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<u32>) {
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<u32>, i: usize, j: usize) {
let temp = v[i];
v[i] = v[j];
v[j] = temp;
}
主程序只需要循环就行了,向量的初始值就是第一个排列,从2开始找到第100万个排列数。
fn main() {
let mut v: Vec<u32> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for i in 2..=1_000_000 {
next_perm(&mut v);
//println!("{} {:?}", i, v);
}
println!("{:?}", v);
}
这里的v是向量表示,要转换成一个整数,可以这样:
let v_str = v.iter()
.map(|x| x.to_string())
.collect::<String>();
println!("{}", v_str.parse::<u64>().unwrap());
这里的组合数一直是10个数字,也可以换成定长数组的写法,
fn main() {
let mut v = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for i in 2..=1_000_000 {
next_perm(&mut v);
}
println!("{:?}", v);
}
fn next_perm(v: &mut [u32]) {
// 保持不变
}
fn swap(v: &mut [u32], i: usize, j: usize) {
let temp = v[i];
v[i] = v[j];
v[j] = temp;
}
语法知识点:
- 注意向量或数组传递到函数里的写法
第25题
问题描述:
在斐波那契数列中,第一个有1000位数字的是第几项?
本题与第16题非常相似,稍微修改就出来,不解释。
extern crate num_bigint;
use num_bigint::BigUint;
fn main() {
let mut prev = BigUint::from(1 as u64);
let mut cur = BigUint::from(1 as u64);
for i in 3.. {
let next = prev + &cur;
let str = next.to_string();
if str.len() >= 1000 {
println!("{} {} {}", i, str, str.len());
break;
}
prev = cur;
cur = next;
}
}
在projecteuler中注册一个账号,可以添加好友,一起讨论学习,我的Key是:
1539870_KBNiIXymh4SnmDEDZmUTg7tu1MTBVlLj
近期文章:
通过欧拉计划学习Rust编程(第22~25题)的更多相关文章
- 用欧拉计划学习Rust编程(第13~16题)
最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,所以先补一下Rust的基础知识.学习了一段时间,发现Rust的学习曲线非常陡峭,不过仍有快速入门的办法. 学习任何一项技能最怕没有 ...
- 通过欧拉计划学Rust编程(第54题)
由于研究Libra等数字货币编程技术的需要,学习了一段时间的Rust编程,一不小心刷题上瘾. 刷完欧拉计划中的63道基础题,能学会Rust编程吗? "欧拉计划"的网址: https ...
- 通过欧拉计划学Rust编程(第500题)
由于研究Libra等数字货币编程技术的需要,学习了一段时间的Rust编程,一不小心刷题上瘾. "欧拉计划"的网址: https://projecteuler.net 英文如果不过关 ...
- 用欧拉计划学Rust编程(第26题)
最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,所以先补一下Rust的基础知识.学习了一段时间,发现Rust的学习曲线非常陡峭,不过仍有快速入门的办法. 学习任何一项技能最怕没有 ...
- 通过欧拉计划学Rust(第1~6题)
最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,看来想准确理解MOVE的机制,还需要对Rust有深刻的理解,所以开始了Rust的快速入门学习. 看了一下网上有关Rust的介绍,都 ...
- 用欧拉计划学Rust语言(第17~21题)
最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,所以先补一下Rust的基础知识.学习了一段时间,发现Rust的学习曲线非常陡峭,不过仍有快速入门的办法. 学习任何一项技能最怕没有 ...
- 用欧拉计划学Rust语言(第7~12题)
最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,所以先补一下Rust的基础知识.学习了一段时间,发现Rust的学习曲线非常陡峭,不过仍有快速入门的办法. 学习任何一项技能最怕没有 ...
- 刷完欧拉计划中难度系数为5%的所有63道题,我学会了Rust中的哪些知识点?
我为什么学Rust? 2019年6月18日,Facebook发布了数字货币Libra的技术白皮书,我也第一时间体验了一下它的智能合约编程语言MOVE,发现这个MOVE是用Rust编写的,看来想准确理解 ...
- 【欧拉计划4】Largest palindrome product
欢迎访问我的新博客:http://www.milkcu.com/blog/ 原文地址:http://www.milkcu.com/blog/archives/1371281760.html 原创:[欧 ...
随机推荐
- Centos安装jdk1.8出现-bash: //usr/local/soft/jdk1.8.0_191/bin/javac: /lib/ld-linux.so.2: bad ELF interpreter: 没有那个文件或目录错误。
1.从来没有这么郁闷,之前安装都是好好的,自从将Centos升级到7.0版本,安装了jdk报了这个错误,也是郁闷的一毛,参考了一下百度的,记录一下.使用java命令还有java -version命令都 ...
- 2019-11-25-win10-uwp-通过命令行脚本开启旁加载
原文:2019-11-25-win10-uwp-通过命令行脚本开启旁加载 title author date CreateTime categories win10 uwp 通过命令行脚本开启旁加载 ...
- Python - 元组 - 第九天
Python 元组 Python 的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可.例如: >&g ...
- Response.Write的alert换行问题
Response.Write("<script> alert('恭喜 clientuser1注册成功!!!\\r\\n正在跳转到登录界面......');window.locat ...
- Maven环境搭配及继承
1. Maven简单介绍 Apache Maven是个项目管理和自动构建工具,基于项目对象模型(POM)的概念. 作用:完成项目的相关操作,如:编译,构建,单元测试,安装,网站生成和基于Maven部署 ...
- scrapy学习笔记(一)
环境:Windows 7 x64 Python3.7.1 pycharm 一.安装scrapy 1.1linux系统使用:pip install scrapy 1.2Windows系统: pi ...
- python 排序和查找算法
一.搜索 1.顺序查找 数据存储在具有线性或顺序关系的结构中时,可顺序访问查找 def sequential_search(ilist, item): pos = 0 while pos < l ...
- Kali无法使用Chrome原因及解决方法
Kali安装好后,默认的浏览器是Firefox-ESR(Extended Support Release 长期支持)版本. 作为Chrome的死忠粉,当然是要下Chrome用用的. 直到我 ...
- LeetCode学习_day1:原地算法
原地算法:是一种使用小的,固定数量的额外之空间来转换资料的算法.当算法执行时,输入的资料通常会被要输出的部份覆盖掉. 范例:冒泡排序.选择排序.插入排序.希尔排序 (1)冒泡排序: 冒泡排序算法的原理 ...
- 【DB_MySQL】MySQL日志分析
MySQL数据库常见的日志有:错误日志(log_error).慢查询日志(slow_query_log).二进制日志(bin_log).通用日志(general_log) 开启慢查询日志并分析 开启慢 ...