For loop two variables, one incrementing and other decrementing

How would you write this for loop in Rust, please?

        for (int l = i + 1, h = points.length - 1; l < h; l++, h--) {
            int[] temp = points[l];
            points[l] = points[h];
            points[h] = temp;
        }

Do I need to use while or loop?

points[i+1..].reverse()

3 Likes

I mean, I can also come up with a for loop

    for (l, h) in (i + 1..)
        .zip((0..points.len()).rev())
        .take_while(|(l, h)| l < h)
    {
        points.swap(l, h)
    }

though most similar (looking) to the C++ code would be a while loop

    let (mut l, mut h) = (i + 1, points.len() - 1);
    while l < h {
        points.swap(l, h);
        l += 1;
        h -= 1;
    }

Note that indexing in Rust has some bounds-checking overhead compared to C++; so using a library method like reverse will be more similar to the C++ solution performance-wise, while the manual loop might be slightly slower.


All examples in the playground:

using reverse
using for and iterators
using while

4 Likes

To add one more alternative to the mix, for this kind of iteration pattern, you could split up the slice and iterate, reverse the right half iterator, and zip:

    let slice = &mut points[i + 1..];
    let (left_slice, right_slice) = slice.split_at_mut(slice.len() / 2);
    for (l, r) in left_slice.iter_mut().zip(right_slice.iter_mut().rev()) {
        std::mem::swap(l, r);
    }

playground

2 Likes

You should definitely use the slice reverse method for this. If I'm reading your iteration bounds right, it'd be points[(i + 1)..].reverse().

I went and optimized reverse last year (PR#90821), making sure that -- unlike the CS101 implementations -- it can optimize out all the bounds checks and that LLVM vectorizes it: https://rust.godbolt.org/z/16EaG1rT3.

8 Likes

Thank you!

For that specific case, reverse does the job and is pretty cool.

But what I really wanted to know was how people code that for loop where you initialize two variables and one is incrementing and the other is decrementing.

As usual in Rust, the answer is that you try not to deal with loops where you're manually decrementing or incrementing in the first place. If you look at your request and step back a bit, you have two things you're looping over -- so maybe you want zip -- and one goes in the other direction -- so maybe you want rev.

Combining those two observations, perhaps you'd write it something like this:

pub fn reverse_simple<T>(points: &mut [T]) {
    let half = points.len() / 2;
    let (front, back) = points.split_at_mut(half);
    std::iter::zip(front, back.into_iter().rev())
        .for_each(|(a, b)| std::mem::swap(a, b));
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=70f1f72f29b733903914524d1fc888a5

...which is what steffahn wrote above :slight_smile:

1 Like