用歐拉計劃學Rust語言(第7~12題)

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

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

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

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

前六題的學習過程見這篇文章編程語言

第7題

問題描述:函數式編程

求第10001個素數。函數

按一般的逐個試餘法,效率極差,須要用著名的篩子求素數算法,請自行百度。從網上找來其它語言的源代碼,稍作修改便可。學習

let max_number_to_check = 1_000_000;

let mut prime_mask = vec![true; max_number_to_check];
prime_mask[0] = false;
prime_mask[1] = false;

let mut total_primes_found = 0;

const FIRST_PRIME_NUMBER: usize = 2;
for p in FIRST_PRIME_NUMBER..max_number_to_check {
    if prime_mask[p] {
        // println!("{}", p);
        total_primes_found += 1;
        if total_primes_found == 10001 {
            println!("the 10001st prime number is : {}", p);
            break;
        }
        let mut i = 2 * p;
        while i < max_number_to_check {
            prime_mask[i] = false;
            i += p;
        }
    }
}

篩子算法須要提早分配內存空間,因此指定一個足夠大的搜索範圍 max_number_to_check,還須要一個數組 prime_mask 存放素數的標識位,另外還用 total_primes_found 對找到的素數進行計數。優化

這裏有一個常量聲明的語法點:網站

const FIRST_PRIME_NUMBER : usize = 2;

第8題

問題描述:

在1000位的大整數裏找到相鄰的13個數字,使其乘積最大。

首先系統內建的u32, u64或u128整數確定沒法保存1000位的整數,咱們用字符串來表示這個大整數,爲了讓代碼好看些,用數組表示,並用concat()函數合併。

let digits = vec![
    "73167176531330624919225119674426574742355349194934",
    "96983520312774506326239578318016984801869478851843",
    "85861560789112949495459501737958331952853208805511",
    "12540698747158523863050715693290963295227443043557",
    "66896648950445244523161731856403098711121722383113",
    "62229893423380308135336276614282806444486645238749",
    "30358907296290491560440772390713810515859307960866",
    "70172427121883998797908792274921901699720888093776",
    "65727333001053367881220235421809751254540594752243",
    "52584907711670556013604839586446706324415722155397",
    "53697817977846174064955149290862569321978468622482",
    "83972241375657056057490261407972968652414535100474",
    "82166370484403199890008895243450658541227588666881",
    "16427171479924442928230863465674813919123162824586",
    "17866458359124566529476545682848912883142607690042",
    "24219022671055626321111109370544217506941658960408",
    "07198403850962455444362981230987879927244284909188",
    "84580156166097919133875499200524063689912560717606",
    "05886116467109405077541002256983155200055935729725",
    "71636269561882670428252483600823257530420752963450",
].concat();

找到相鄰的13個數字,須要用到字符串的切片(slice)功能,好比找到從i開始的13個字符造成了一個子串。這裏面的「&」符號是容易出錯的地方,digits變量有全部權,若是被借用後,就不能再被使用,熟悉C++的朋友,能夠把「&」理解爲引用,這樣不破壞原來的全部權。

let x = &digits[i .. i + 13];

如今須要用到函數式編程的思路,將13個字符分離出來,並轉換成數字,再相乘起來,用到chars(), map(), to_digit(), unwrap(), fold()等一連串的函數,請自行體會。

x.chars()
 .map(|c| c.to_digit(10).unwrap())
 .fold(1u64, |p, a| p * a as u64);

to_digit(10) 可用於將字符轉換爲數字,例如'9'轉換爲9,須要注意這裏的轉換有可能出現異常,而rust處理異常的方式很特別,要重點學習 Option 的用法。

用unwrap()函數能夠將Option 類型轉換成u64類型。

最後是這樣:

const ADJACENT_NUMBERS: usize = 13;

let mut max = 0;
for i in 0..digits.len() - ADJACENT_NUMBERS {
    let x = &digits[i..i + ADJACENT_NUMBERS];
    let prod = x
        .chars()
        .map(|c| c.to_digit(10).unwrap())
        .fold(1u64, |p, a| p * a as u64);
    if prod > max {
        println!("index: {}   x: {}   prod: {}", i, x, prod);
        max = prod;
    }
}

第9題

問題描述:

找到和爲1000的勾股數,並求積。

簡單粗暴地遍歷求解便可。

for a in 1..1000 {
    for b in a..1000 {
        let c = 1000 - a - b;
        if c > 0 && a*a + b*b == c*c {
            println!("{} = {} x {} x {}", a*b*c, a, b, c);
            return;
        }
    }
}

第10題

問題描述:

求小於2百萬的全部素數之和

在第7題的基礎上稍作修改便可,爲防止溢出,須要用u64保存累計值sum。

let max_number_to_check = 2_000_000;

let mut prime_mask = vec![true; max_number_to_check];
prime_mask[0] = false;
prime_mask[1] = false;

let mut sum: u64 = 0;

const FIRST_PRIME_NUMBER: usize = 2;
for p in FIRST_PRIME_NUMBER..max_number_to_check {
    if prime_mask[p] {
        sum += p as u64;
        let mut i = 2 * p;
        while i < max_number_to_check {
            prime_mask[i] = false;
            i += p;
        }
    }
}
println!("{}", sum);

第11題

問題描述:

在一個矩陣裏,找到一條線上、相鄰的、乘積最大的4個數,求積。

先把數值用二維數組表示。

let arr = [ [08,02,22,97,38,15,00,40,00,75,04,05,07,78,52,12,50,77,91,08],
            [49,49,99,40,17,81,18,57,60,87,17,40,98,43,69,48,04,56,62,00],
            [81,49,31,73,55,79,14,29,93,71,40,67,53,88,30,03,49,13,36,65],
            [52,70,95,23,04,60,11,42,69,24,68,56,01,32,56,71,37,02,36,91],
            [22,31,16,71,51,67,63,89,41,92,36,54,22,40,40,28,66,33,13,80],
            [24,47,32,60,99,03,45,02,44,75,33,53,78,36,84,20,35,17,12,50],
            [32,98,81,28,64,23,67,10,26,38,40,67,59,54,70,66,18,38,64,70],
            [67,26,20,68,02,62,12,20,95,63,94,39,63,08,40,91,66,49,94,21],
            [24,55,58,05,66,73,99,26,97,17,78,78,96,83,14,88,34,89,63,72],
            [21,36,23,09,75,00,76,44,20,45,35,14,00,61,33,97,34,31,33,95],
            [78,17,53,28,22,75,31,67,15,94,03,80,04,62,16,14,09,53,56,92],
            [16,39,05,42,96,35,31,47,55,58,88,24,00,17,54,24,36,29,85,57],
            [86,56,00,48,35,71,89,07,05,44,44,37,44,60,21,58,51,54,17,58],
            [19,80,81,68,05,94,47,69,28,73,92,13,86,52,17,77,04,89,55,40],
            [04,52,08,83,97,35,99,16,07,97,57,32,16,26,26,79,33,27,98,66],
            [88,36,68,87,57,62,20,72,03,46,33,67,46,55,12,32,63,93,53,69],
            [04,42,16,73,38,25,39,11,24,94,72,18,08,46,29,32,40,62,76,36],
            [20,69,36,41,72,30,23,88,34,62,99,69,82,67,59,85,74,04,36,16],
            [20,73,35,29,78,31,90,01,74,31,49,71,48,86,81,16,23,57,05,54],
            [01,70,54,71,83,51,54,69,16,92,33,48,61,43,52,01,89,19,67,48]
        ];

先不考慮代碼的囉嗦和美觀性,4個方向都比較一遍,找出最大的便可。

let mut max = 0;
for i in 0..20 {
    for j in 0..20 {
        if i+4<=20 {
            let p = arr[i][j] * arr[i+1][j] * arr[i+2][j] * arr[i+3][j];
            if p > max {
                max = p;
                println!("下 {} {} {}",i,j, max);
            }
        }
        if j<=20-4 {
            let p = arr[i][j] * arr[i][j+1] * arr[i][j+2] * arr[i][j+3];
            if p > max {
                max = p;
                println!("右 {} {} {}",i,j, max);
            }
        }
        if i<=20-4 && j<=20-4 {
            let p = arr[i][j] * arr[i+1][j+1] * arr[i+2][j+2] * arr[i+3][j+3];
            if p > max {
                max = p;
                println!("右下 {} {} {}",i,j, max);
            }
        }
        if i<=20-4 && j>=3 {
            let p = arr[i][j] * arr[i+1][j-1] * arr[i+2][j-2] * arr[i+3][j-3];
            if p > max {
                max = p;
                println!("左下 {} {} {}",i,j, max);
            }
        }
    }
}
println!("{}", max);

代碼裏存在大量的與max比較的重複代碼,能夠用k=0,1,2,3表明四個方向,多一個內層循環,讓代碼簡潔一點。

for k in 0..4 {
    let mut p = 0;
    if k == 0 && i+4<=20 {
        p = arr[i][j] * arr[i+1][j] * arr[i+2][j] * arr[i+3][j];
    }
    if k == 1 && j<=20-4 {
        p = arr[i][j] * arr[i][j+1] * arr[i][j+2] * arr[i][j+3];
    }
    if k == 2 && i<=20-4 && j<=20-4 {
        p = arr[i][j] * arr[i+1][j+1] * arr[i+2][j+2] * arr[i+3][j+3];
    }
    if k == 3 && i<=20-4 && j>=3 {
        p = arr[i][j] * arr[i+1][j-1] * arr[i+2][j-2] * arr[i+3][j-3];
    }

    if p > max {
        max = p;
        println!("{} {} {}",i,j, max);
    }
}

問題12

問題描述:

求有超過500個因子的三角數。

三角數就是從1一直累加以後的數,好比第7個三角數,就是 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28。

而28一共有6個因子:1,2,4,7,14,28

求全部因子,能夠試着取餘數就行。

fn factors(num :u32) -> Vec<u32> {
    (1..=num).filter(|x| num % x == 0).collect::<Vec<u32>>() 
}

而後暴力嘗試便可,但效率很是差,等10分鐘也算不出來。

fn main() {
    for i in 1.. { 
        let num = (1..=i).sum::<u32>();
        let f = factors(num);
        if f.len() > 500 {
            println!("i:{} num:{} len:{} {:?}", i, num, f.len(), f );
            println!("{}", num );
            break;
        }
    }    
}

能夠稍作優化,只嘗試一半的因子就行,能夠大幅提升速度,幾秒鐘能夠計算完成。

fn main() {
    for i in 2.. { // 12375
        let num = (1..=i).sum::<u32>();
        let f = half_factors(num);
        if f.len() * 2 > 500 {
            println!("i:{} num:{} len:{} half of factors:{:?}", i, num, 2 * f.len(), f );
            println!("{}", num );
            break;
        }
    }    
}

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

實際上還能夠利用因子的數學性質進一步優化,提升上千倍不止,這裏不展開討論了。


在projecteuler中註冊一個帳號,能夠添加好友,一些討論學習,個人Key是:

1539870_KBNiIXymh4SnmDEDZmUTg7tu1MTBVlLj

近期文章:

相關文章
相關標籤/搜索