Thành thạo các kỹ năng trong Functional Programming giúp chúng ta thao tác tính toán một cách dễ dàng

Outline

  • Chương 0: Tìm hiểu về Functional Programming ?
  • Chương 1: Tìm hiểu về Vec, HashMap, HashSet
  • Chương 2: Closure và Iterator
    • Fn closures
    • FnMut closures
    • FnOnce closures
    • Iterator
  • Chương 3: Thực hành
  • Chương 4: Tổng kết

References:

  • https://doc.rust-lang.org/cargo/
  • https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html
  • https://semver.org/
  • https://toml.io/en/

Chương 0: Tìm hiểu về Functional Programming ?

Phong cách lập trình

Các điểm cần học trong Rust

  • Closures
  • Iterator
  • Vec, HashMap<K,V>, HashSet

Chương 1: Tìm hiểu về Vec, HashMap, Iterator, HashSet

Vec

Là kiểu dữ liệu luật chuẩn trong thư viện std::collections

  • Dynamic arrays (Vec) are the most universal and straightforward to use sequential data structure. They capture the speed and accessibility of an array, the dynamic sizing of a list, and they are the fundamental building block for higher order structures (such as stacks, heaps, or even trees). So, when in doubt a Vec is always a good choice.
  • VecDeque is a close relative of the Vec, implemented as a ring buffer—a dynamic array that wraps around the ends end, making it look like a circular structure. Since the underlying structure is still the same as Vec, many of its aspects also apply here.
  • The LinkedList is very limited in its functionality in Rust. Direct index access will be inefficient (it's a counted iteration), which is probably why it can only iterate, merge and split, and insert or retrieve from the back and front.

HashMap<K,V>

  • Chèn một phần tử
use std::collections::HashMap;

let mut map = HashMap::new();
map.insert(1, "a");
assert_eq!(map.remove(&1), Some("a"));
assert_eq!(map.remove(&1), None);
  • Duyệt qua các phần tử: Sử dụng hàm iter_mut and iter
use std::collections::HashMap;

let mut map = HashMap::from([
    ("a", 1),
    ("b", 2),
    ("c", 3),
]);
// Update all values
for (_, val) in map.iter_mut() {
    *val *= 2;
}

for (key, val) in &map {
    println!("key: {key} val: {val}");
}
  • Xoá một phần tử
pub fn remove<Q>(&mut self, k: &Q) -> Option<V>
where
    K: Borrow<Q>,
    Q: Hash + Eq + ?Sized,

use std::collections::HashMap;

let mut map = HashMap::new();
map.insert(1, "a");
assert_eq!(map.remove(&1), Some("a"));
assert_eq!(map.remove(&1), None);

Giải thích: Tại sao std::borrow:Borrow<Q> Nghĩa là bất kỳ kiểu nào mà có hàm biến đổi từ chính nó sang kiểu Q. Thông thường nó muốn biển đổi 1 kiểu sang kiểu ẩn chứa dưới nó.Eg Box<T> có thể được mượn như T, trong khi String có thể được mượn như str

pub trait Borrow<Borrowed>
where
    Borrowed: ?Sized,
{
    // Required method
    fn borrow(&self) -> &Borrowed;
}

Kiểu này khá giống thằng AsRef nhưng Có kiểu khác

https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/borrow-and-asref.html https://rusty-ferris.pages.dev/blog/asref-vs-borrow-trait/

Chương 2: Closure

Định nghĩa

|x| {}

Fn

struct User {
    name: String,
    age: u32,
    salary: u64,
}

fn validate_user(name: &str) -> bool {
    name.len() != 0
}
pub fn run() {
    let p1 = User {
        name: String::from("Tuanpa"),
        age: 35,
        salary: 40_000,
    };
    let validate_user = |name: &str| name.len() != 0;
    println!("User is valid: {}", validate_user(&p1.name));
}
> User is valid: true

FnOnce

fn run2() {
    let mut a = Box::new(23);
    let call_me = || {
        let c = a;
    };
    call_me();
    call_me(); // --> 
}

Và lỗi như sau

use of moved value: `call_me`
value used here after moverustcClick for full compiler diagnostic
closure_test.rs(25, 5): `call_me` moved due to this call
closure_test.rs(23, 17): closure cannot be invoked more than once because it moves the variable `a` out of its environment
closure_test.rs(25, 5): this value implements `FnOnce`, which causes it to be moved when called

Gỉai thích: Chúng ta let c = a; do đó a được move vào trong hàm và do đó khi gọi hàm lần 2 chúng ta không thể nào gọi được do đã move

FnMut


fn main() {
    let mut a = String::from("Hey!");
    let fn_mut_closure = || {
        a.push_str("Alice");
    };
    fn_mut_closure();
    println!("Main says: {}", a);
}

Lỗi như sau

calling `fn_mut_closure` requires mutable binding due to mutable borrow of `a`

Chỉ cần chúng ta khai báo let mut fn_mut_closure là biên dịch thành công

Functional Pointer

Chúng ta hay định nghĩa functional pointer bằng fn. Vi du

 fn(u32) -> u32

Kết luận: Phụ thuộc vào cách chúng ta sử dụng trong closure thế nào mà hàm sẽ dạng Fn, FnOne, hày FnMut Mặc định các biến sẽ được tham chiếu trong hàm closure

Bài tập: Bài 1: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3ecc4a4a0f0b9683c8a4bedab12f21f5 Bài 2: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f15120e9d974a8d744dc1ef70b3fe697

Iterator

Bài toán: Chúng ta cho một struct

struct User {
    name: String,
    age: u32,
    salary: u64,
}

Chúng ta cần duyệt qua từng trường của struct này

Iterator trait

Thuật ngữ:

  • iteration: Nói đến mỗi lần duyệt qua 1 phần tử
  • iterate: Hành động lặp đi lặp lại từng lượt một trong quá trình xử lý tính toán
  • iterable: Khả năng có thể duyệt qua của đối tương đó
pub trait Iterator {
    type Item;

    // Required method
    fn next(&mut self) -> Option<Self::Item>;

    // Provided methods
    fn next_chunk<const N: usize>(
        &mut self
    ) -> Result<[Self::Item; N], IntoIter<Self::Item, N>>
       where Self: Sized { ... }

Trait này khi implement nó cần định nghĩa 2 thứ

  • Kiểu mỗi item trong nó: type Item
  • Hàm fn next(&mut self) -> Option<Self::Item> cách thức duyệt qua nó khi mỗi lần gọi hàm next

Ngoài ra nó có vô số hàm nữa

Iterator

impl Iterator for User {
    type Item = Vec<String>;
    fn next(&mut self) -> Option<Self::Item> {
        Some(vec![
            "name".to_string(),
            "age".to_string(),
            "salary".to_string(),
        ])
    }
}

IntoIterator

Ý nghĩa của thằng này là cách chúng ta biến thành Iterator từ một đối tượng bất kỳ

pub trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;

    // Required method
    fn into_iter(self) -> Self::IntoIter;
}

Ví dụ:

struct Book {
    title: String,
    author: String,
    genre: String,
}
struct BookIterator {
    properties: Vec<String>,
}
impl Iterator for BookIterator {
    type Item = String;
    fn next(&mut self) -> Option<Self::Item> {
        if !self.properties.is_empty() {
            Some(self.properties.remove(0))
        } else {
            None
        }
    }
}

impl IntoIterator for Book {
    type Item = String;
    type IntoIter = BookIterator;
    fn into_iter(self) -> Self::IntoIter {
        BookIterator {
            properties: vec![self.title, self.author, self.genre],
        }
    }
}

fn main() {
    let book: Book = Book {
        title: "Rust Zero to Hero".to_string(),
        author: "Kitto".to_string(),
        genre: "Test".to_string(),
    };

    let mut bi = book.into_iter();
    println!("{:?}", bi.next());
    println!("{:?}", bi.next());
    println!("{:?}", bi.next());
    println!("{:?}", bi.next());

    for bi in book.into_iter() {
        println!("{:?}", bi);
    }
}

Hãy nhìn vào Vec<T>

Vector thông qua Iterator

fn main() {
    let mut v1 = vec![45, 3033, 222, 222];

    // for v in v1 {
    //     println!("{v}")
    // }

    for v in &v1 {
        println!("{v}")
    }
}

HashMap<K,V> thông qua Iterator

fn main() {
    let words = vec!["hello", "world", "rust"];

    // let result: Vec<String> = words.iter().map(|word| word.to_uppercase()).collect();
    let result: Vec<String> = words
        .into_iter()
        .filter(|&word| word.starts_with("r"))
        .map(|word| word.to_uppercase())
        .collect();
    println!("{:?}", result);
}

Nghiên cứu một số hàm hay dùng

Hàm filter

fn filter<P>(self, predicate: P) -> Filter<Self, P>
    where
        Self: Sized,
        P: FnMut(&Self::Item) -> bool,
    {
        Filter::new(self, predicate)
    }

Ta nhìn thấy rằng hàm này FnMut(&Self::Item) -> bool

  • Nó nhận một tham chiếu tới Self::Item --> Do do ví dụ trên ta viết |&word|
  • Nó trả về một Filter<Self, P>. Đây là kiểu lazy implement. Khi nào chúng ta gọi hàm collect() nó mới thực sự gọi

Hàm map

 fn map<B, F>(self, f: F) -> Map<Self, F>
    where
        Self: Sized,
        F: FnMut(Self::Item) -> B,
    {
        Map::new(self, f)
    }

Hàm này biến đổi một Self::Item tới kiểu B. Trong ví dụ trên ta biến đổi rust thành RUST. Chú ý là nó consume Item nhé.

let a = [1, 2, 3];

let mut iter = a.iter().map(|x| 2 * x);

assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(6));
assert_eq!(iter.next(), None);

Các MapFilter nó đều thực hiện Iterator nên nó có thể gọi hàm next() được nhé

Tham khảo: https://doc.rust-lang.org/std/iter/struct.Map.html

Hàm zip

fn zip<U>(self, other: U) -> Zip<Self, U::IntoIter>
    where
        Self: Sized,
        U: IntoIterator,
    {
        Zip::new(self, other.into_iter())
    }

Nó nhóm 2 bộ với nhau

let a1 = [1, 2, 3];
let a2 = [4, 5, 6];

let mut iter = a1.iter().zip(a2.iter());

assert_eq!(iter.next(), Some((&1, &4)));
assert_eq!(iter.next(), Some((&2, &5)));
assert_eq!(iter.next(), Some((&3, &6)));
assert_eq!(iter.next(), None);

Hàm fold

let a = [1, 2, 3];

// the sum of all of the elements of the array
let sum = a.iter().fold(0, |acc, x| acc + x);

assert_eq!(sum, 6);

Hàm enumerate

Hàm này để lấy index

Hàm for_each

fn main() {
    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]);
}

Hàm inspect

fn inspect<F>(self, f: F) -> Inspect<Self, F> ⓘ
where
    Self: Sized,
    F: FnMut(&Self::Item),


    let a = [1, 4, 2, 3];

// this iterator sequence is complex.
let sum = a.iter()
    .cloned()
    .filter(|x| x % 2 == 0)
    .fold(0, |sum, i| sum + i);

println!("{sum}");

// let's add some inspect() calls to investigate what's happening
let sum = a.iter()
    .cloned()
    .inspect(|x| println!("about to filter: {x}"))
    .filter(|x| x % 2 == 0)
    .inspect(|x| println!("made it through filter: {x}"))
    .fold(0, |sum, i| sum + i);

println!("{sum}");

Chương 3: thực hành

Bài 1:

Cho một danh sách các từ, nhóm các từ lại mà anagrams

fn main() {
    let words = vec!["hello", "world", "rust", "elloh", "rldow", "tusr", "ruts"];
}

Nhóm các cụm giống nhau thành một danh sách ["hello", "ellho"], ["rust", "tusr", "rust"]

Solution:

use std::collections::HashMap;

fn group_words(list: Vec<String>) -> Vec<Vec<String>> {
    let mut word_hash = HashMap::new();
    let mut char_freq = vec![0; 26];
    for word in list {
        for c in word.to_lowercase().chars() {
            char_freq[(c as u8 - b'a') as usize] += 1;
        }
        let key = char_freq
            .into_iter()
            .map(|i| i.to_string())
            .collect::<Vec<String>>(); // turbo fis ::<>

        word_hash.entry(key).or_insert(vec![]).push(word);
        char_freq = vec![0; 26];
    }
    for (key, value) in &word_hash {
        println!("{:?} {:?}", key, value);
    }
    word_hash.into_iter().map(|(_, v)| v).collect()
}

Bài 2:

Bài 3: