用歐拉計劃學Rust編程(第61題)

因爲研究Libra等數字貨幣編程技術的須要,學習了一段時間的Rust編程,一不當心刷題上癮。我把解決63道問題的過程記錄了下來,寫成了一本《用歐拉計劃學 Rust 編程》PDF電子書,請隨意下載。git

連接:https://pan.baidu.com/s/1NRfTwAcUFH-QS8jMwo6pqwgithub

提取碼:qfha算法

「歐拉計劃」的網址:
https://projecteuler.net編程

英文若是不過關,能夠到中文翻譯的網站:
http://pe-cn.github.io/數組

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

此次解答的是第61題:閉包

https://projecteuler.net/problem=61編程語言

題目描述:函數

循環的多邊形數學習

三角形數、正方形數、五邊形數、六邊形數、七邊形數和八邊形數統稱爲多邊形數。它們分別由以下的公式給出:

三角形數 P3,n=n(n+1)/2 1, 3, 6, 10, 15, …
正方形數 P4,n=n2 1, 4, 9, 16, 25, …
五邊形數 P5,n=n(3n−1)/2 1, 5, 12, 22, 35, …
六邊形數 P6,n=n(2n−1) 1, 6, 15, 28, 45, …
七邊形數 P7,n=n(5n−3)/2 1, 7, 18, 34, 55, …
八邊形數 P8,n=n(3n−2) 1, 8, 21, 40, 65, …

由三個4位數812八、288二、8281構成的有序集有以下三個有趣的性質。

  • 這個集合是循環的,每一個數的後兩位是後一個數的前兩位(最後一個數的後兩位也是第一個數的前兩位)。
  • 每種多邊形數——三角形數(P3,127=8128)、正方形數(P4,91=8281)和五邊形數(P5,44=2882)——在其中各有一個表明。
  • 這是惟一一組知足上述性質的4位數有序集。

存在惟一一個包含六個4位數的有序循環集,每種多邊形數——三角形數、正方形數、五邊形數、六邊形數、七邊形數和八邊形數——在其中各有一個表明。求這個集合的元素和。

解題過程:

把複雜的問題分解爲一個一個的簡單問題。

第一步: 先把全部四位數求出來

利用map()、filter()和take_while()等函數,能夠用一行語句將數組生成。

let p3: Vec<u32> = (1..)
    .map(|n| n * (n + 1) / 2)
    .filter(|&n| n > 1000)
    .take_while(|&n| n <= 9999)
    .collect();
    
let p4: Vec<u32> = (1..)
    .map(|n| n * n)
    .filter(|&n| n > 1000)
    .take_while(|&n| n <= 9999)
    .collect();

若是這樣生成六種多邊形數,會有大量的重複代碼,能夠閉包做爲函數的參數,統一寫一個函數,這樣代碼很是簡練。

let p3 = gen_poly_numbers(1000, 9999, |n| n * (n + 1) / 2);
let p4 = gen_poly_numbers(1000, 9999, |n| n * n);
let p5 = gen_poly_numbers(1000, 9999, |n| n * (3 * n - 1) / 2);
let p6 = gen_poly_numbers(1000, 9999, |n| n * (2 * n - 1));
let p7 = gen_poly_numbers(1000, 9999, |n| n * (5 * n - 3) / 2);
let p8 = gen_poly_numbers(1000, 9999, |n| n * (3 * n - 2));
 

fn gen_poly_numbers(start: u32, end: u32, f: fn(u32) -> u32) -> Vec<u32> {
    (1..).map(f)
         .filter(|&n| n >= start)
         .take_while(|&n| n <= end)
         .collect()
}

這裏的一個語法知識點是f: fn(u32) -> u32的寫法,把一個函數做爲參數傳遞給另外一個函數。

第二步: 遞歸算法

如今仍不要直接奔向最後的問題,先從簡單的狀況入手,用題目中給出的三個多邊形數[8128, 8281, 2882],實現一個遞歸算法,驗證算法的正確性。

算法描述:

1)從p3中的取一個數 for n in p3

2)假設這個數是8128,記錄到found數組中,咱們下一步的任務是從[p4, p5]數組中尋找28開頭,81結尾的兩個數,僞代碼:find([p4, p5], 28, 81, [8128])

3)先在p4中找,再在p5中找 for cur_list in lists

4)在數組中尋找以28開頭的數 for n in cur_list

5)假設是在p5中找到了2882,此時found數組變爲[8128, 2882],遞歸調用,在[p4]中查找以82開頭,81結尾的一個數 find([p4], 82, 81, [8128, 2882])

5.1)在p4裏會找到一個以82開頭的數,即8281,更新found,再調用 find([], 81, 81 [8128, 2882, 8281]

5.2)從這裏能夠發現遞歸函數的退出機制,待查的數組已經爲空,要找的開頭與結尾正好相等。

遞歸函數的源代碼:

fn find(lists: &[&[u32]], start: u32, end: u32, found: &[u32])  {
    if lists.is_empty() && start == end {
        let result = found.iter().sum::<u32>();
        println!("{:?} {}", found, result);
    }

    for (i, &cur_list) in lists.iter().enumerate() {
        for &n in cur_list {
            if head(n) == start {
                let mut lists_copy = lists.to_vec();
                lists_copy.remove(i);
                let mut found_copy = found.to_vec();
                found_copy.push(n);
                find(&lists_copy, tail(n), end, &found_copy);
            }
        }
    }
}

lists.copy.remove(i)的含義,假設當前搜索的數組爲[p4, p5, p6, p7, p8],若是在p5中找到了知足條件的數,後續的搜索範圍將是[p4, p6, p7, p8],這裏的i記住數組中的位置,remove(i)將刪除p5。

主程序中的代碼:

for n in p3 {
    find(&[&p4, &p5], n % 100, n / 100, &[n]);
}

第三步:解決最終的問題

搜索3個數的代碼測試經過後,再搜索6個數。

for n in p3 {
    find(&[&p4, &p5, &p6, &p7, &p8], n % 100, n / 100, &[n]);
}

因爲p8中的數據元素較少,先搜索p8可能會稍快一點。

--- END ---

我把解決這些問題的過程記錄了下來,寫成了一本《用歐拉計劃學 Rust 編程》PDF電子書,請隨意下載。

連接:https://pan.baidu.com/s/1NRfTwAcUFH-QS8jMwo6pqw

提取碼:qfha

因爲歐拉計劃不讓發佈100題以外的解題步驟,不然封號,因此最新PDF再也不公開,請加我微信(SLOFSLB)索要最新的PDF電子書。

相關文章
相關標籤/搜索