心血來潮跑去用 Rust 寫一下 Project Euler #1 的題目, 題目其實很簡單,算出 1000 以下 3 或 5 的倍數的總和, 就是個簡單的數學題,這樣的題目如果也只用非常簡單的作法來解就太無聊了, 所以就拿簡單的題目試試看可以展現出 Rust 的哪些 features (?

#![feature(iter_arith)]     // for .sum()
                            // unstable library feature 'iter_arith' : bounds recently changed

fn p1_sol1() -> u64 {
    // formula solution

    //  3,  6,  9, ...
    //  5, 10, 15, ...
    // 15, 30, 45, ...

    //  3 * 1,  3 * 2,  3 * 3, ...,  3 * a => sum:  3 * a * (1 + a) / 2
    //  5 * 1,  5 * 2,  5 * 3, ...,  5 * b => sum:  5 * b * (1 + b) / 2
    // 15 * 1, 15 * 2, 15 * 3, ..., 15 * c => sum: 15 * c * (1 + c) / 2

    fn sum_range(start: u64, end: u64) -> u64 {
        end * (start + end) / 2
    }

    fn sum_multiples_of_two_below(base_num: &[u64], end: u64) -> u64 {
        let v1 = base_num[0];
        let v2 = base_num[1];
        let a = (end - 1) / v1;
        let b = (end - 1) / v2;
        let c = (end - 1) / (v1 * v2);
        let sum_a = v1 * sum_range(1, a);
        let sum_b = v2 * sum_range(1, b);
        let sum_c = (v1 * v2) * sum_range(1, c);
        sum_a - sum_c + sum_b
    }

    sum_multiples_of_two_below(&[3, 5], 1000)

    // -C opt-level=3 :
    //
    //      p1_sol1::h7b3c1cda8687c063eaa:
    //          cmpq     %fs:112, %rsp
    //          ja       .LBB0_2
    //          movabsq  $8, %r10
    //          movabsq  $0, %r11
    //          callq    __morestack
    //          retq
    //      .LBB0_2:
    //          pushq    %rbp
    //          movq     %rsp, %rbp
    //          movl     $233168, %eax
    //          popq     %rbp
    //          retq
    //
    // -C opt-level=3 -C no-stack-check :
    //
    //      p1_sol1::h7b3c1cda8687c063eaa:
    //          pushq   %rbp
    //          movq    %rsp, %rbp
    //          movl    $233168, %eax
    //          popq    %rbp
    //          retq
}

fn p1_sol2() -> u64 {
    // simple filter solution
    (1..1000)
        .filter(|&n| (n % 3 == 0) || (n % 5 == 0))
        .sum()
}

fn p1_sol3() -> u64 {
    // general filter solution
    fn sum_multiples_of_base_below(base_num: &[u64], start: u64, end: u64) -> u64 {
        (start..end)
            .filter(|&n| base_num.iter().any(|&base| n % base == 0))
            .sum()
    }

    sum_multiples_of_base_below(&[3, 5], 1, 1000)
}

fn p1_sol4() -> u64 {
    // macro version for filter
    macro_rules! sum_multiples_of_base_below {
        ( OωO $($base:expr),+ OωO , $start:expr, $end:expr ) => {
            ($start..$end)
                .filter(|&n| $(n % $base == 0)||+)
                .sum()
        }
    }

    sum_multiples_of_base_below!(OωO 3, 5 OωO, 1, 1000)
}

fn p1_sol5() -> u64 {
    // closure

    let mut s = 0;
    let mut v = 0;

    {
        // only borrow in this scope

        let mut f = || {
            v = v + 1;
            s = s + ((v % 3) * (v % 5) < 1) as u64 * v;
        };

        for _ in 1..1000 {
            f();
        }
    }

    s
}

fn p1_sol6() -> u64 {
    // static variable

    fn f() -> u64 {
        static mut s: u64 = 0;
        static mut v: u64 = 0;

        // static mut is unsafe
        unsafe {
            v = v + 1;
            s = s + ((v % 3) * (v % 5) < 1) as u64 * v;

            s
        }
    }

    let mut result = 0;

    for _ in 1..1000 {
        result = f();
    }

    result
}

fn p1_sol7() -> u64 {
    // iterator

    struct Euler {
        s: u64,
        v: u64,
    }

    impl Iterator for Euler {
        type Item = u64;

        fn next(&mut self) -> Option<u64> {
            let s = self.s;
            let v = self.v;
            let s = s + ((v % 3) * (v % 5) < 1) as u64 * v;
            let v = v + 1;
            self.s = s;
            self.v = v;

            Some(self.s)
        }
    }

    let euler = Euler { s: 0, v: 0 };

    euler.skip(1000-1)
        .next().unwrap_or(0)
}

fn p1_sol8() -> u64 {
    // overload index
    // index has side-effect, it's bad :P

    use std::ops::{Index, IndexMut};

    struct Euler {
        s: u64,
        v: u64,
    }

    impl Index<u64> for Euler {
        type Output = u64;

        fn index(&self, _index: u64) -> &u64 {
            &self.s
        }
    }

    impl IndexMut<u64> for Euler {
        fn index_mut(&mut self, _index: u64) -> &mut u64 {
            let v = self.v;
            let s = self.s;
            let v = v + 1;
            let s = s + ((v % 3) * (v % 5) < 1) as u64 * v;
            self.v = v;
            self.s = s;
            &mut self.s
        }
    }

    let mut euler = Euler { s: 0, v: 0 };

    for _ in 1..1000 {
        &mut euler[0];
    }

    euler[0]
}

fn main() {
    // sum of all the multiples of 3 or 5 below 1000
    // ans : 233168
    println!("p1_sol1 : {}", p1_sol1());
    println!("p1_sol2 : {}", p1_sol2());
    println!("p1_sol3 : {}", p1_sol3());
    println!("p1_sol4 : {}", p1_sol4());
    println!("p1_sol5 : {}", p1_sol5());
    println!("p1_sol6 : {}", p1_sol6());
    println!("p1_sol7 : {}", p1_sol7());
    println!("p1_sol8 : {}", p1_sol8());
}

Solution 1

一般的公式解,但是還沒全部完成, 目前只能吃兩個 base number,需要更 general。

這邊剛好可以看到幾個地方, 一個是 Rust 會用 & 符號來表示 pass by reference, array 的 reference 就會變成 &[type] 。 再來是 Rust 的 function 裡面可以定義 function, 個人有在寫 Python, 而 Python 對於這種 function 裡面定義 function 是蠻方便且常見的 (看看 decorator), 看到 Rust 可以有 local function 就有種熟悉感, 雖然 Python 跟 Rust 這兩個語言在本質上有很大的差異 XD

Solution 2

Rust 可以用 .. 這個特殊的 operator 來產生 Python 中 range 的效果, 使用方法就直接寫 start..end 就可以了。

另外 Rust 有支援豐富的 iterator 操作, 可以使用 .filter 來對 iterator 做篩選, 而 .filter 中可以直接寫 lambda function 傳入, lambda function 的 syntax 是用 | ... | 把參數夾住, 可以指定參數的 type 以及是否要用 reference, 如果要寫多行的 lambda function 的話, 可以用 { ... } 來撰寫 (在 Rust 中 {} 是個 expression)。

Solution 3

Solution 2 的改版, 利用傳入的 array 來做出 .filter 需要的 lambda function, 如此一來可以適用於任何長度的 array。

Solution 4

Solution 3 的改版, 刻意拿 macro 出來玩 (?)。 這邊要先注意一件事, Rust 的 macro 跟 C 和 C++ 的不同, 並不是單純的字串取代, 而是跟 AST 息息相關的一部份。 這個範例把 OωO 當作前面 base numbers 的開頭和結尾, 中間的數字都會被 $($base:expr),* 吃進去, $base:expr 表示說我要爬的是 expression, 而爬到的資料我稱它為 “base”, 最外面的 $(...),+ 則表示裡面的 pattern 會重複一次以上 (中間用 , 區隔), 後面分別是 $start:expr$end:expr 兩個 expression, 到這邊已經把東西 parse 完了, 接下來是要生出想要的程式碼, $start$end 直接拿去用在 range 上, 而 $(n % $base == 0)||* 則把寫的 base numbers 都展開成判斷式 (用 || 接起來), 最後就達到類似 Solution 3 的效果啦~

Todo

  • 把 Solution 1 改的更 general

  • 觀察 Rust 編譯完後出來的結果

  • 觀察 Rust 底下的記憶體操作

  • 不同優化下生出的 assembly