【问题标题】:Using the same iterator multiple times in Rust在 Rust 中多次使用同一个迭代器
【发布时间】:2014-05-31 10:52:52
【问题描述】:

编者注:此代码示例来自 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;
}

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

【问题讨论】:

  • 对于它的价值,截至 2015 年 2 月,Peekable&lt;Chars&gt; 不是Copy,因此原始代码不会无限循环——它会引发编译错误,表明调用时所有权更改take_while。参见,例如:is.gd/1ppVX0

标签: rust


【解决方案1】:

正如您所确定的,每个take_while 调用都复制iter,因为take_while 采用self 并且Peekable 字符迭代器是Copy。 (仅在 Rust 1.0 之前为真——编辑器

您希望每次都修改迭代器,也就是说,take_while 对您的迭代器的 &amp;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"]

但是,我想这是不正确的。

我实际上建议将其编写为普通的 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
}

这种形式还允许使用更少的分配来执行此操作(也就是说,不需要Strings),因为每个返回值只是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&lt;String&gt; 类型提示和.to_string 调用。

甚至可以编写这样的迭代器,以避免分配Vec。类似于fn split&lt;'a&gt;(input: &amp;'a str) -&gt; Splits&lt;'a&gt; { /* construct a Splits */ } 的东西,其中Splits 是实现Iterator&lt;&amp;'a str&gt; 的结构。

【讨论】:

    【解决方案2】:

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

    这产生了一个具有两个循环级别的解决方案;另一种有效的方法是将其建模为仅深一层的状态机。问你是否不确定我的意思,我会示范一下。

    【讨论】:

    • 我想提一下,itertools crate 提供了一个 peeking_take_while 方法,它基本上完成了这个答案的功能,但是以一种通用的方式。
    猜你喜欢
    • 2020-10-26
    • 1970-01-01
    • 2014-03-11
    • 2010-11-25
    • 1970-01-01
    • 2021-11-08
    • 1970-01-01
    • 2018-06-10
    • 2021-10-05
    相关资源
    最近更新 更多