函式式語言特性:-迭代器和閉包
本章內容
- 閉包(closures)
- 迭代器(iterators)
- 優化改善 12 章的實體專案
- 討論閉包和迭代器的運行時性能
一、閉包(1)- 使用閉包創建抽象行為
什么是閉包(closure)
- 閉包:可以捕獲其所在環境的匿名函式,
- 閉包:
- 是匿名函式
- 保存為變數、作為引數
- 可在一個地方創建閉包,然后在另一個背景關系中呼叫閉包來完成運算
- 可從其定義的作用域捕獲值
例子 - 生成自定義運動計劃的程式
- 演算法的邏輯并不是重點,重點是演算法中的計算程序需要幾秒鐘時間
- 目標:不讓用戶發生不必要的等待
- 僅在必要時呼叫該演算法
- 只呼叫一次
創建專案
~/rust
? cargo new closure
Created binary (application) `closure` package
~/rust
? cd closure
closure on master [?] via ?? 1.67.1
? c
closure on master [?] via ?? 1.67.1
?
src/main.rs 檔案
use std::thread;
use std::time::Duration;
fn main() {
let simulated_user_specified_value = https://www.cnblogs.com/QiaoPengjun/archive/2023/04/08/10;
let simulated_random_number = 7;
generate_workout(simulated_user_specified_value, simulated_random_number);
}
fn simnulated_expensive_calculation(intensity: u32) -> u32 {
println!("calculating slowly ...");
thread::sleep(Duration::from_secs(2));
intensity
}
fn generate_workout(intensity: u32, random_number: u32) {
if intensity < 25 {
println!("Today, do {} pushups!", simnulated_expensive_calculation(intensity));
println!("Next, do {} situps!", simnulated_expensive_calculation(intensity));
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!("Today, run for {} minutes!", simnulated_expensive_calculation(intensity));
}
}
}
未用閉包優化:
use std::thread;
use std::time::Duration;
fn main() {
let simulated_user_specified_value = https://www.cnblogs.com/QiaoPengjun/archive/2023/04/08/10;
let simulated_random_number = 7;
generate_workout(simulated_user_specified_value, simulated_random_number);
}
fn simnulated_expensive_calculation(intensity: u32) -> u32 {
println!("calculating slowly ...");
thread::sleep(Duration::from_secs(2));
intensity
}
fn generate_workout(intensity: u32, random_number: u32) {
let expensive_result = simnulated_expensive_calculation(intensity);
if intensity < 25 {
println!("Today, do {} pushups!", expensive_result);
println!("Next, do {} situps!", expensive_result);
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!("Today, run for {} minutes!", expensive_result);
}
}
}
優化:
use std::thread;
use std::time::Duration;
fn main() {
let simulated_user_specified_value = https://www.cnblogs.com/QiaoPengjun/archive/2023/04/08/10;
let simulated_random_number = 7;
generate_workout(simulated_user_specified_value, simulated_random_number);
}
fn generate_workout(intensity: u32, random_number: u32) {
let expensive_closure = |num| {
println!("calculating slowly ...");
thread::sleep(Duration::from_secs(2));
num
};
if intensity < 25 {
println!("Today, do {} pushups!", expensive_closure(intensity));
println!("Next, do {} situps!", expensive_closure(intensity));
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!("Today, run for {} minutes!", expensive_closure(intensity));
}
}
}
二、閉包(2)- 閉包型別推斷和標注
閉包的型別推斷
- 閉包不要求標注引數和回傳值的型別
- 閉包通常很短小,只在狹小的背景關系中作業,編譯器通常能推斷出型別
- 可以手動添加型別標注
let expensive_closure = |num: u32| -> u32 {
println!("calculating slowly ...");
thread::sleep(Duration::from_secs(2));
num
};
函式和閉包的定義語法
fn add_one_v1(x: u32) -> u32 { x + 1 } // 函式
let add_one_v2 = |x: u32| -> u32 { x + 1 }; // 閉包
let add_one_v3 = |x| { x + 1 }; // 閉包
let add_one_v4 = |x| x + 1 ; // 閉包
閉包的型別推斷
- 注意:閉包的定義最終只會為引數/回傳值推斷出唯一具體的型別
fn main() {
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5) // 報錯
}
三、閉包(3)- 使用泛型引數和 Fn Trait 來存盤閉包
繼續解決 13.1 中 ”運動計劃“ 程式的問題
- 另一種解決方案:
- 創建一個 Struct,它持有閉包及其呼叫結果,
- 只會在需要結果時才執行該閉包
- 可快取結果
- 這個模式通常叫做記憶化(memoization)或延遲計算(lazy evaluation)
如何讓 Struct 持有閉包
- Struct 的定義需要知道所有欄位的型別
- 需要指明閉包的型別
- 每個閉包實體都有自己唯一的匿名型別,即使兩個閉包簽名完全一樣,
- 所以需要使用:泛型和 Trait Bound
Fn Trait
- Fn traits 由標準庫提供
- 所有的閉包都至少實作了以下 Trait 之一:
- Fn
- FnMut
- FnOnce
use std::thread;
use std::time::Duration;
struct Cacher<T>
where
T: Fn(u32) -> u32,
{
calculation: T,
value: Option<u32>,
}
impl<T> Cacher<T>
where
T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = https://www.cnblogs.com/QiaoPengjun/archive/2023/04/08/Some(v);
v
}
}
}
}
fn main() {
let simulated_user_specified_value = 10;
let simulated_random_number = 7;
generate_workout(simulated_user_specified_value, simulated_random_number);
}
fn generate_workout(intensity: u32, random_number: u32) {
let mut expensive_closure = Cacher::new(|num| {
println!("calculating slowly ...");
thread::sleep(Duration::from_secs(2));
num
});
if intensity < 25 {
println!("Today, do {} pushups!", expensive_closure.value(intensity));
println!("Next, do {} situps!", expensive_closure.value(intensity));
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!("Today, run for {} minutes!", expensive_closure.value(intensity));
}
}
}
使用快取器 (Cacher)實作的限制
- Cacher 實體假定針對不同的引數 arg,Value 方法總會得到同樣的值,
- 可以使用 HashMap 代替單個值:
- Key:arg 引數
- Value:執行閉包的結果
- 可以使用 HashMap 代替單個值:
use std::thread;
use std::time::Duration;
struct Cacher<T>
where
T: Fn(u32) -> u32,
{
calculation: T,
value: Option<u32>,
}
impl<T> Cacher<T>
where
T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = https://www.cnblogs.com/QiaoPengjun/archive/2023/04/08/Some(v);
v
}
}
}
}
fn main() {
let simulated_user_specified_value = 10;
let simulated_random_number = 7;
generate_workout(simulated_user_specified_value, simulated_random_number);
}
fn generate_workout(intensity: u32, random_number: u32) {
let mut expensive_closure = Cacher::new(|num| {
println!("calculating slowly ...");
thread::sleep(Duration::from_secs(2));
num
});
if intensity < 25 {
println!("Today, do {} pushups!", expensive_closure.value(intensity));
println!("Next, do {} situps!", expensive_closure.value(intensity));
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
expensive_closure.value(intensity)
);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn call_with_different_values() {
let mut c = super::Cacher::new(|a| a);
let v1 = c.value(1);
let v2 = c.value(2);
assert_eq!(v2, 2);
}
}
- 只能接收一個u32型別的引數和 u32 型別的回傳值
四、閉包(4)- 使用閉包捕獲環境
閉包可以捕獲他們所在的環境
- 閉包可以訪問定義它的作用域內的變數,而普通函式則不能,
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
- 會產生記憶體開銷,
閉包從所在環境捕獲值的方式
- 與函式獲得引數的三種方式一樣:
- 取得所有權:FnOnce
- 可變借用:FnMut
- 不可變借用:Fn
- 創建閉包時,通過閉包對環境值的使用,Rust推斷出具體使用哪個 Trait:
- 所有的閉包都實作了 FnOnce
- 沒有移動捕獲變數的實作了 FnMut
- 無需可變訪問捕獲變數的閉包實作了 Fn
move 關鍵字
- 在引數串列前使用 move 關鍵字,可以強制閉包取得它所使用的環境值的所有權
- 當將閉包傳遞給新執行緒以移動資料使其歸新執行緒所有時,此技術最為有用,
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x); // 報錯
let y = vec![1, 2, 3];
assert!(equal_to_x(y))
}
最佳實踐
- 當指定 Fn trait bound 之一時,首先用 Fn,基于閉包體里的情況,如果需要 FnOnce 或 FnMut,編譯器會再告訴你,
五、迭代器(1)- Iterator trait 和 next 方法
什么是迭代器
- 迭代器模式:讀一系列項執行某些任務
- 迭代器負責:
- 遍歷每個項
- 確定序列(遍歷)何時完成
- Rust的迭代器:
- 懶惰的:除非呼叫消費迭代器的方法,否則迭代器本身沒有任何效果,
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
pringln!("Got: {}", val);
}
}
Iterator trait
- 所有迭代器都實作了 Iterator trait
- Iterator trait 定義于標準庫,定義大致如下:
pub trait Iterator {
type item;
fn next(&mut self) -> Option<Self::Item>;
// methods with default implementations elided
}
-
Type Item 和 Self::Item 定義了與此該 Trait 關聯的型別,
- 實作 Iterator trait 需要你定義一個 Item 型別,它用于 next 方法的回傳型別(迭代器的回傳型別),
-
Iterator trait 僅要求實作一個方法:next
-
next:
- 每次回傳迭代器中的一項
- 回傳結果包裹在 Some 里
- 迭代結束,回傳 None
-
可直接在迭代器上呼叫 next 方法
#[cfg(test)]
mod tests {
#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
}
}
幾個迭代方法
- iter 方法:在不可變參考上創建迭代器
- into_iter 方法:創建的迭代器會獲得所有權
- iter_mut 方法:迭代可變的參考
六、迭代器(2)- 消耗/產生迭代器
消耗迭代器的方法
- 在標準庫中,Iterator trati 有一些帶默認實作的方法
- 其中有一些方法會呼叫 next 方法
- 實作 Iterator trati 時必須實作 next 方法的原因之一
- 呼叫next 的方法叫做”消耗型配接器“
- 因為呼叫它們會把迭代器消耗盡
- 例如:Sum方法(就會耗盡迭代器)
- 取得迭代器的所有權
- 通過反復呼叫 next,遍歷所有元素
- 每次迭代,把當前元素添加到一個總和里,迭代結束,回傳總和
#[cfg(test)]
mod tests {
#[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];
let v1_iter = v1,iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
}
}
產生其它迭代器的方法
- 定義在 Iterator trait 上的另外一些方法叫做 ”迭代器配接器“
- 把迭代器轉換為不同種類的迭代器
- 可以通過鏈式呼叫使用多個迭代器配接器來執行復雜的操作,這種呼叫可讀性較高,
- 例如:map
- 接收一個閉包,閉包作用于每個元素
- 產生一個新的迭代器
#[cfg(test)]
mod tests {
#[test]
fn iterator_sum() {
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
}
}
- Collect 方法:消耗型配接器,把結果收集到一個集合型別中,
七、迭代器(3)- 使用閉包捕獲環境
使用閉包捕獲環境
- filter 方法:
- 接收一個閉包
- 這個閉包在遍歷迭代器的每個元素時,回傳bool型別
- 如果閉包回傳 true:當前元素將會包含在 filter 產生的迭代器中
- 如果閉包回傳 false:當前元素將不會包含在 filter 產生的迭代器中
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|x| x.size == shoe_size).collect()
}
#[test]
fn filter_by_size() {
let shoes = vec![
Shoe {
size: 10,
style: String::from("sneaker"),
},
Shoe {
size: 13,
style: String::from("sandal"),
},
Shoe {
size: 10,
style: String::from("boot").
},
];
let in_my_size = shoes_in_my_size(shoes, 10);
assert_eq!(in_my_size, vec![
Shoe {
size: 10,
style: String::from("sneaker")
},
Shoe {
size: 10,
style: String::from("boot")
},
]);
}
八、迭代器(4)- 創建自定義迭代器
使用 Iterator trait 來創建自定義迭代器
- 實作 next 方法
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
#[test]
fn calling_next_directly() {
let mut counter = Counter::new();
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), Some(4));
assert_eq!(counter.next(), Some(5));
assert_eq!(counter.next(), None);
}
#[test]
fn using_other_iterator_trait_methods() {
let sum: u32 = Counter::new() // 1 2 3 4 5
.zip(Counter::new().skip(1)) // 2 3 4 5 None
.map(|(a, b)| a * b) // 2 6 12 20
.filter(|x| x % 3 == 0) // 6 12
.sum(); // 6 + 12 = 18
assert_eq!(18, sum);
}
九、使用迭代器和閉包改進I/O 專案(minigrep)
src/main.rs 檔案
use minigrep::Config;
use std::env;
use std::process;
fn main() {
let config = Config::new(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});
if let Err(e) = minigrep::run(config) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
src/lib.rs 檔案
use std::env;
use std::error::Error;
use std::fs;
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.filename)?;
let results = if config.case_sensitive {
search(&config.query, &contents)
} else {
search_case_insensitive(&config.query, &contents)
};
for line in results {
println!("line: {}", line);
}
// println!("With text:\n{}", contents);
// println!("query: {:?}", config.query);
Ok(())
}
pub struct Config {
pub query: String,
pub filename: String,
pub case_sensitive: bool,
}
impl Config {
pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
args.next();
let query = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};
let filename = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a file name"),
};
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {
query,
filename,
case_sensitive,
})
}
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
// let mut results = Vec::new();
// for line in contents.lines() {
// if line.contains(query) {
// results.push(line);
// }
// }
// results
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
// let mut results = Vec::new();
// let query = query.to_lowercase();
// for line in contents.lines() {
// if line.to_lowercase().contains(&query) {
// results.push(line);
// }
// }
// results
contents
.lines()
.filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
// fn one_result() {
// let query = "duct";
// let contents = "\
// Rust:
// safe, fast, productive.
// Pick three.";
// assert_eq!(vec!["safe, fast, productive."], search(query, contents))
// }
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents))
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fase, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
)
}
}
十、性能比較:- 回圈 VS 迭代器
一個測驗
- 把一本小說的內容放在一個 String 里面,搜索 “the”:
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
- 迭代器的版本更快一點!
零開銷抽象 Zero-Cost Abstraction
- 使用抽象時不會引入額外的運行時開銷,
音頻解碼器的例子
let buffer: &mut [i32];
let coefficients: [i64; 12];
let qlp_shift: i16;
for i in 12..buffer.len() {
let prediction = coefficients.iter().zip(&buffer[i - 12..i]).map(|(&c, &s)| c * s as i64).sum::<i64>() >> qlp_shift;
let delta = buffer[i];
buffer[i] = prediction as i32 + delta;
}
本文來自博客園,作者:QIAOPENGJUN,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17299340.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549502.html
標籤:其他
上一篇:【演算法資料結構專題】「延時佇列演算法」史上手把手教你針對層級時間輪(TimingWheel)實作延時佇列的開發實戰落地(下)
