1. 正则表达式

  需要安装regex库和lazy_static库,可通过cargo add regexcargo add lazy_static 命令安装

[dependencies]
lazy_static = "1.4.0"
regex = "1.8.1"

1.1 验证并提取电子邮件登录信息

  验证电子邮件地址的格式是否正确,并提取 @ 符号之前的所有内容。

use lazy_static::lazy_static;

use regex::Regex;

fn extract_login(input: &str) -> Option<&str> {
lazy_static! {
static ref RE: Regex = Regex::new(
r"(?x)
^(?P<login>[^@\s]+)@
([[:word:]]+\.)*
[[:word:]]+$
"
)
.unwrap();
}
RE.captures(input)
.and_then(|cap| cap.name("login").map(|login| login.as_str()))
}

fn main() {
assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email"));
assert_eq!(
extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"),
Some(r"sdf+sdsfsd.as.sdsd")
);
assert_eq!(extract_login(r"唐三藏@tang.da"), Some(r"唐三藏"));
assert_eq!(extract_login(r"More@Than@One@at.com"), None);
assert_eq!(extract_login(r"Not an email@email"), None);
}
  • 运行cargo run校验

1.2 从文本提取标签元素唯一的列表

  展示从文本中提取、排序和去除标签列表的重复元素。这里给出的标签正则表达式只捕获以字母开头的拉丁语标签,完整的 twitter 标签正则表达式要复杂得多。

use lazy_static::lazy_static;

use regex::Regex;
use std::collections::HashSet;

fn extract_hashtags(text: &str) -> HashSet<&str> {
lazy_static! {
static ref HASHTAG_REGEX: Regex = Regex::new(r"\#[a-zA-Z][0-9a-zA-Z_]*").unwrap();
}
HASHTAG_REGEX
.find_iter(text)
.map(|mat| mat.as_str())
.collect()
}

fn main() {
let tweet = "Hey #world, I just got my new #dog, say hello to Till. #dog #forever #2 #_ ";
let tags = extract_hashtags(tweet);
assert!(tags.contains("#dog") && tags.contains("#forever") && tags.contains("#world"));
assert_eq!(tags.len(), 3);
}
  • 运行cargo run校验

1.3 从文本提取电话号码

  需要安装error-chain库,可通过cargo add error-chain 命令安装

[dependencies]
error-chain = "0.12.4"
regex = "1.8.1"

  使用 Regex::captures_iter 处理一个文本字符串,以捕获多个电话号码。这里的例子中是美国电话号码格式。

use error_chain::error_chain;

use regex::Regex;
use std::fmt;

error_chain! {
foreign_links {
Regex(regex::Error);
Io(std::io::Error);
}
}

struct PhoneNumber<'a> {
area: &'a str,
exchange: &'a str,
subscriber: &'a str,
}

impl<'a> fmt::Display for PhoneNumber<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "1 ({}) {}-{}", self.area, self.exchange, self.subscriber)
}
}

fn main() -> Result<()> {
let phone_text = "
+1 505 881 9292 (v) +1 505 778 2212 (c) +1 505 881 9297 (f)
(202) 991 9534
Alex 5553920011
1 (800) 233-2010
1.299.339.1020";

let re = Regex::new(
r#"(?x)
(?:\+?1)? # 国家代码,可选项
[\s\.]?
(([2-9]\d{2})|\(([2-9]\d{2})\)) # 地区代码
[\s\.\-]?
([2-9]\d{2}) # 交换代码
[\s\.\-]?
(\d{4}) # 用户号码"#,
)?;

let phone_numbers = re.captures_iter(phone_text).filter_map(|cap| {
let groups = (cap.get(2).or(cap.get(3)), cap.get(4), cap.get(5));
match groups {
(Some(area), Some(ext), Some(sub)) => Some(PhoneNumber {
area: area.as_str(),
exchange: ext.as_str(),
subscriber: sub.as_str(),
}),
_ => None,
}
});

assert_eq!(
phone_numbers.map(|m| m.to_string()).collect::<Vec<_>>(),
vec![
"1 (505) 881-9292",
"1 (505) 778-2212",
"1 (505) 881-9297",
"1 (202) 991-9534",
"1 (555) 392-0011",
"1 (800) 233-2010",
"1 (299) 339-1020",
]
);

Ok(())
}
  • 运行cargo run校验

1.4 通过匹配多个正则表达式来筛选日志文件

  读取名为 application.log(可以根据以下输出的内容自建) 的文件,并且只输出包含下列内容的行:“version X.X.X”、端口为 443 的 IP 地址(如 “192.168.0.1:443”)、特定警告。正则表达集构造器 regex::RegexSetBuilder 构建了正则表达式集 regex::RegexSet。由于反斜杠在正则表达式中非常常见,因此使用原始字符串字面量可以使它们更具可读性。

use error_chain::error_chain;

use std::fs::File;
use std::io::{BufReader, BufRead};
use regex::RegexSetBuilder;

error_chain! {
foreign_links {
Io(std::io::Error);
Regex(regex::Error);
}
}

fn main() -> Result<()> {
let log_path = "application.log";
let buffered = BufReader::new(File::open(log_path)?);

let set = RegexSetBuilder::new(&[
r#"version "\d\.\d\.\d""#,
r#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:443"#,
r#"warning.*timeout expired"#,
]).case_insensitive(true)
.build()?;

buffered
.lines()
.filter_map(|line| line.ok())
.filter(|line| set.is_match(line.as_str()))
.for_each(|x| println!("{}", x));

Ok(())
}
  • 运行cargo run输出:
fasfsafad version 1.2.1 afsadfwew 163.211.115.56:443 asfasdfsfsdfas warning
fasfsafad version 1.2.2 afsadfwew 63.211.115.56:443 asfasdfsfsdfas
fasfsafad version 1.2.3 afsadfwew 13.211.115.56:443 asfasdfsfsdfas timeout expired

1.5 文本模式替换

  将所有出现的国际标准 ISO 8601 日期模式 YYYY-MM-DD 替换为美式英语及中式日期模式。例如: 2013-01-15 替换为 01/15/2013Regex::replace_all 方法将替换整个正则表示匹配的所有内容。&str 实现了 Replacer trait,它允许类似 $abcde 的变量引用相应的搜索匹配模式(search regex)中的命名捕获组 (?P<abcde>REGEX)。有关示例和转义的详细信息,请参阅替换字符串语法。

use lazy_static::lazy_static;

use regex::Regex;
use std::borrow::Cow;

fn reformat_dates_us(before: &str) -> Cow<str> {
lazy_static! {
static ref ISO8601_DATE_REGEX: Regex =
Regex::new(r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})").unwrap();
}
ISO8601_DATE_REGEX.replace_all(before, "$m/$d/$y")
}

fn reformat_dates_cn(before: &str) -> Cow<str> {
lazy_static! {
static ref ISO8601_DATE_REGEX: Regex =
Regex::new(r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})").unwrap();
}
ISO8601_DATE_REGEX.replace_all(before, "$y年$m月$d日")
}

fn main() {
let before = "2012-03-14, 2013-01-15 and 2014-07-05";
let after_us = reformat_dates_us(before);
assert_eq!(after_us, "03/14/2012, 01/15/2013 and 07/05/2014");
let after_cn = reformat_dates_cn(before);
assert_eq!(
after_cn,
"2012年03月14日, 2013年01月15日 and 2014年07月05日"
);
}
  • 运行cargo run校验

2. 字符串解析

2.1 收集 Unicode 字符

  需要安装unicode-segmentation库,可通过cargo add unicode-segmentation 命令安装

[dependencies]
unicode-segmentation = "1.10.1"

  使用 unicode-segmentation crate 中的 UnicodeSegmentation::graphemes 函数,从 UTF-8 字符串中收集个别的 Unicode 字符。

use unicode_segmentation::UnicodeSegmentation;

fn main() {
let name = "José Guimarães 大胃王\r\n";
let graphemes = UnicodeSegmentation::graphemes(name, true).collect::<Vec<&str>>();
println!("{:?}", graphemes);
assert_eq!(graphemes[3], "é");
}
  • 运行cargo run校验

2.2 自定义结构体并实现 FromStr trait

  实例中,创建一个自定义结构体 RGB 并实现 FromStr trait,以将提供的颜色十六进制代码转换为其 RGB 颜色代码。

use std::str::FromStr;

#[derive(Debug, PartialEq)]
struct RGB {
r: u8,
g: u8,
b: u8,
}

impl FromStr for RGB {
type Err = std::num::ParseIntError;

// 解析格式为 '#rRgGbB..' 的颜色十六进制代码
// 将其转换为 'RGB' 实例
fn from_str(hex_code: &str) -> Result<Self, Self::Err> {
// u8::from_str_radix(src: &str, radix: u32)
// 将给定的字符串切片转换为 u8
let r: u8 = u8::from_str_radix(&hex_code[1..3], 16)?;
let g: u8 = u8::from_str_radix(&hex_code[3..5], 16)?;
let b: u8 = u8::from_str_radix(&hex_code[5..7], 16)?;

Ok(RGB { r, g, b })
}
}

fn main() {
let code: &str = &r"#fa7268";
match RGB::from_str(code) {
Ok(rgb) => {
println!(r"RGB颜色代码是: R: {} G: {} B: {}", rgb.r, rgb.g, rgb.b);
}
Err(_) => {
println!("{} 不是有效的颜色十六进制代码!", code);
}
}

// 测试 from_str 函数执行是否符合预期
assert_eq!(
RGB::from_str(&r"#fa7268").unwrap(),
RGB {
r: 250,
g: 114,
b: 104
}
);
}
  • 运行cargo run输出及校验
RGB颜色代码是: R: 250 G: 114 B: 104