經過歐拉計劃學Rust(第1~6題)

最近想學習Libra數字貨幣的MOVE語言,發現它是用Rust編寫的,看來想準確理解MOVE的機制,還須要對Rust有深入的理解,因此開始了Rust的快速入門學習。算法

看了一下網上有關Rust的介紹,都說它的學習曲線至關陡峭,曾一度被其嚇着,後來發現Rust借鑑了Haskell等函數式編程語言的優勢,而我之前專門學習過Haskell,通過一段時間的入門學習,我如今已經喜歡上這門神奇的語言。編程

入門資料我用官方的《The Rust Programming Language》,很是權威,配合着《Rust by example》這本書一塊兒學習,效果很是不錯。數組

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

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

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

環境準備

在Windows下安裝,用官網上的rustup直接默認安裝便可。函數

安裝完成以後,就有了《The Rust Programming Language》這本書的離線HTML版本,直接用命令打開:oop

rustup doc --book

還要會使用強大的包管理器:cargo學習

這個cargo好用的另人髮指,建項目、編譯、運行都用用它:優化

cargo new euler1
cd euler1
cargo build
cargo run

第一題

問題描述:

1000之內(不含1000)的全部被3或5整除的整數之和。

直接上答案:

let mut sum = 0;
for i in 1..1000 {
    if i % 3 == 0 || i % 5 == 0 {
        sum += i;
    }
}
println!("{}", sum);

mut關鍵字(mutable的縮寫)是Rust的一大特點,全部變量默認爲不可變的,若是想可變,須要mut關鍵字,不然在 sum += i 時會報編譯錯誤。

println! 後面有一個歎號,表示這是一個宏,Rust裏的宏也是很是很是強大!如今還不到了解的時候。

學過Python的列表推導(List Comprehension)語法的感受這種題徹底能夠用一行語句搞定,Rust中須要用到filter()和sum()函數。

// 爲了閱讀,分紅多行
println!(
    "{}",
    (1..1000).filter(|x| x % 3 == 0 || x % 5 == 0)
             .sum::<u32>() 
);

.. 這個語法糖表示一個範圍,須要注意最後不包括1000,若是想包含1000,須要這樣寫:(1..=1000)

filter裏面的|x|定義了一個閉包函數,關於閉包,又是一個複雜的主題。

sum:: () 是一個範型函數,這種兩個冒號的語法讓我好不適應。

還能夠用fold()函數,是這樣寫的:

println!(
    "{}",
    (1..1000)
        .filter(|x| x % 3 == 0 || x % 5 == 0)
        .fold(0, |s, a| s + a)
);

想把這些數所有打印出來:

println!(
    "{:?}",
    (1..1000)
        .filter(|x| x % 3 == 0 || x % 5 == 0)
        .collect::<Vec<u32>>()
);
// [3, 5, 6, 9, 10, 12, ... 999]

第二題

問題描述:

400萬以內全部偶數的斐波那契數字之和。

算法並不難,這裏的數列以[1, 2]開始,後面每一個數是前面2個數字之和:

let mut fib = vec![1, 2];

let mut i = 2; // 已經有2個元素
let mut sum = 2; 
loop {
    let c = fib[i - 1] + fib[i - 2];
    if c >= 4_000_000 {
        break;
    }
    fib.push(c); 
    if c % 2 == 0 {
        sum += c;
    }
    i += 1;
}
println!("{}", sum);

這裏沒有使用函數式編程,大量使用了mut,無限循環用loop語法。

rust中關於整數的表示提供了多種數據類型,默認的整數類型是i32,默認浮點類型是f64。

數字類型中比較有特色的是能夠用'_'分隔符,讓數字更容易讀一些,還能夠把u32, i64等類型做爲後綴來指明類型。

let 賦值語句與其它語言也不同,還能夠改變其類型,這個特性爲隱藏shadowing。

let x = 500u16;
let x = x + 1;
let x = 4_000_000_u64;
let x = "slb";

fib是一個向量,至關於其它語言裏的數組、列表。vec! 宏能夠進行初始化任務。
這一行:

let mut fib = vec![1, 2];

與下面三行等價:

let mut fib = Vec::new();
fib.push(1);
fib.push(2);

push()函數用於給列表增長一個元素。

還能夠改進,利用rust的延遲評價特性,有起始值無終止值的無限序列能夠用for語句搞定,原來的代碼能夠再精練一些,這種「2..」的語法在其它語言是沒法想像的。

let mut fib = vec![1, 2];

let mut sum = 2;
for i in 2.. {
    let c = fib[i - 1] + fib[i - 2];
    if c >= 4_000_000 {
        break;
    }
    fib.push(c);
    if c % 2 == 0 {
        sum += c;
    }
}
println!("{}", sum);

若是再使用函數式編程,還能夠更精練一點:

let mut fib = vec![1, 2];
for i in 2.. {
    let c = fib[i - 1] + fib[i - 2];
    if c >= 4_000_000 { break; }
    fib.push(c);
}
println!("{}", fib.iter().filter(|&x| x % 2 == 0).sum::<u32>());

第三題

問題描述:
找出整數600851475143的最大素數因子。

素數就是隻能被1和自己整除的數,首先定義一個函數is_prime(),用於判斷是否爲素數:

fn is_prime(num: u64) -> bool {
    for i in 2..(num / 2 + 1) {
        if num % i == 0 {
            return false;
        }
    }
    true
}

Rust是強類型語言,看到函數定義裏的 -> bool,讓我想起了Haskell的語法。

函數最後一行的true孤零零的,沒有分號,讓人感受很奇怪。Rust是一個基於表達式的語言,一個語句塊的最後是一個表達式,固然也能夠用return true;

如今能夠查找最大的素數因子了:

let big_num = 600851475143;
for i in (2..=big_num).rev() {
    if big_num % i == 0 && is_prime(i) {
        println!("{}", i);
        break;
    }
}

程序編譯沒問題,但幾分鐘也運行不出來結果,試着把數字調小一點,好比:600851,不到1秒出來結果,看來程序的效率太差了,主要是須要大量的判斷素數的運算量,須要優化。

嘗試把大數進行素數因子分解,而且把素因子記錄下來進行比較,效率獲得大幅提高,不到1秒得出結果。

let mut big_num = 600851475143;
let mut max_prime_factor = 2;

while big_num >= 2 {
    for i in 2..=big_num {
        if big_num % i == 0 && is_prime(i) {
            big_num /= i;
            if i > max_prime_factor  {
                max_prime_factor = i;
                break;
            }
        }
    }
}
println!("{}", max_prime_factor);

第四題

問題描述:

求兩個3位數之積最大的迴文數。

所謂迴文數,就是兩邊讀都同樣的數,好比:698896。

先寫一個判斷迴文數的函數:

fn is_palindromic(n: u64) -> bool {
    let s = n.to_string();
    s.chars().rev().collect::<String>() == s
}

我把數字轉換成字符串,再把字符串反序,若是與原字符串同樣,則是迴文數。

Rust中字符串的反序操做好奇怪,居然不是s.rev(),我是google找到的那個代碼片斷。

剩下的邏輯並不複雜,用兩重循環能夠快速搞定。

let mut max = 0;
for x in 100..=999 {
    for y in 100..=999 {
        let prod = x * y;
        if is_palindromic(prod) && prod > max {
            max = prod;
            // println!("{} x {} = {}", x, y, prod);
        }
    }
}
println!("{}", max);

我一開始覺得只要反序搜索就能夠快速找到答案,但找到的數並非最大,你能發現問題之所在嗎?不過,從這個錯誤代碼中,我學會了雙重循環如何跳出外層循環的語法。真是沒有白走的彎路。

// 錯誤代碼
'outer: for x in (100..=999).rev() {
    for y in (100..=999).rev() {
        let prod = x * y;
        if is_palindromic(prod) {
            println!("{} x {} = {}", x, y, prod);
            break 'outer;
        }
    }
}

第五題

問題描述:

找出可以被1, 2, 3, ..., 20整除的最小整數。

代碼邏輯很簡單,一個一個嘗試整除,找到後跳出最外層循環。

let mut x = 2 * 3 * 5 * 7;
'outer: loop {
    for f in 2..=20 {
        if x % f != 0 {break;}
        if f == 20 {
            println!("{}", x);
            break 'outer;
        }
    }
    x += 2;
}

若是你感受程序運行效率不夠高,能夠用下面這個命令行運行,差異仍是很是大的,感受與C程序的效率相媲美:

cargo run --release

第六題

問題描述:
求1到100天然數的「和的平方」與「平方和」的差。

用普通的過程式編程方法,這題太簡單,但要嘗試一下函數式編程思路,代碼能夠異常簡潔。

let sum_of_squares = (1..=100).map(|x| x*x).sum::<u32>();
let sum = (1..=100).sum::<u32>();
println!("{}", sum * sum - sum_of_squares);

另外還有一種使用fold()函數的寫法,理解起來更困難一些:

let sum_of_squares = (1..=100).fold(0, |s, n| s + n * n);
let sum = (1..=100).fold(0, |s, n| s + n);
println!("{}", sum * sum - sum_of_squares);
相關文章
相關標籤/搜索