1

I wanted to use Streams.intRange(int start, int end, int step) to achieve reverse ordered stream. However it seems that java.util.Streams class is no longer available (however it is still in rt.jar in standard library). Is this method in some other class or replaced with something else?

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
mtadmk
  • 63
  • 1
  • 7
  • 2
    This link might be helpful to you in terms of what you want: `http://stackoverflow.com/questions/24010109/java-8-stream-reverse-order` – Kartic Sep 15 '15 at 13:01

3 Answers3

4

Both solutions proposed so far don't respect parallelization. The spliterator proposed by @fge does not parallelize at all. The iterate-based stream proposed by @RealSkeptic will use buffered parallelization (some numbers will be loaded into the intermediate array and handed over to the another thread) which is not always effective.

There's quite simple alternative solution which provides normal parallelization (here end is exclusive):

public static IntStream intRange(int start, int end, int step ) {
    int limit = (end-start+step-(step>>31|1))/step;
    return IntStream.range(0, limit).map(x -> x * step + start);
}

Or if you want to take into account really weird inputs like intRange(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE):

public static IntStream intRange(int startInclusive, int endExclusive, int step) {
    if(step == 0)
        throw new IllegalArgumentException("step = 0");
    if(step == 1)
        return IntStream.range(startInclusive, endExclusive);
    if(step == -1) {
        // Handled specially as number of elements can exceed Integer.MAX_VALUE
        int sum = endExclusive+startInclusive;
        return IntStream.range(endExclusive, startInclusive).map(x -> sum - x);
    }
    if((endExclusive > startInclusive ^ step > 0) || endExclusive == startInclusive)
        return IntStream.empty();
    int limit = (endExclusive-startInclusive)*Integer.signum(step)-1;
    limit = Integer.divideUnsigned(limit, Math.abs(step));
    return IntStream.rangeClosed(0, limit).map(x -> x * step + startInclusive);
}
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • 2
    The rounding error is too high, e.g. `intRange(0, 9, 2).forEach(System.out::println);` won’t print the `8`. Besides that, this solution also is the only one having a predictable size, which is used internally even inside sequential operations, e.g. `toArray()`. – Holger Sep 16 '15 at 13:42
  • 2
    It’s not easy to come up with a `limit` calculation which works for both directions. The “simplest” I have so far is `limit = (end-start+step-(step>>31|1))/step`. – Holger Sep 16 '15 at 13:47
  • @Holger, thank you. My bad, I did not test the limit thoroughly. Copied your formula now – Tagir Valeev Sep 16 '15 at 14:48
  • 2
    @Holger, actually the problem is more tricky when it comes to inputs like `intRange(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE)`. I edited the answer to add my best effort on solution which accounts all the corner cases. Not very pretty, but all my tests are passed so far. I don't want to use the `long` type as the similar `longRange` method should also be implemented in the same way. – Tagir Valeev Sep 16 '15 at 17:54
1

There is indeed no such method anymore in the JDK; the next closest you could get is IntStream.range() but that will only step one by one.

One solution here would be to implement your own Spliterator.OfInt; for instance something like this (VERY CRUDE; can be improved!):

public final class StepRange
    implements Spliterator.OfInt
{
    private final int start;
    private final int end;
    private final int step;

    private int currentValue;

    public StepRange(final int start, final int end, final int step)
    {
        this.start = start;
        this.end = end;
        this.step = step;
        currentValue = start;
    }

    @Override
    public OfInt trySplit()
    {
        return null;
    }

    @Override
    public long estimateSize()
    {
        return Long.MAX_VALUE;
    }

    @Override
    public int characteristics()
    {
        return Spliterator.IMMUTABLE | Spliterator.DISTINCT;
    }

    @Override
    public boolean tryAdvance(final IntConsumer action)
    {
        final int nextValue = currentValue + step;
        if (nextValue > end)
            return false;
        action.accept(currentValue);
        currentValue = nextValue;
        return true;
    }
}

You would then use StreamSupport.intStream() to generate your stream from an instance of the class above.

fge
  • 119,121
  • 33
  • 254
  • 329
0

You can create it based on an infinite stream:

public static IntStream intRange(int start, int end, int step ) {
    if ( step == 0 ) {
        throw new IllegalArgumentException("Cannot iterate with a step of zero");
    }
    final int limit = (end - start + step) / step;
    if ( limit < 0 ) {
        return IntStream.empty();
    }
    return IntStream.iterate(start, x -> x + step )
                    .limit( limit );
}

If the range doesn't make sense (e.g. range from 7 to 2 in steps of 1) you get an empty stream.

The limit is inclusive. That is, range from 2 to 8 in steps of 2 will give you 2,4,6,8. If you want it to be exclusive (without the 8), change the limit to:

final int limit = (end - start) / step;

Possible usage:

intRange(8 ,2, -2).forEach(System.out::println);

Output:

8
6
4
2
RealSkeptic
  • 33,993
  • 7
  • 53
  • 79