回擼Rust China Conf 2020 之《淺談Rust在算法題和競賽中的應用》

Review

很難通過某種單一的方式,就能get到所有Rust技能,學習的方式方法要多樣化:

  • 循序漸進的系統性學習(內存管理->類型系統->所有權)
  • 主題學習(異步、宏)
  • 交流學習(開發者大會、社區)
  • 刻意練習(LeetCode)

剛剛結束的首屆Rust China Conf 2020就是一種交流學習的方式。Rust中文社區采用直播并提供視頻回放,為所有Rustacean提供了絕佳的、寶貴的學習資料。

本篇回擼一把《淺談Rust在算法題和競賽中的應用》,琳瑯滿目的特性和應用,讓人愛不釋手。

Speaker: Wu Aoxiang (吳翱翔)

視頻:Day2 ,03:54:00~04:20:00

1 std::iter::Iterator::peekable

很實用的迭代器能力,標準庫的注釋如下:

Creates an iterator which can use [peek](https://doc.rust-lang.org/std/iter/struct.Peekable.html%23method.peek) to look at the next element of the iterator without consuming it.

fn peekable(self) -> Peekable<Self>

let xs = [1, 2, 3];
?
let mut iter = xs.iter().peekable();
?
// peek() lets us see into the future
assert_eq!(iter.peek(), Some(&&1));
assert_eq!(iter.next(), Some(&1));

2 ?的Option解包能力的應用:LeetCode 7 整數反轉。

impl Solution {
    pub fn reverse(mut x: i32) -> i32 {
        || -> Option<i32> {
            let mut ret = 0i32;
            while x.abs() != 0 {
                ret = ret.checked_mul(10)?.checked_add(x%10)?;
                x /= 10;
            }
            Some(ret)
        }().unwrap_or(0)
    }
}

3 std::iter::Iterator::fold的應用:LeetCode 1486 數組異或操作

impl Solution {
    pub fn xor_operation(n: i32, start: i32) -> i32 {
        (1..n).fold(start, |acc, i| { acc ^ start + 2*i as i32 })
    }
}

4 std::net::IpAddr的應用:LeetCode 468 驗證IP地址

IpAddr是個枚舉類型,能通過String::parse直接進行解析。以下PPT上的代碼并不能通過,所以我猜講者只想給出一個算法框架方便做演示。

use std::net::IpAddr;
?
impl Solution {
    pub fn valid_ip_address(ip: String) -> String {
        match ip.parse::<IpAddr>() {
            Ok(IpAddr::V4(_)) => String::from("IPv4"),
            Ok(IpAddr::V6(_)) => String::from("IPv6"),
            _ => String::from("Neither"),
        }
    }
}

添加檢查后可以通過測試,代碼如下。可見標準庫對IP地址的合法性還是比較寬容的。

use std::net::IpAddr;
?
impl Solution {
    pub fn valid_ip_address(ip: String) -> String {
        match ip.parse::<IpAddr>() {
            Ok(IpAddr::V4(x)) => {
                let array: Vec<Vec<char>> = ip.split('.').map(|x| x.chars().collect()).collect();
                for i in 0..array.len() {
                    if (array[i][0] == '0' && array[i].len() > 1) { return String::from("Neither"); }
                }
                String::from("IPv4")
            },
            Ok(IpAddr::V6(_)) => {
                let array: Vec<Vec<char>> = ip.split(':').map(|x| x.chars().collect()).collect();
                for i in 0..array.len() {
                    if array[i].len() == 0  { return String::from("Neither"); }
                }
                String::from("IPv6")
            },
            _ => String::from("Neither"),
        }
    }
}

5 Rust調用C函數

調用C函數的能力,使得Rust的能力范圍又擴展了。

extern "C" {
    fn rand() -> i32;
}
?
fn main() {
    let rand = unsafe { rand() };
    println!("{}", rand);
}

6 String零開銷提供數組訪問:std::string::String::into_bytes

從源碼來看,String提供字節數組訪問,簡直不費吹灰之力。在ASCII范圍的場景(大多數LeetCode字符串題目),每個字節通常對應一個拉丁字符,CRUD都非常方便。

源碼如下:

This consumes the String, so we do not need to copy its contents.

#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn into_bytes(self) -> Vec<u8> {
    self.vec
}

需要注意的是,如果字符串涉及到國際化的時候,一個字節可能已經不再能映射一個字符了(比如中文字需要多字節存儲),此時直接在字節層面進行CRUD都是非常危險的。

7 飽和運算(防溢出)

各類型的整數和浮點數都有saturating運算系列,以u8減法為例:

pub const fn saturating_sub(self, rhs: u8) -> u8

assert_eq!(100u8.saturating_sub(27), 73);
assert_eq!(13u8.saturating_sub(127), 0);

講者在分析LeetCode 《1512 好數對的數目》一題中應用了該方法。但是就該題目來說,本文給出一種更加簡單的解法,一次迭代即可。

同時講者還使用了cargo bench,來得到該方法納秒級的運行時間,比LeetCode的毫秒級要精確1000倍了。

這里需要注意的是,盡管Rust 2018已經放棄了extern crate,還是需要使用extern crate test來引入test "sysroot" crates。這是例外

#![feature(test)]
extern crate test;
?
pub fn num_identical_pairs(nums: Vec<i32>) -> i32 {
    let mut map: [i32;101] = [0;101];
    nums.into_iter().fold(0, |mut acc, x| { 
        acc += map[x as usize];
        map[x as usize] += 1;
        acc })
}
?
#[cfg(test)]
mod tests {
    use test::Bencher;
    use super::*;
    #[bench]
    fn bench_func(b: &mut Bencher) {
        b.iter(|| {
            num_identical_pairs(vec![1,2,3,1,1,3,1]);
        });
    }
}

輸出:

PS D:\Project\rust_II\Language\r10_64_benchmark> cargo bench
    Finished bench [optimized] target(s) in 0.02s
     Running target\release\deps\r10_64_benchmark-8eb89c8ab9ad043d.exe
?
running 1 test
test tests::bench_func ... bench:          81 ns/iter (+/- 74)
?
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out

8 善用cargo clippy

使用clippy,多多益善。cargo clippy是CI友好的,如果你是眼睛里容不下warning的人,可以設置使任何warning導致編譯失敗:

cargo clippy -- -D warnings

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容