隱私權政策

搜尋此網誌

技術提供:Blogger.

關於我自己

我的相片
目前從事軟體相關行業,喜歡閱讀、健身、喝調酒。習慣把遇到的問題記下來,每天做一些整理方便自己以後查。 Python、Rust、Kotlin等程式語言皆為自學,目前比較著重在Rust語言,歡迎一起討論。

2023年11月26日 星期日

git 常用指令紀錄


git clone

    git clone 應該是最常用的

    簡單來說就是把GitHub上面的東西載下來

    這邊有兩個我比較常用的

    git clone <url>

        url就是GitHub專案的網址

        例如: git clone https://github.com/Lagee666/git-practice.git     

    git clone -b <branch name> <url>

        git上面通常有許多分支

        大家在一起工作時總喜歡開一個自已的分支

        這時候就會需要clone分支下來

        例如: git clone -b new-branch https://github.com/Lagee666/git-practice.git


git status

    clone完之後可以透過git status觀看資訊

    會顯示目前在哪個分支、commit資訊、修改的檔案


git add

    git add 為將檔案加入到索引

    加完之後才可以commit

    git add <file name>

        例如 git add test.py

        這串意思就是將test.py這個檔案加入到索引

    git add .

        將資料夾內所有東西加入到索引


git commit

    git commit為提交版本資訊

    git commit -m "<msg>"

        將提交msg作為版本資訊

        例如: git commit -m "Test"

        這樣在GitHub上面就會顯示Test

    git commit --amend -m "<msg>"

        amend更可修改上次提交的數據

        當已經push後

        可以用amend覆蓋最後一次的資訊

        例如: git commit -amend -m "test1"

        就會把Test更改為test1


git push

    上傳好朋友

    透過git add放入索引

    並使用git commit輸入資訊後

    可以透過git push直接上傳到GitHub

    git push記得要看好路徑,以及確認status的狀態

    不然就是要指定路徑

    git push -f

        強制上傳

        例如git commit -amend後會無法上傳

        透過這個可以強制把資料上傳到GitHub

        但要小心豬隊友強制把檔案覆蓋


git log

    查看版本

    可以查看GitHub上版本更新紀錄


git reset[1]

    退版本

    可以將GitHub上面的版本往前

    例如    git reset HEAD^  可以回復到上個版本

                git reset  12ab  回復到12ab這個版本

    而版本可以從git log看到


git rebase[2]

    git rebase為將兩個分支合併

    如果有兩個工程師

    分別以main為基礎創建兩個branch

    可以透過git rebase將兩個branch合併到main


這篇稍微紀錄一下最近常用到的幾個git指令

詳細介紹到時候再補


參考資料

[1] https://www.runoob.com/git/git-reset.html

[2] Git 版本控制教學 - 用範例學 rebase - MyApollo



2023年11月25日 星期六

Rust 切片windows - 一次對數組中多個元素處理


如果想要一次對數組中多個元素處理

例如兩個、三個等等

非常適合用windows

windows就是常聽到的滑動窗口

不只在題目很常看到

在平常Coding也是很好用的方法


windows

文件[1]這樣提到

傳回長度為size的窗口迭代器,並且窗口重疊

...

可能不是很好懂

直接看定義

pub fn windows(&self, size: usize) -> Windows<'_, T>

self為切片本身,可以是字串切片也可以是數組切片

size則是窗口內的要幾個數值

最後回傳一個Windows迭代器

而窗口會重疊

如果傳入大於切片大小的size,不回傳數值

如果size為0,則發生panic


例子

只看文字是不是很難理解

那舉個例子

如果有個數組為[1, 2, 3, 4, 5]

size為2

也就是會輸出[1, 2][2, 3][3, 4][4, 5]

用圖解釋比較好解釋

圖1. 窗口移動講解

以程式碼來說

    let data = vec![1, 2, 3, 4, 5];
    let res = data.windows(2).collect::<Vec<_>>();
    println!("{:?}", res);
[[1, 2], [2, 3], [3, 4], [4, 5]]

第一行創建數組

第二行使用windows創立迭代器,視窗大小設定為2,然後放入數組中

可看出輸出結果與預想一樣


再舉一個

假設size為3

圖2. 窗口大小為3

可看出一開始為[1, 2, 3],第二次會出現[2, 3, 4],第三次為[3, 4, 5]

以程式碼就是這樣

    let data = vec![1, 2, 3, 4, 5];
    let res = data.windows(3).collect::<Vec<_>>();
    println!("{:?}", res);
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]


而如果size大於data的數量會發生什麼事?

    let data = vec![1, 2, 3, 4, 5];
    let res = data.windows(6).collect::<Vec<_>>();
    println!("{:?}", res); []

會直接輸出空數組


另一個是size為0會發生什麼事?

    let data = vec![1, 2, 3, 4, 5];
    let res = data.windows(0).collect::<Vec<_>>();
    println!("{:?}", res);
thread 'main' panicked at 'window size must be non-zero', src\main.rs:11:20

這次會直接panic,明確顯示window size不能為0


應用

舉一個簡單的應用

原本記得在LeetCode上面有做到蠻有趣的題目

但突然找不到

用一個windows搭配map[2]先頂著

假設一樣剛剛的數組

希望獲得每兩個數值的平方相加

可以這麼做

    let data = vec![1, 2, 3, 4, 5];
    let res = data
        .windows(2)
        .map(|x| x[0] * x[0] + x[1] * x[1])
        .collect::<Vec<_>>();
    println!("{:?}", res);
[5, 13, 25, 41]

windows會取出兩個數值

透過map以及閉包配合

可以獲得每兩個數值平方相加結果


參考資料

[1] https://doc.rust-lang.org/std/primitive.slice.html#method.windows

[2] https://lageeblog.blogspot.com/2023/10/rust-map.html

2023年11月24日 星期五

Rust 迭代sum和product概述 - 求和以及求積


這篇講求和以及求積

sum 為迭代器中所有數值加起來

product 為迭代器中所有數值乘起來

這兩個算是比較簡單可以實現和以及積的方法


sum

文件[1]這樣提到了sum

將迭代器所有元素加起來

    fn sum<S>(self) -> S
    where
        Self: Sized,
        S: Sum<Self::Item>,

self為迭代器自身

S為回傳加起來的結果


product

文件[2]

提出product為將所有元素加起來

    fn product<P>(self) -> P
    where
        Self: Sized,
        P: Product<Self::Item>,

self為迭代器自身

P為乘起來的結果


不管是sum還是product

都可以對Some、Result或是數字本身求和求積


例子

    let nums = [0, 1, 2, 3, 4, 5];
    println!("sum: {}", nums.iter().sum::<i32>());
    println!("product: {}", nums.iter().product::<i32>()); sum: 15 product: 0

第一行數組

第二行與第三行,都是透過iter

將數組放入迭代器中

之後透過sum以及product求和求積

要注意使用sum和product很常要明確指定類型

因此要加上::<i32>

0+1+2+3+4+5 = 15

0*1*2*3*4*5 = 0


而使用Option和Ok也可以

    let nums = [Some(0), Some(1), Some(2), Some(3), Some(4), Some(5), None];
    let res_sum: Option<i32> = nums.iter().cloned().sum();
    match res_sum {
        Some(sum) => println!("Option Sum: {}", sum),
        None => println!("Option Sum: None"),
    }
Option Sum: None

因為數組中有一個None

因此會直接回傳None

Ok也可以像程式碼這樣直接用

    let nums: [Result<i32, &str>; 5] = [Ok(1), Ok(2), Ok(3), Ok(4), Ok(5)];
    let res_product: Result<i32,_> = nums.into_iter().product();
    match res_product {
        Ok(product) => println!("Product: {}", product),
        Err(e) => println!("Error: {}", e),
    } Product: 120

就可以將數組中所有的值乘起來


這兩個程式碼有個小地方不一樣

第一個Option我使用cloned[3]

第二個程式碼我是用into_iter[4]

那是因為在Option和Result使用sum和product

需要得到其所有權

cloned可以把每個元素都clone

而into_iter則是所有權轉移


應用

很多題目都會用到求和求積的方法

這邊有一個CodeWar[5]的題目我覺得蠻有趣的

A Narcissistic Number (or Armstrong Number) is a positive number which is the sum of its own digits, each raised to the power of the number of digits in a given base. In this Kata, we will restrict ourselves to decimal (base 10).

For example, take 153 (3 digits), which is narcissistic:

    1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153

and 1652 (4 digits), which isn't:

    1^4 + 6^4 + 5^4 + 2^4 = 1 + 1296 + 625 + 16 = 1938

這題要找自戀數

簡單來說,假設有個n位數,他需要滿足每一位數的n次方相加後,要等於該數字

例如 153, 1^3 + 5^3 + 3^3 = 153

每個位數的次方要等於該數長度

更詳細可以在參考資料找到

    fn narcissistic(num: u64) -> bool {
        todo!()
    }
    #[cfg(test)]
    mod tests {
        use super::*;

        fn dotest(input: u64, expected: bool) {
            let actual = narcissistic(input);
            assert_eq!(actual, expected, "\nIncorrect answer for n={}\nExpected: {expected}\nActual: {actual}", input)
        }

        #[test]
        fn basic_tests() {
            dotest(   7,  true);
            dotest( 371,  true);
            dotest( 122, false);
            dotest(4887, false);
        }
    }

可以由narcissistic撰寫

並且由下方程式碼測試


解答

一個小思路

假設我可以把每一位數取出來

並且把每位數乘上n次方

再加起來做比較


思路有了直接實作

    fn narcissistic(num: u64) -> bool {
        num == num
        .to_string()
        .chars()
        .map(|c| c.to_digit(10).unwrap() as u64)
        .map(|digit| digit.pow(num.to_string().len() as u32))
        .sum()
    }

第一行做比較,是否加完後的值會等於num

第二行將其轉String並且使用chars放入迭代

因為此時轉成char,因此先使用第一次map轉成數字

再使用第二次map,將每個數字乘上num字數次方

最後加起來


參考資料

[1] https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.sum

[2] https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.product

[3] https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.cloned

[4] https://lageeblog.blogspot.com/2023/11/rust-iterintoiter.html

[5] https://www.codewars.com/kata/5287e858c6b5a9678200083c

[6] https://zh.wikipedia.org/zh-tw/%E6%B0%B4%E4%BB%99%E8%8A%B1%E6%95%B0

2023年11月23日 星期四

Rust 切片join概述 - 在每個值中間插值


join,可以在兩個切片中插值

我覺得是一個做題目,或者資料夾操作的時候比較常用到的方法

特別是題目真的不少

後面會舉個例子


join

join被歸類在slice下

文件[1]這樣寫

在每個值中間給定指定符號

    pub fn join<Separator>(&self, sep: Separator) -> <[T] as Join<Separator>>::Output
    where
        [T]: Join<Separator>,

self為切片本身

sep為分隔符號

回傳一個字串String


例子

舉個簡單的例子

    let res = ["Hello", "world", "Rust"];
    println!("{:?}", res.join(" ")); "Hello world Rust"

假設有一個數組分別為["Hello", "world", "Rust"]

可以透過join將其組合成一個數組

成功顯示 Hello world Rust


而join也可以連接數組

    let res = [[1], [2], [3]];
    println!("{:?}", res.join(&0));
[1, 0, 2, 0, 3]

可以將1, 2, 3連接成數組1, 0, 2, 0, 3

也可以連接成字串1, 2, 3

    let nums = [1, 2, 3];
    let res = nums
        .into_iter()
        .map(|s| s.to_string())
        .collect::<Vec<String>>()
        .join(",");
    println!("{:?}", res);

創立一個數組1, 2, 3

透過into_iter[2]丟入迭代器

使用map[3],將每個元素轉成字串

透過collect,將字串值存成數組,也就是成先["1".to_string(), "2".to_string(), "3".to_string()]

最後再透過join連結每個字串


實際例子

我個人覺得

CodeWar的題目真的很多會用到join

如果不太會用join有些題目會蠻麻煩的

我舉一題當作範例[4]

這題是7kyu

不難,可以自己做做看

Complete the function that accepts a string parameter, and reverses each word in the string. All spaces in the string should be retained.

Examples

"This is an example!" ==> "sihT si na !elpmaxe"

"double  spaces"      ==> "elbuod  secaps"

完成一個函數,反轉String中的單字,每個空白鍵要依然留著

fn reverse_words(str: &str) -> String {
    // your code here
    "Test String".to_string()
}

可以用這個做測試

fn sample_test() {
  assert_eq!(reverse_words("The quick brown fox jumps over the lazy dog."), "ehT kciuq nworb xof spmuj revo eht yzal .god");
  assert_eq!(reverse_words("apple"), "elppa");
  assert_eq!(reverse_words("a b c d"),"a b c d");
  assert_eq!(reverse_words("double  spaced  words"), "elbuod  decaps  sdrow");
}




解題思路

預期取出每個單字

並對每個單字做反向才連接


fn reverse_words(str: &str) -> String {
    str.split(" ")
        .into_iter()
        .map(|word| word.chars().rev().collect::<String>())
        .collect::<Vec<_>>()
        .join(" ")
}

先透過split(" ")對空白鍵分割,此時取得每個單字

之後放入數組

透過map將單字反向,使用閉包

將每個單字的字符放入迭代器中並做反向,最後蒐集成String

使用collect::<Vec<_>>將每個String放入數組

最後join以空格連接


參考資料

[1] https://doc.rust-lang.org/std/primitive.slice.html#method.join

[2] https://lageeblog.blogspot.com/2023/10/rust-map.html

[3] https://lageeblog.blogspot.com/2023/10/rust-map.html



2023年11月22日 星期三

Rust 迭代for_each概述 - 對每個元素執行指定操作


最近每次都想要把程式縮減

有時候想要對元素操作

第一個會想到的就是map[1]

但map需要有一個返還值

也就是需要打

let x = num.iter().map(||);

類似這樣子的式子

如果是不需要返還值的操作可以用for_each


for_each

for_each被歸類在迭代器下

文件[2]這樣寫

對迭代器中的每個元素進行指定操作

    fn for_each<F>(self, f: F)
    where
        Self: Sized,
        F: FnMut(Self::Item),

self為迭代器

f為閉包,包含一元素

在大部分的情況下

for_each可以用for迴圈呈現

for迴圈會比較簡單易懂,for_each會有迭代的風格


例子

舉個例子

假設有個數組

希望println數組裡面元素的平方

通常會使用for迴圈這樣打

    let nums = vec![1, 2, 3, 4, 5];
    for num in nums {
        let square = num * num;
        println!("The square value: {}", square);
    }
The square value: 1 The square value: 4 The square value: 9 The square value: 16 The square value: 25

使用for迴圈要打多行指令

才可以達成要的功能

不優雅,太不優雅了


於是有個優雅的功能出現

使用for_each可以這樣打

    let nums = vec![1, 2, 3, 4, 5];
    nums.iter().for_each(|num| {
        let square = num * num;
        println!("The square value: {}", square);
    })
The square value: 1 The square value: 4 The square value: 9 The square value: 16 The square value: 25

第一行為數組

透過iter將其放入迭代器

並且適用for_each對每個元素操作

閉包|num|表示元素值

square為 num * num,之後呈現


for_each跟for一樣,也可以改變原本數組的數值

    let mut nums = vec![1, 2, 3, 4, 5];
    nums.iter_mut().for_each(|num: &mut i32| {
        *num = *num * *num;
    });
    println!("The square value: {:?}", nums); The square value: [1, 4, 9, 16, 25]

透過iter_mut[3]進行可變引用迭代

對*num解引用,並把值設成num * num

用for迴圈的話則是這樣操作

    let mut nums = vec![1, 2, 3, 4, 5];
    for num in nums.iter_mut() {
        *num = *num * *num;
    }
    println!("The square value: {:?}", nums);
The square value: [1, 4, 9, 16, 25]



應用

for_each的文件中舉了一個比較難的例子

就用這題當作範例


首先得要介紹mpsc::channel[4]

文件解釋,創建一個非同步線程,回傳接收方與發送方

...

我的部落格比較希望盡量都可以看懂

畢竟當初新手的時候真覺得很多名詞都不懂

講簡單一點

channel就是會創造一個傳送方(tx)與接收方(rx)

傳送方可以傳送資料,讓遠在天邊的接收方收到資料

特點是可以讓不同程式碼之間傳送資料

至於非同步通道就之後在講


    use std::sync::mpsc::channel;

    let (tx, rx) = channel();
    (0..5).map(|x| x * 2 + 1)
          .for_each(move |x| tx.send(x).unwrap());
   
    let v: Vec<_> = rx.iter().collect();
    assert_eq!(v, vec![1, 3, 5, 7, 9]);

因此一行一行介紹程式碼

第一行導入channel

第三行,透過使用channel創建傳送方(tx)與接收方(rx)

第四行(0..5)表示創建一個0到5的數組,也就是[0, 1, 2, 3, 4]

透過map將數組中的值乘2加1

第五行,透過for_each將x的值傳送出去

第七行,rx接收到tx傳送的x,並將全部蒐集起來放到數組中

最後比較結果


至於第五行有個move[5]

閉包沒辦法使用閉包外的參數

如果要使用,必須使用move將外部的參數放進去

這在閉包、非同步線程很常使用

之後有空會把move和非同步線程講更細一點


結論

有時候用傳統for迴圈會比較直觀

有時候用for_each會比較容易閱讀

我覺得這取決於工程師

看怎樣打會比較開心

我自己單人作業時是喜歡用for_each

因為把程式塞成一行,很帥

特別是在Leetcode、Codewar之類的,更帥

但如果是整個團隊要一起打程式就會看情況了





參考資料

[1] https://lageeblog.blogspot.com/2023/10/rust-map.html

[2] https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.for_each

[3] https://doc.rust-lang.org/std/primitive.slice.html#method.iter_mut

[4] https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html

[5] https://doc.rust-lang.org/std/keyword.move.html

2023年11月21日 星期二

Rust 獲取Option和Result方法 - unwrap五兄弟


Option[1]

    enum Option<T> {
        Some(T),
        None,
    }

有兩個值

一個是Some,另一個則是None

用於表示存在數據或不存在數據

例如初始化、函數返回等


Result[2]

    pub enum Result<T, E> {
        Ok(T),
        Err(E),
    }

Result一樣有兩個值

Ok(T)表示操作成功,Err(E)則是操作失敗

用於表示可能成功或失敗的操作

例如文件讀取、網路請求等


獲取值方法

而想要獲得Some或Ok裡面的值

unwrap是一種方法

但unwrap其實有很多種

根據不同場合可以使用不一樣的

有五個我比較常用的

1. unwrap[3]

    pub fn unwrap(self) -> T

最常用的

直接把Some和Ok的值拿出來

但如果出現None以及Err會直接panic

    let x: Result<u32, &str> = Ok(2);
    assert_eq!(x.unwrap(), 2);

這樣可以取出2

    let x: Result<u32, &str> = Err("emergency failure");
    x.unwrap();

如果使用Err,則會panic

panic顯示emergency failure

2. unwrap_or[4]

    pub fn unwrap_or(self, default: T) -> T

unwrap_or需要輸入一個預設值

如果發生None或者Err,則以預設值為主

若沒發生則取出Some或Ok

    let default = 3;
    let ans: Result<i32, &str> = Ok(5);
    assert_eq!(ans.unwrap_or(default), 5);
    let default = 3;
    let ans: Result<i32, &str> = Err("error");
    assert_eq!(ans.unwrap_or(default), 3);

第一次unwrap_or,成功

所以取出的值是5

第二次unwrap_or,失敗

但有輸入預設值,當失敗值會輸出預設值,也就是3

3. unwrap_or_else[5]

    pub fn unwrap_or_else<F>(self, op: F) -> T
    where
        F: FnOnce(E) -> T,

unwrap_or_else,需要輸入一個閉包

要馬回傳Some或Ok,要馬會回傳閉包值

fn get_user_info(user_id: u32) -> Result<&'static str, &'static str> {
    match user_id {
        1 => Ok("User: Alice"),
        2 => Ok("User: Bob"),
        _ => Err("User not found"),
    }
}

fn main() {
    let user_id_to_fetch = 2;
    let user_info = get_user_info(user_id_to_fetch);
    let result = user_info.unwrap_or_else(|err| {
        println!("Error retrieving user info: {}", err);
        "Unknown User"
    });
    println!("User Info: {}", result);
}
User Info: User: Bob

一個例子,透過unwrap_or_else可以處理Err的情況

如果沒有Err,則會回傳正確的值

以範例程式來說,就是回傳人物的名字

如果Err,則會執行閉包內容,閉包元素為Err錯誤訊息

舉個錯誤例子

fn get_user_info(user_id: u32) -> Result<&'static str, &'static str> {
    match user_id {
        1 => Ok("User: Alice"),
        2 => Ok("User: Bob"),
        _ => Err("User not found"),
    }
}

fn main() {
    let user_id_to_fetch = 5;
    let user_info = get_user_info(user_id_to_fetch);
    let result = user_info.unwrap_or_else(|err| {
        println!("Error retrieving user info: {}", err);
        "Unknown User"
    });
    println!("User Info: {}", result);
}
Error retrieving user info: User not found User Info: Unknown User

會先將err訊息列印出來,然後把Unknown User放入result

最後再次印出來

4. unwrap_or_default[6]

    pub fn unwrap_or_default(self) -> T

unwrap_or_default,跟unwrap_or很像

但unwrap_or需要輸入預設值

unwrap_or_default則不需要

會回傳類型的默認值

例如

    let num: Result<i32, i32> = Ok(3);
    let num1: Result<i32, i32> = Err(1);
    println!("success: {}", num.unwrap_or_default());
    println!("error: {}", num1.unwrap_or_default());
success: 3 error: 0

如果是Ok,則直接回傳Ok值

而如果是Err,則回傳型態默認值

i32的默認值是0

因此最後會回傳0

5. unwrap_err[7]

    pub fn unwrap_err(self) -> E

跟上方四個不一樣

這個是要回傳錯誤的值

如果是Ok會panic

例如

fn main() {
    let num: Result<i32, &str> = Err("Something wrong");
    println!("error: {}", num.unwrap_err());
}
error: Something wrong

但如果是這樣

    let num: Result<i32, &str> = Ok(666);
    println!("Ok: {}", num.unwrap_err());
thread 'main' panicked at 'called `Result::unwrap_err()` on an `Ok` value: 666'

Ok反而會直接跳出錯誤訊息


參考資料

[1] https://doc.rust-lang.org/std/option/enum.Option.html

[2] https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err

[3] https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap

[4] https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_or

[5] https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_or_else

[6] https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_or_default

[7] https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err

2023年11月20日 星期一

Rust 迭代scan - 條件累加並回傳迭代器


Scan 

Scan和Fold[1]很像

都是有一個累加器

可以累加執行閉包後的數值

差別在於Scan回傳的是一個迭代器

Fold則是回傳一個值


Scan被分類在iter

文件[2]這樣解釋Scan

    fn scan<St, B, F>(self, initial_state: St, f: F) -> Scan<Self, St, F>
    where
        Self: Sized,
        F: FnMut(&mut St, Self::Item) -> Option<B>,

self 為一個迭代器

需要有一個初始值以及閉包,閉包內需要兩個元素

一個為內部狀態,另一個為迭代器的值

最後回傳一個迭代器

而所謂的狀態與fold的累加器很像

只是累加器最後只會有一個值,狀態則最後則是疊加器


例子

舉一個簡單的例子

假設有一個數組1, 2, 3, 4...., 10

希望呈現 1、1 + 2、1 + 2 + 3、1 + 2 + 3 + 4 、 ... 、1 + 2 + 3 + 4 + 5 + ... + 10的迭代器

可以使用scan實現

    let res = (1..=10)
        .into_iter()
        .scan(0, |state, x| {
            *state = *state + x;
            Some(*state)
        })
        .collect::<Vec<_>>();
    println!("{:?}", res);
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

第一行為創建1..10的數組

第二行放入蝶愛器

第三行使用scan,包括初始值0、狀態state以及元素值x

第四行,因為state是&mut,必須解引用成i32才可以使用他

第五行則是回傳,因為scan閉包回傳值為Option,因此使用Some判斷裡面的值

最後則是輸出

可以看到滿足要求


應用

費波那契數(Successione di Fibonacci)[3],又稱為黃金分割、費式數列

黃金分割指的是,一個整體會被切成兩塊

較大的那塊會是較小那塊的0.618

被公認為最能引起美感的比例

特別在文藝復興前後受到各地的歡迎[4]

不知道有沒有看過這張圖



這個數列滿足這個公式

也就是會呈現0, 1, 1, 2, 3, 5, 8, 13, 21...

假設題目希望算出n = 20內的所有黃金比例數值

就很適合用scan


解答

    let res = (1..=20)
        .into_iter()
        .scan((0, 1), |state, _| {
            let next = state.0 + state.1;
            *state = (state.1, next);
            Some(next)
        })
        .collect::<Vec<_>>();
    println!("{:?}", res);
[1, 2, 3, 5, 8, 13, 21, 34, 55, 89,
144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946]

一樣先將數組放入迭代器中

但這次scan初始值要放兩個

初始值為0和1,因此輸入(0, 1)

state具有兩個值,第一個元素表示F(n-2),第二個則是F(n-1)

以一開始來說,第一個元素為0,第二個元素為1

next 表示 F(n),F(n)為F(n-2)+F(n-1)

算出現在的值後,需更新state

state放入F(n-1),也就是state.1,與next

詳細可以看這張表

表1,透過scan實現費式數列詳細資料




參考資料

[1] https://lageeblog.blogspot.com/2023/10/rust-fold.html

[2] https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.scan

[3] https://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0

[4] https://baike.baidu.hk/item/%E9%BB%83%E9%87%91%E5%88%86%E5%89%B2/115896