12

This code:

play

fn main() {
    let text = "abcd";

    for char in text.chars() {
        if char == 'b' {
            // skip 2 chars
        }
        print!("{}", char);
    }
    // prints `abcd`, but I want `ad`
}

prints abcd, but I want to skip 2 chars if b was found, so that it prints ad. How do I do that?

I tried to put the iterator into a variable outside the loop and manipulate that iterator within the loop, but the Borrow Checker doesn't allow that.

Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89

2 Answers2

15

AFAIK you can't do that with a for loop. You will need to desugar it by hand:

let mut it = text.chars();
while let Some(char) = it.next() {
    if char == 'b' {
        it.nth(1); // nth(1) skips/consumes exactly 2 items
        continue;
    }
    print!("{}", char);
}

Playground

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Jmb
  • 18,893
  • 2
  • 28
  • 55
  • What If I want to skip, say 50 chars? Calling `.next` 50 times is neither nice nor fast. I tried modifying your example by replacing the second `.next` with `.skip` but that doesn't work. – Nurbol Alpysbayev Nov 26 '19 at 07:21
  • My bad! Your answer is (almost) correct! I've edited it slightly so that it better matches my case. – Nurbol Alpysbayev Nov 26 '19 at 07:45
  • Remember that "calling 50 times" applies only to people, especially when we are talking about iterator api, which is highly optimizable and LLVM tears apart repetetive tasks like these with ease. Unless you generated and looked at ASM yourself, you can't really claim that it happens "50 times", your cpu would increment-check 50 times either way, the call will most likely be elided. –  Nov 26 '19 at 08:52
  • @Sahsahae You are so wrong... See for example this: https://stackoverflow.com/questions/59044638/why-is-what-timers-show-so-counter-intuitive/59045092#59045092 where simple `.nth` shows how incredibly slow it may become. – Nurbol Alpysbayev Nov 26 '19 at 09:07
  • 1
    What you linked is completely different so it in no way makes me wrong, there you iterate from beginning N times, I'm talking about iter().next() on same iterator, besides, if you're writing a parser by hand, you probably should be using `.next()` manually in any case, so it completely removes the issue. –  Nov 26 '19 at 09:26
  • 1
    @NurbolAlpysbayev please avoid change code without permission specially if you forget to update playground link - - – Stargateur Nov 26 '19 at 18:53
3

If you want to keep an iterator style, you can use std::iter::successors (I've replaced the special char with '!' for being more readable:

fn my_iter<'a>(s: &'a str) -> impl Iterator<Item = char> + 'a {
    let mut it = s.chars();

    std::iter::successors(it.next(), move |c| {
        if *c == '!' {
            it.next().and_then(|_| it.next())
        } else {
            it.next()
        }
    })
    .filter(|c| *c != '!')
}

fn main() {
    assert!(my_iter("a!bc").eq("ac".chars()));
    assert!(my_iter("!abcd").eq("bcd".chars()));
    assert!(my_iter("abc!d").eq("abc".chars()));
    assert!(my_iter("abcd!").eq("abcd".chars()));
}
Boiethios
  • 38,438
  • 19
  • 134
  • 183