编者注:此代码示例来自1.0之前的Rust版本,当时许多迭代器实现了
Copy
。此代码的更新版本会产生不同的错误,但答案仍包含有价值的信息。
我试图编写一个函数来将一个字符串拆分成一堆字母和数字;例如,"test123test"
会变为[ "test", "123", "test" ]
。这是我到目前为止的尝试:
pub fn split(input: &str) -> Vec<String> {
let mut bits: Vec<String> = vec![];
let mut iter = input.chars().peekable();
loop {
match iter.peek() {
None => return bits,
Some(c) => if c.is_digit() {
bits.push(iter.take_while(|c| c.is_digit()).collect());
} else {
bits.push(iter.take_while(|c| !c.is_digit()).collect());
}
}
}
return bits;
}
然而,这不起作用,永远循环。似乎每次调用iter
时都会使用take_while
的克隆,一次又一次地从相同的位置开始。我希望每次使用相同的iter
,在所有each_time
上推进相同的迭代器。这可能吗?
答案 0 :(得分:13)
如您所述,每个take_while
来电均重复iter
,因为take_while
需要self
而Peekable
字符迭代器为Copy
。 (仅在Rust 1.0之前为真 - 编辑)
您希望每次都修改迭代器,即take_while
在&mut
上对迭代器进行操作。这正是.by_ref
适配器的用途:
pub fn split(input: &str) -> Vec<String> {
let mut bits: Vec<String> = vec![];
let mut iter = input.chars().peekable();
loop {
match iter.peek().map(|c| *c) {
None => return bits,
Some(c) => if c.is_digit(10) {
bits.push(iter.by_ref().take_while(|c| c.is_digit(10)).collect());
} else {
bits.push(iter.by_ref().take_while(|c| !c.is_digit(10)).collect());
},
}
}
}
fn main() {
println!("{:?}", split("123abc456def"))
}
打印
["123", "bc", "56", "ef"]
但是,我想这不正确。
我实际上建议使用char_indices
迭代器将其写为普通for
循环:
pub fn split(input: &str) -> Vec<String> {
let mut bits: Vec<String> = vec![];
if input.is_empty() {
return bits;
}
let mut is_digit = input.chars().next().unwrap().is_digit(10);
let mut start = 0;
for (i, c) in input.char_indices() {
let this_is_digit = c.is_digit(10);
if is_digit != this_is_digit {
bits.push(input[start..i].to_string());
is_digit = this_is_digit;
start = i;
}
}
bits.push(input[start..].to_string());
bits
}
此表单还允许使用更少的分配(即不需要String
)来执行此操作,因为每个返回的值只是input
的一个切片,我们可以使用一生都要陈述:
pub fn split<'a>(input: &'a str) -> Vec<&'a str> {
let mut bits = vec![];
if input.is_empty() {
return bits;
}
let mut is_digit = input.chars().next().unwrap().is_digit(10);
let mut start = 0;
for (i, c) in input.char_indices() {
let this_is_digit = c.is_digit(10);
if is_digit != this_is_digit {
bits.push(&input[start..i]);
is_digit = this_is_digit;
start = i;
}
}
bits.push(&input[start..]);
bits
}
所有更改都是类型签名,删除了Vec<String>
类型提示和.to_string
来电。
甚至可以编写这样的迭代器,以避免分配Vec
。像fn split<'a>(input: &'a str) -> Splits<'a> { /* construct a Splits */ }
这样的地方Splits
是一个实现Iterator<&'a str>
的结构。
答案 1 :(得分:3)
take_while
按值获取self
:它使用迭代器。在Rust 1.0之前,不幸的是,它也被隐式复制,导致你正在观察的令人惊讶的行为。
由于这些原因,您无法将take_while
用于您想要的内容。您需要手动展开take_while
次调用。
以下是处理此问题的许多可能方法之一:
pub fn split(input: &str) -> Vec<String> {
let mut bits: Vec<String> = vec![];
let mut iter = input.chars().peekable();
loop {
let seeking_digits = match iter.peek() {
None => return bits,
Some(c) => c.is_digit(10),
};
if seeking_digits {
bits.push(take_while(&mut iter, |c| c.is_digit(10)));
} else {
bits.push(take_while(&mut iter, |c| !c.is_digit(10)));
}
}
}
fn take_while<I, F>(iter: &mut std::iter::Peekable<I>, predicate: F) -> String
where
I: Iterator<Item = char>,
F: Fn(&char) -> bool,
{
let mut out = String::new();
loop {
match iter.peek() {
Some(c) if predicate(c) => out.push(*c),
_ => return out,
}
let _ = iter.next();
}
}
fn main() {
println!("{:?}", split("test123test"));
}
这产生了具有两级循环的解决方案;另一种有效的方法是将其建模为只有一级深度的状态机。问你是否不确定我的意思,我会证明。