在Rust中多次使用相同的迭代器

Ben*_*n S 7 rust

编者注:这个代码示例来自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;
}
Run Code Online (Sandbox Code Playgroud)

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

huo*_*uon 14

正如您所指出的,每个take_while调用都是重复的iter,因为take_whiletake selfPeekablechars迭代器是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"))
}
Run Code Online (Sandbox Code Playgroud)

打印

["123", "bc", "56", "ef"]
Run Code Online (Sandbox Code Playgroud)

但是,我想这不正确.

我实际上建议for使用char_indices迭代器将其写为普通循环:

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
}
Run Code Online (Sandbox Code Playgroud)

这种形式也允许用更少的分配(也就是说,String不需要s)来做到这一点,因为每个返回的值只是一个切片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
}
Run Code Online (Sandbox Code Playgroud)

所有改变的是类型签名,删除Vec<String>类型提示和.to_string调用.

甚至可以编写这样的迭代器,以避免分配Vec.类似于fn split<'a>(input: &'a str) -> Splits<'a> { /* construct a Splits */ }哪里的东西Splits是一个实现的结构Iterator<&'a str>.


Chr*_*gan 5

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

\n\n

由于这些原因,您无法用于take_while您想要的用途。您将需要手动展开您的take_while调用。

\n\n

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

\n\n
pub fn split(input: &str) -> Vec<String> {\n    let mut bits: Vec<String> = vec![];\n    let mut iter = input.chars().peekable();\n    loop {\n        let seeking_digits = match iter.peek() {\n            None => return bits,\n            Some(c) => c.is_digit(10),\n        };\n        if seeking_digits {\n            bits.push(take_while(&mut iter, |c| c.is_digit(10)));\n        } else {\n            bits.push(take_while(&mut iter, |c| !c.is_digit(10)));\n        }\n    }\n}\n\nfn take_while<I, F>(iter: &mut std::iter::Peekable<I>, predicate: F) -> String\nwhere\n    I: Iterator<Item = char>,\n    F: Fn(&char) -> bool,\n{\n    let mut out = String::new();\n    loop {\n        match iter.peek() {\n            Some(c) if predicate(c) => out.push(*c),\n            _ => return out,\n        }\n        let _ = iter.next();\n    }\n}\n\nfn main() {\n    println!("{:?}", split("test123test"));\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这产生了具有两级循环的解决方案;另一种有效的方法是将其建模为仅一层深的状态机。询问您是否\xe2\x80\x99 不确定我的意思,我\xe2\x80\x99 将演示。

\n