最近想学习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)、字符常量,需要看基础教程慢慢理解。

  1. fn english_number(n: usize) -> String {
  2. let list0_9 = vec![
  3. "zero", "one", "two", "three", "four",
  4. "five", "six", "seven", "eight", "nine",
  5. ];
  6. if n <= 9 {
  7. return list0_9[n].to_string();
  8. }
  9. if n <= 19 {
  10. let list = vec![
  11. "ten", "eleven", "twelve", "thirteen", "fourteen",
  12. "fifteen", "sixteen", "seventeen", "eighteen", "nineteen",
  13. ];
  14. return list[n - 10].to_string();
  15. }
  16. if n <= 99 {
  17. let a: usize = n / 10; // 十位
  18. let b: usize = n % 10;
  19. let list = vec![
  20. "", "", "twenty", "thirty", "forty",
  21. "fifty", "sixty", "seventy", "eighty", "ninety"
  22. ];
  23. let str = list[a].to_string();
  24. if b > 0 {
  25. return str + "-" + &english_number(b);
  26. }
  27. return str;
  28. }
  29. if n <= 999 {
  30. let a: usize = n / 100; // 百位
  31. let b: usize = n % 100;
  32. let str = list0_9[a].to_string() + " hundred";
  33. if b > 0 {
  34. return str + " and " + &english_number(b);
  35. }
  36. return str;
  37. }
  38. if n == 1000 {
  39. return "one thousand".to_string();
  40. }
  41. return "unknown".to_string();
  42. }

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

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

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

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

第18题

问题描述:

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

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

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

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

  1. fn row(n: usize) -> usize {
  2. let mut s = 0;
  3. for r in 1.. {
  4. s += r;
  5. if s > n {return r;}
  6. }
  7. return 0;
  8. }

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

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

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

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

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

第19题

问题描述:

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

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

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

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

  1. use chrono::prelude::*;
  2. use time::Duration;

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

  1. let mut count = 0;
  2. let mut d = Utc.ymd(1901, 1, 1);
  3. while d <= Utc.ymd(2000, 12, 31) {
  4. if d.weekday() == Weekday::Sun && d.day() == 1 {
  5. println!("{}", d);
  6. count += 1;
  7. }
  8. d = d + Duration::days(1);
  9. }
  10. println!("{}", count);

第20题

问题描述:

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

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

  1. let mut prod = BigUint::from(1 as u64);
  2. for i in 1..=100 {
  3. prod *= BigUint::from(i as u64);
  4. }
  5. let full_str = prod.to_str_radix(10);
  6. let s = full_str
  7. .chars()
  8. .map(|c| c.to_digit(10).unwrap())
  9. .sum::<u32>();
  10. println!("{}", s);

第21题

问题描述:

10000之内的所有亲和数之和。所谓亲和数,是指两个正整数中,彼此的全部约数之和(本身除外)与另一方相等。比如,220的因子有1, 2, 4, 5, 10, 11, 20, 22, 44, 55 和 110,因子之和是 284,而284的所有因子是 1, 2, 4, 71 和 142,因子之和是220。

问题分解:

  • 求所有因子
  • 因子求和
  • 找出亲和数
  • 亲和数再求和

在第12题里已经求出了一半的因子,函数是:

  1. fn half_factors(num: u32) -> Vec<u32> {
  2. let s = (num as f32).sqrt() as u32;
  3. (1..=s).filter(|x| num % x == 0).collect::<Vec<u32>>()
  4. }

很容易补上后面的一半因子。

  1. fn proper_divisors(num: u32) -> Vec<u32> {
  2. let mut v = half_factors(num);
  3. for i in (1..v.len()).rev() { //不要num自身,所以从1开始
  4. v.push(num / v[i]);
  5. }
  6. v
  7. }

所有因子求和:

  1. fn proper_divisors_sum(num: u32) -> u32 {
  2. let divs = proper_divisors(num);
  3. divs.iter().sum::<u32>()
  4. }

主程序暴力循环即可,10000之内只有5对亲和数:

  1. let mut sum = 0;
  2. for a in 1u32..10000 {
  3. let b = proper_divisors_sum(a);
  4. if a != b && proper_divisors_sum(b) == a {
  5. sum += a;
  6. println!("{} {}", a, b);
  7. }
  8. }
  9. println!("{}", sum);

因为亲和数是成对出现的,还可以优化性能,引入一个数组,这里不再展开了。


在projecteuler中注册一个账号,可以添加好友,一起讨论学习,我的Key是:

1539870_KBNiIXymh4SnmDEDZmUTg7tu1MTBVlLj

近期文章:

用欧拉计划学Rust语言(第17~21题)的更多相关文章

  1. 通过欧拉计划学Rust编程(第54题)

    由于研究Libra等数字货币编程技术的需要,学习了一段时间的Rust编程,一不小心刷题上瘾. 刷完欧拉计划中的63道基础题,能学会Rust编程吗? "欧拉计划"的网址: https ...

  2. 通过欧拉计划学Rust编程(第500题)

    由于研究Libra等数字货币编程技术的需要,学习了一段时间的Rust编程,一不小心刷题上瘾. "欧拉计划"的网址: https://projecteuler.net 英文如果不过关 ...

  3. 用欧拉计划学Rust语言(第7~12题)

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

  4. 用欧拉计划学Rust编程(第26题)

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

  5. 通过欧拉计划学Rust(第1~6题)

    最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,看来想准确理解MOVE的机制,还需要对Rust有深刻的理解,所以开始了Rust的快速入门学习. 看了一下网上有关Rust的介绍,都 ...

  6. 用欧拉计划学习Rust编程(第13~16题)

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

  7. 通过欧拉计划学习Rust编程(第22~25题)

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

  8. 刷完欧拉计划中难度系数为5%的所有63道题,我学会了Rust中的哪些知识点?

    我为什么学Rust? 2019年6月18日,Facebook发布了数字货币Libra的技术白皮书,我也第一时间体验了一下它的智能合约编程语言MOVE,发现这个MOVE是用Rust编写的,看来想准确理解 ...

  9. 【欧拉计划4】Largest palindrome product

    欢迎访问我的新博客:http://www.milkcu.com/blog/ 原文地址:http://www.milkcu.com/blog/archives/1371281760.html 原创:[欧 ...

随机推荐

  1. 【UOJ#389】【UNR#3】白鸽(欧拉回路,费用流)

    [UOJ#389][UNR#3]白鸽(欧拉回路,费用流) 题面 UOJ 题解 首先第一问就是判断是否存在一条合法的欧拉回路,这个拿度数和连通性判断一下就行了. 第二问判断转的圈数,显然我们只需要考虑顺 ...

  2. 浅析java线程和OS线程的关系

    探究java线程和OS线程之间的联系 一.准备工作 1.查看linux创建线程的方法    man pthread_create 根据man的配置可知,pthread_create会创建一个线程,这个 ...

  3. .Net 高级 模拟事件模型

    第一步:创建一个类,并继承:IHttpModule using System; using System.Collections.Generic; using System.Linq; using S ...

  4. ORM:对象关系映射

    一.简单操作 定义:面向对象和关系型数据库的一种映射,通过操作对象的方式操作数据 对应关系: 类对应数据表 对象对应数据行(记录) 属性对应字段 导入:from app01 import models ...

  5. 【初识Spring】对象(Bean)实例化及属性注入(注解方式)

    通过xml的方式进行对象的实列化或属性注入或许有一些繁琐,所以在开发中常用的方式更多是通过注解的方式实现对象实例化和属性注入的. 开始之前 1.导入相关的包(除了导入基本的包还要导入aop的包): 创 ...

  6. 【原创】Centos 7 升级安装python3.7.4

    1.安装必须的软件 #更新源中包列表 yum -y update #先安装扩展源EPEL 才能安装pip 否则会报错 yum -y install epel-release //解决ssl问题,否则报 ...

  7. day6_面向对象的概念

    #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019/7/11 16:20 # @Author : 大坏男孩 # @File : d ...

  8. maxima已知方程,计算结果

  9. luoguP3704 [SDOI2017]数字表格

    题意 默认\(n\leqslant m\) 所求即为:\(\prod\limits_{i=1}^n\prod\limits_{j=1}^mf[\gcd(i,j)]\) 枚举\(\gcd(i,j)\)变 ...

  10. web框架--tornado之cookie与session初识

    cookie的本质其实就是在浏览器端保存的键值对, 每当浏览器端发送一次请求, 都会将这些键值对附加在请求中并发送给服务器端. 一.目录结构 二.main_pro.py #!/usr/bin/env ...