Isaac
Isaac

Reputation: 3616

Creating an iterator that either steps upwards or downwards

I'd ideally like to have something like the following:

iter = if go_up {
  (min .. limit)
} else {
  (limit .. max).rev()
};

to create an iterator that either counts up or down to some limit, depending on the situation. However, because Range and Rev are different types, I can't do this. I can use the step_by feature, but because my limits are an unsigned data-type, I then also have to cast everything. The best I have so far is:

#![feature(step_by)]
iter = if go_up {
  (min as i64 .. limit as i64).step_by(1)
} else {
  (limit as i64 .. max as i64).step_by(-1)
};

but this requires both unstable features, and shoehorning my types. It seems like there should be a neater way to do this; does anyone know one?

Upvotes: 1

Views: 164

Answers (2)

Veedrac
Veedrac

Reputation: 60147

Personally, your solution

iter = if go_up {
  (min as i64 .. limit as i64).step_by(1)
} else {
  (limit as i64 .. max as i64).step_by(-1)
};

is a better option than Shepmaster's first example, since it's more complete (eg. there's a size_hint), it's more likely to be correct by virtue of being a standard tool and it's faster to write.

It's true that this is unstable, but there's nothing stopping you from just copying the source in the meantime. That gives you a nice upgrade path for when this eventually gets stabilized.

The enum wrapper technique is great in more complex cases, though, but in this case I'd be tempted to KISS.

Upvotes: 2

Shepmaster
Shepmaster

Reputation: 431011

The direct solution is to simply create an iterator that can either count upwards or downwards. Use an enum to choose between the types:

use std::ops::Range;
use std::iter::Rev;

enum Foo {
    Upwards(Range<u8>),
    Downwards(Rev<Range<u8>>),
}

impl Foo {
    fn new(min: u8, limit: u8, max: u8, go_up: bool) -> Foo {
        if go_up {
          Foo::Upwards(min..limit)
        } else {
          Foo::Downwards((limit..max).rev())
        }
    }
}

impl Iterator for Foo {
    type Item = u8;

    fn next(&mut self) -> Option<Self::Item> {
        match *self {
            Foo::Upwards(ref mut i) => i.next(),
            Foo::Downwards(ref mut i) => i.next(),
        }
    }
}

fn main() {
    for i in Foo::new(1, 5, 10, true) { 
        println!("{}", i);
    }

    for i in Foo::new(1, 5, 10, false) { 
        println!("{}", i);
    }
}

Another pragmatic solution that introduces a little bit of indirection is to Box the iterators:

fn thing(min: u8, limit: u8, max: u8, go_up: bool) -> Box<Iterator<Item = u8>> {
    if go_up {
      Box::new(min..limit)
    } else {
      Box::new((limit..max).rev())
    }
}

fn main() {
    for i in thing(1, 5, 10, true) { 
        println!("{}", i);
    }

    for i in thing(1, 5, 10, false) { 
        println!("{}", i);
    }
}

Upvotes: 4

Related Questions