經過歐拉計劃學習Rust編程(第22~25題)

最近想學習Libra數字貨幣的MOVE語言,發現它是用Rust編寫的,因此先補一下Rust的基礎知識。學習了一段時間,發現Rust的學習曲線很是陡峭,不過仍有快速入門的辦法。算法

學習任何一項技能最怕沒有反饋,尤爲是學英語、學編程的時候,必定要「用」,學習編程時有一個很是有用的網站,它就是「歐拉計劃」,網址: https://projecteuler.net編程

這個網站提供了幾百道由易到難的數學問題,你能夠用任何辦法去解決它,固然主要還得靠編程,編程語言不限,論壇裏已經有Java、C#、Python、Lisp、Haskell等各類解法,固然若是你直接用google搜索答案就沒任何樂趣了。數組

學習Rust最好先把基本的語法和特性看過一遍,而後就能夠動手解題了,解題的過程就是學習、試錯、再學習、掌握和鞏固的過程,學習進度會大大加快。緩存

第22題

問題描述:編程語言

從文件中讀取一堆名字,按字母順序排序,求名字分總和。名字分 = 順序號 * 名字中幾個字母的序號和。ide

例如: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

近期文章:

相關文章
相關標籤/搜索