用歐拉計劃學習Rust編程(第32~34題)
- 2019 年 10 月 6 日
- 筆記

最近想學習Libra數字貨幣的MOVE語言,發現它是用Rust編寫的,所以先補一下Rust的基礎知識。學習了一段時間,發現Rust的學習曲線非常陡峭,不過仍有快速入門的辦法。
學習任何一項技能最怕沒有回饋,尤其是學英語、學編程的時候,一定要「用」,學習編程時有一個非常有用的網站,它就是「歐拉計劃」,網址:https://projecteuler.net
英文如果不過關,可以到中文翻譯的網站:http://pe-cn.github.io/
這個網站提供了幾百道由易到難的數學問題,你可以用任何辦法去解決它,當然主要還得靠編程,程式語言不限,論壇里已經有Java、C#、Python、Lisp、Haskell等各種解法,當然如果你直接用google搜索答案就沒任何樂趣了。
學習Rust最好先把基本的語法和特性看過一遍,然後就可以動手解題了,解題的過程就是學習、試錯、再學習、掌握和鞏固的過程,學習進度會大大加快。
全數字的乘積
問題描述:
如果一個n位數包含了1至n的所有數字恰好一次,我們稱它為全數字的;例如,五位數15234就是1至5全數字的。
7254是一個特殊的乘積,因為在等式39 × 186 = 7254 中,被乘數、乘數和乘積恰好是1至9全數字的。
找出所有被乘數、乘數和乘積恰好是1至9全數字的乘法等式,並求出這些等式中乘積的和。
注意:有些乘積可能從多個乘法等式中得到,但在求和的時候只計算一次。
解題思路:
1)判斷一個字元串中只能出現一次的1到9
2)循環嘗試,記錄每一個滿足要求的乘積
3)求和
第一步,先寫一個判斷字元串里只能出現一次1到9的函數。
n exists_only_once_1_to_9(s: &str) -> bool { let mut has_digit = vec![false; 10]; for ch in s.to_string().chars() { let c = ch.to_digit(10).unwrap() as usize; if c == 0 { // 不允許0的存在 return false; } if has_digit[c] { return false; } has_digit[c] = true; } true }
第二步和第三步,比較簡單,循環的終止條件要考慮一下,被乘數和乘數都不能超過4位數,所以只需要試驗到9876。
let mut v: Vec<u32> = vec![]; for a in 2..9876 { for b in 2..a { let c = a * b; let abc = a.to_string() + &b.to_string() + &c.to_string(); if abc.len() == 9 && exists_only_once_1_to_9(&abc) { println!("{} x {} = {}", a, b, c); if !v.contains(&c) { v.push(c); } } } } println!("{}", v.iter().sum::<u32>());
程式到這裡已經可以跑起來了,但運行起來較慢,發現少了一條重要的優化語句,乘積在大於9876時,後面的數都不用試了。
在循環體加一條判斷語句,程式在1秒之內運行完成。
if c > 9876 {break;}
第33題 消去數字的分數
問題描述:
49/98是一個有趣的分數,因為缺乏經驗的數學家可能在約簡時錯誤地認為,等式49/98 = 4/8之所以成立,是因為在分數線上下同時抹除了9的緣故。
我們也會想到,存在諸如30/50 = 3/5這樣的平凡解。
這類有趣的分數恰好有四個非平凡的例子,它們的分數值小於1,且分子和分母都是兩位數。
將這四個分數的乘積寫成最簡分數,求此時分母的值。
求解思路:
四個分數很容易求出,我這裡沒有進行通分運算,後面手算即可。
for ab in 10..=99 { let a = ab / 10; let b = ab % 10; for cd in (ab+1)..=99 { let c = cd / 10; let d = cd % 10; if b == c && ab * d == cd * a { println!("{} / {} = {} / {}", ab, cd, a, d); } } }
第34題 各位數字的階乘
問題描述
145是個有趣的數,因為1! + 4! + 5! = 1 + 24 + 120 = 145。
找出所有各位數字的階乘和等於其本身的數,並求它們的和。
注意:因為1! = 1和2! = 2不是和的形式,所以它們並不在討論範圍內。
解題思路
1)求階乘
2)找出一個數的各位數字
3)循環求解
第一步,階乘可以用遞歸實現。
fn factorial(n: u32) -> u32 { if n <= 1 { return 1; } return n * factorial(n - 1); }
1到9的階乘需要經常用到,用一個數組快取起來。
// [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] let mut fac = vec![1; 10]; for i in 0..=9 { fac[i] = factorial(i as u32); }
上面幾行,等價於這樣一行:
let fac: Vec<u32> = (0..10).map(|x| factorial(x)).collect();
後面的邏輯比較簡單,我只搜索到了999999,後面好像不存在滿足條件的更大的解。
let mut sum = 0; for n in 3..999999 { // 各位數字的階乘之和 let mut sum_fac = 0; for digit in n.to_string().chars().map(|x| x.to_digit(10).unwrap()) { sum_fac += fac[digit as usize]; } if n == sum_fac { println!("{}! = {}", n, n); sum += n; } } println!("{}", sum);
求sum_fac那幾行還可以這樣寫:
let sum_fac: u32 = n .to_string() .chars() .map(|x| fac[x.to_digit(10).unwrap() as usize]) .sum();
知識點:
學會使用map()函數
// 0-9的階乘 let fac: Vec<u32> = (0..10).map(|x| factorial(x)).collect(); // 取各個數字 n.to_string().chars().map(|x| x.to_digit(10).unwrap())
在projecteuler中註冊一個帳號,可以添加好友,一起討論學習,我的Key是:
1539870_KBNiIXymh4SnmDEDZmUTg7tu1MTBVlLj
最新的源程式碼同步更新在github上。
https://github.com/slofslb/rust-project-euler