隱私權政策

搜尋此網誌

技術提供:Blogger.

關於我自己

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

2023年11月19日 星期日

Rust 迭代take_while - 有條件控制迭代次數


之前有提到take [1]

這篇提他的好兄弟take_while

take是回傳前幾個元素

而take_while則是回傳元素直到不滿足條件


take_while

take_while被歸類在iter下面

文件[2]這樣解釋

創建一個迭代器,取出滿足條件的元素

    fn take_while<P>(self, predicate: P) -> TakeWhile<Self, P>
    where
        Self: Sized,
        P: FnMut(&Self::Item) -> bool,

self為迭代器自身

predicata為一個閉包

會搜索元素,直到第一個false為止

最後回傳TakeWhile迭代器


例子

假設我們要找出一個數組中

小於等於五的數字

可以這樣打

    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let result: Vec<_> = numbers.into_iter().take_while(|&x| x <= 5).collect();
    println!("{:?}", result);
[1, 2, 3, 4, 5]

將數組透過into_iter放入迭代器

使用take_while

找到第一個小於等於5的

也就是會把6以前的都輸出


那這樣會有個疑問

他個filter有什麼差

filter這樣做也可以達成小於等於五

    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let result: Vec<_> = numbers.into_iter().filter(|&x| x <= 5).collect();
    println!("{:?}", result);
[1, 2, 3, 4, 5]


差別在take_while會在false的第一時間就結束迭代

因此如果數組變成

    let numbers = vec![1, 2, 3, 4, 5, 6, 7, -8, 9, 10];
    let result: Vec<_> = numbers.into_iter().filter(|&x| x <= 5).collect();
    println!("{:?}", result);
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, -8, 9, 10];
    let result: Vec<_> = numbers.into_iter().take_while(|&x| x <= 5).collect();
    println!("{:?}", result);
[1, 2, 3, 4, 5, -8] [1, 2, 3, 4, 5]

當迭代到-6時

take_while會停止

filter則是會繼續濾


實際應用

最近做題目,CodeWars[3]有一題蠻有趣的

A beer can pyramid will square the number of cans in each level - 1 can in the top level, 4 in the second, 9 in the next, 16, 25...Complete the beeramid function to return the number of complete levels of a beer can pyramid you can make, given the parameters of:

  1. your referral bonus, and

  2. the price of a beer can

有一個啤酒塔,算出可以疊幾層

最上面是1個

第二層會是4個

第三層會是9個

以此類推,下面那層會是層數的平方

簡單畫一下3D圖,也就是長這樣


會給予兩個值

一個是總資產,另一個是一個啤酒是多少錢

透過這資產和單價算出可以疊幾層

fn beeramid (bonus: i32, price: f32) -> usize {

}

可以用下面這些測試


#[cfg(test)]
mod tests {
    use super::beeramid;

    #[test]
    fn sample_tests() {
        assert_eq!(beeramid(9, 2.0), 1);
        assert_eq!(beeramid(10, 2.0), 2);
        assert_eq!(beeramid(11, 2.0), 2);
        assert_eq!(beeramid(21, 1.5), 3);
        assert_eq!(beeramid(454, 5.0), 5);
        assert_eq!(beeramid(455, 5.0), 6);
        assert_eq!(beeramid(4, 4.0), 1);
        assert_eq!(beeramid(3, 4.0), 0);
        assert_eq!(beeramid(0, 4.0), 0);
        assert_eq!(beeramid(-1, 4.0), 0);
    }
}

解題

這題就很適合用take_while
    let tmp = ((bonus as f32) / price) as i32;
    (1..=tmp)
        .into_iter()
        .scan(0, |state, x| {
            *state = *state + x * x;
            Some(*state)
        })
        .take_while(|&x| x <= tmp)
        .count()

bouns為總資產,price為每一瓶價格

因此tmp為可以買幾瓶

使用scan[4]跟take_while配合可以達成題目要求

scan可以創建一個的迭代器,內容物為疊加平方值,其數值代表這一層往上共有幾個瓶子

透過take_while,抓出疊起來的瓶子小於等於買的數量

然後透過count計算總迭代次數,也就是層數


參考資料

[1] https://lageeblog.blogspot.com/2023/11/rust-take.html

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

[3] https://www.codewars.com/kata/51e04f6b544cf3f6550000c1

[4] https://lageeblog.blogspot.com/2023/11/rust-scan.htm

0 comments:

張貼留言