[Rust Serial] Bài 3: Functional Programming và Collection Trong Rust
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
anditer
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ó.EgBox<T>
có thể được mượn nhưT
, trong khiString
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áchttps://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àmnext
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àmcollect()
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 Map
và Filter
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()
}