Rust从入门到实战系列一百三十六:实现 search_case_insensitive 函数

search_case_insensitive 函数,将与 search 函数基本相同。唯一的区别是它会将
query 变量和每一 line 都变为小写,这样不管输入参数是大写还是小写,在检查该行是否包含查询字符
串时都会是小写。

use std::error::Error;

use std::fs;

pub struct Config {

pub query: String,

pub filename: String,

}

impl Config {

pub fn new(args: &[String]) -> Result<Config, &'static str> {

if args.len() < 3 {

return Err(“not enough arguments”);

}

let query = args[1].clone();

let filename = args[2].clone();

Ok(Config { query, filename })

}

}

pub fn run(config: Config) -> Result<(), Box> {

let contents = fs::read_to_string(config.filename)?;

for line in search(&config.query, &contents) {

println!(“{}”, line);

}

Ok(())

}

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

}

pub fn search_case_insensitive<'a>(
query: &str,
contents: &'a str,
) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}
results
}

#[cfg(test)]

mod tests {

use super:😗;

#[test]

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, fast, productive.

Pick three.

Trust me.";

assert_eq!(

vec![“Rust:”, “Trust me.”],

search_case_insensitive(query, contents)

);

}

}

示例 12-21:定义 search_case_insensitive 函数,它在比较查询和每一行之前将他们都转换为小写
首先我们将 query 字符串转换为小写,并将其覆盖到同名的变量中。对查询字符串调用 to_lowercase
是必需的,这样不管用户的查询是 ”rust”、”RUST”、”Rust” 或者 ”rUsT”,我们都将其当作 ”rust” 处理并
对大小写不敏感。虽然 to_lowercase 可以处理基本的 Unicode,但它不是 100% 准确。如果编写真实
的程序的话,我们还需多做一些工作,不过这一部分是关于环境变量而不是 Unicode 的,所以这样就够了。
注意 query 现在是一个 String 而不是字符串 slice,因为调用 to_lowercase 是在创建新数据,而不是引
用现有数据。如果查询字符串是 ”rUsT”,这个字符串 slice 并不包含可供我们使用的小写的 u 或 t,所以
必需分配一个包含 ”rust” 的新 String。现在当我们将 query 作为一个参数传递给 contains 方法时,需
要增加一个 & 因为 contains 的签名被定义为获取一个字符串 slice。
接下来在检查每个 line 是否包含 search 之前增加了一个 to_lowercase 调用将他们都变为小写。现在我
们将 line 和 query 都转换成了小写,这样就可以不管查询的大小写进行匹配了。
让我们看看这个实现能否通过测试:
$ cargo test
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished test [unoptimized + debuginfo] target(s) in 1.33s
Running unittests (target/debug/deps/minigrep-9cd200e5fac0fc94)
running 2 tests
test tests::case_insensitive … ok
test tests::case_sensitive … ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests (target/debug/deps/minigrep-9cd200e5fac0fc94)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests minigrep
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
好的!现在,让我们在 run 函数中实际调用新 search_case_insensitive 函数。首先,我们将在 Config
结构体中增加一个配置项来切换大小写敏感和大小写不敏感搜索。增加这些字段会导致编译错误,因为
我们还没有在任何地方初始化这些字段:
文件名: src∕lib.rs

use std::error::Error;

use std::fs;

pub struct Config {
pub query: String,
pub filename: String,
pub case_sensitive: bool,
}

impl Config {

pub fn new(args: &[String]) -> Result<Config, &'static str> {

if args.len() < 3 {

return Err(“not enough arguments”);

}

let query = args[1].clone();

let filename = args[2].clone();

Ok(Config { query, filename })

}

}

pub fn run(config: Config) -> Result<(), Box> {

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);

}

Ok(())

}

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

}

pub fn search_case_insensitive<'a>(

query: &str,

contents: &'a str,

) -> Vec<&'a str> {

let query = query.to_lowercase();

let mut results = Vec::new();

for line in contents.lines() {

if line.to_lowercase().contains(&query) {

results.push(line);

}

}

results

}

#[cfg(test)]

mod tests {

use super:😗;

#[test]

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, fast, productive.

Pick three.

Trust me.";

assert_eq!(

vec![“Rust:”, “Trust me.”],

search_case_insensitive(query, contents)

);

}

}

这里增加了 case_sensitive 字符来存放一个布尔值。接着我们需要 run 函数检查 case_sensitive 字段的
值并使用它来决定是否调用 search 函数或 search_case_insensitive 函数,如示例 12-22 所示。注意这
还不能编译:
文件名: src∕lib.rs

use std::error::Error;

use std::fs;

pub struct Config {

pub query: String,

pub filename: String,

pub case_sensitive: bool,

}

impl Config {

pub fn new(args: &[String]) -> Result<Config, &'static str> {

if args.len() < 3 {

return Err(“not enough arguments”);

}

let query = args[1].clone();

let filename = args[2].clone();

Ok(Config { query, filename })

}

}

pub fn run(config: Config) -> Result<(), Box> {
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);
}
Ok(())
}

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

}

pub fn search_case_insensitive<'a>(

query: &str,

contents: &'a str,

) -> Vec<&'a str> {

let query = query.to_lowercase();

let mut results = Vec::new();

for line in contents.lines() {

if line.to_lowercase().contains(&query) {

results.push(line);

}

}

results

}

#[cfg(test)]

mod tests {

use super:😗;

#[test]

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, fast, productive.

Pick three.

Trust me.";

assert_eq!(

vec![“Rust:”, “Trust me.”],

search_case_insensitive(query, contents)

);

}

}

示例 12-22:根据 config.case_sensitive 的值调用 search 或 search_case_insensitive
最后需要实际检查环境变量。处理环境变量的函数位于标准库的 env 模块中,所以我们需要在 src∕lib.rs
的开头增加一个 use std::env; 行将这个模块引入作用域中。接着在 Config::new 中使用 env 模块的 var方法来检查一个叫做 CASE_INSENSITIVE 的环境变量,如示例 12-23 所示:
文件名: src∕lib.rs
use std::env;
// --snip–

use std::error::Error;

use std::fs;

pub struct Config {

pub query: String,

pub filename: String,

pub case_sensitive: bool,

}

impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err(“not enough arguments”);
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var(“CASE_INSENSITIVE”).is_err();
Ok(Config {
query,
filename,
case_sensitive,
})
}
}

pub fn run(config: Config) -> Result<(), Box> {

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);

}

Ok(())

}

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);

值的 Ok 成员,并在环境变量未被设置时返回 Err 成员。
我们使用 Result 的 is_err 方法来检查其是否是一个 error(也就是环境变量未被设置的情况),这也就
意味着我们 需要进行一个大小写敏感搜索。如果CASE_INSENSITIVE 环境变量被设置为任何值,is_err
会返回 false 并将进行大小写不敏感搜索。我们并不关心环境变量所设置的 值,只关心它是否被设置了,
所以检查 is_err 而不是 unwrap、expect 或任何我们已经见过的 Result 的方法。
我们将变量 case_sensitive 的值传递给 Config 实例,这样 run 函数可以读取其值并决定是否调用
search 或者示例 12-22 中实现的 search_case_insensitive。
让我们试一试吧!首先不设置环境变量并使用查询 to 运行程序,这应该会匹配任何全小写的单词 ”to”
的行:
$ cargo run to poem.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running target/debug/minigrep to poem.txt
Are you nobody, too?
How dreary to be somebody!
看起来程序仍然能够工作!现在将 CASE_INSENSITIVE 设置为 1 并仍使用相同的查询 to。
如果你使用 PowerShell,则需要用两个命令来分别设置环境变量并运行程序:
PS> $Env:CASE_INSENSITIVE=1; cargo run to poem.txt
这回应该得到包含可能有大写字母的 ”to” 的行:
$ CASE_INSENSITIVE=1 cargo run to poem.txt
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running target/debug/minigrep to poem.txt
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!
好极了,我们也得到了包含 ”To” 的行!现在 minigrep 程序可以通过环境变量控制进行大小写不敏感搜
索了。现在你知道了如何管理由命令行参数或环境变量设置的选项了!
一些程序允许对相同配置同时使用参数 和环境变量。在这种情况下,程序来决定参数和环境变量的优先
级。作为一个留给你的测试,尝试通过一个命令行参数或一个环境变量来控制大小写不敏感搜索。并在
运行程序时遇到矛盾值时决定命令行参数和环境变量的优先级。
std:: env 模块还包含了更多处理环境变量的实用功能;请查看官方文档来了解其可用的功能。

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值