zoukankan      html  css  js  c++  java
  • 通过欧拉计划学习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&copyfr=copylinkpop

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

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

    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

    近期文章:

  • 相关阅读:
    TXSQL:云计算时代数据库核弹头——云+未来峰会开发者专场回顾
    游戏场景下的DDoS风险分析及防护
    一站式机器学习平台TI-ONE是什么?——云+未来峰会开发者专场回顾
    带有Apache Spark的Lambda架构
    攻克数据库核心技术壁垒,实现百万级QPS的高吞吐
    想学大数据?大数据处理的开源框架推荐
    可怕!数据库竟然打破安迪-比尔定律
    什么?云数据库也能C位出道?
    协同过滤的R语言实现及改进
    Android代码规范----按钮单击事件的四种写法
  • 原文地址:https://www.cnblogs.com/speeding/p/11449416.html
Copyright © 2011-2022 走看看