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

时间:2014-05-31 10:52:53

标签: 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;
}

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

2 个答案:

答案 0 :(得分:13)

如您所述,每个take_while来电均重复iter,因为take_while需要selfPeekable字符迭代器为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"));
}

这产生了具有两级循环的解决方案;另一种有效的方法是将其建模为只有一级深度的状态机。问你是否不确定我的意思,我会证明。