在Rust中多次使用同一迭代器

在Rust中多次使用同一迭代器,rust,Rust,编者按:此代码示例来自Rust 1.0之前的一个版本,当时许多迭代器实现了Copy。此代码的更新版本会产生不同的错误,但答案仍然包含有价值的信息 我正在尝试编写一个函数,将字符串拆分为一堆字母和数字;例如,“test123test”将变成[“test”、“123”、“test”]。以下是我迄今为止的尝试: pub fn split(input: &str) -> Vec<String> { let mut bits: Vec<String> = ve

编者按:此代码示例来自Rust 1.0之前的一个版本,当时许多迭代器实现了
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;
}
pub-fn-split(输入:&str)->Vec{
设mut位:Vec=Vec![];
让mut iter=input.chars().peek();
环路{
匹配iter.peek(){
无=>返回位,
一些(c)=>如果c.是数字(){
bits.push(iter.take_while(|c | c.is_digit()).collect());
}否则{
bits.push(iter.take_while(|c |!c.is_digit()).collect());
}
}
}
返回位;
}

然而,这不起作用,永远循环。似乎每次我调用
take_while
,它都在使用一个
iter
的克隆,从同一个位置一次又一次地开始。我希望它每次都使用相同的
iter
,在所有
每次
s上推进相同的迭代器。这可能吗?

take\u而
按值获取
self
:它消耗迭代器。不幸的是,在Rust 1.0之前,它还能够被隐式复制,从而导致您观察到的令人惊讶的行为

由于这些原因,您不能使用
take_while
来满足您的需求。您需要在调用时手动展开
take\u

以下是处理此问题的多种可能方法之一:

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"));
}
pub-fn-split(输入:&str)->Vec{
设mut位:Vec=Vec![];
让mut iter=input.chars().peek();
环路{
让搜索数字=匹配iter.peek(){
无=>返回位,
有些(c)=>c是数字(10),
};
如果正在查找\u个数字{
位推(take_while(&mut iter,| c | c.is_digital(10));
}否则{
位推送(取时(&mut iter,| c |!c.is_位(10));
}
}
}
fn take_while(iter:&mut std::iter::Peekable,谓词:F)->String
哪里
I:迭代器,
F:Fn(&char)->bool,
{
let mut out=String::new();
环路{
匹配iter.peek(){
一些(c)if谓词(c)=>out.push(*c),
_=>返回,
}
让u=iter.next();
}
}
fn main(){
println!(“{:?}”,拆分(“test123test”);
}

这将产生一个具有两级循环的解决方案;另一种有效的方法是将其建模为一个仅在一个层次上的状态机。如果你不确定我的意思,我将演示。

正如你所指出的,每个
take_while
调用都是重复的
iter
,因为
take_while
take
self
可查看的
字符迭代器是。(仅在Rust 1.0之前为真-编辑器

您希望每次都修改迭代器,也就是说,
take_while
在迭代器的
&mut
上操作。这正是适配器的用途:

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"))
}
然而,我认为这是不正确的

实际上,我建议使用迭代器将其作为正常的
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
}
更改的只是类型签名,删除了
Vec
类型提示和
.to_字符串
调用


人们甚至可以编写这样的迭代器,以避免分配
Vec
。类似于
fn split Splits

的东西,从2015年2月起,可查看的
不是
副本
,因此原始代码不会无限循环——它会抛出一个编译错误,指示在调用
take\u
时发生所有权更改。例如,请参见:
itertools
板条箱提供了一种
peek\u take\u while
方法,该方法基本上实现了该答案函数的功能,但采用了通用方式。
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
}