This recent question Print Consecutive numbers by comparing two parameters frustrated me because I could not find a convenient way in Java 8 to support the conditions that are required. I answered it in a somewhat cumbersome way for Java 8... (the non-stream part of my answer is "OK" in terms of usability).
To reiterate the problem (as I interpreted it):
print the values in the range from
a
tob
, whereb
may be less thana
. e.g. from1
to3
will print1, 2, 3
and from3
to1
will print3, 2, 1
.
What I really wanted was something like this:
IntSteam.loop(a, b, a < b ? ++ : --).foreach(System.out::println);
(yes, the above code makes no sense, treat it as pseudocode...).
Basically, stream from a value to another value, either incrementing, or decrementing the loop variable.
To make a real implementation of that, I figured the stream needs a seed, a terminal condition, and a stepping function. I implemented it like:
int a = 10; int b = 5; IntUnaryOperator op = a < b ? i -> i + 1 : i -> i - 1; ForIntStream.until(a, v -> v == b, op).forEach(System.out::println);
The semantics there are: start from a, go until a == b, and use the operator to change the value.
While I was doing that, I also implemented a similar loop, that, instead of running until a condition, runs while a condition is true:
int a = 10; int b = 5; IntUnaryOperator op = a < b ? i -> i + 1 : i -> i - 1; ForIntStream.of(a, v -> v != b, op).forEach(System.out::println);
Note that the difference there is that the 'of' stream terminates and does not include the terminating.
Here is the code that implements the above features. Any suggestions, or alternatives are welcome
import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.function.IntUnaryOperator;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
public class ForIntStream {
private static final class WhileIterator implements PrimitiveIterator.OfInt {
private int next;
private final IntPredicate proceed;
private final IntUnaryOperator step;
private boolean terminated;
public WhileIterator(int next, IntPredicate proceed, IntUnaryOperator step) {
this.next = next;
this.proceed = proceed;
this.step = step;
this.terminated = !proceed.test(next);
}
@Override
public void forEachRemaining(IntConsumer action) {
while (!terminated) {
action.accept(next);
next = step.applyAsInt(next);
terminated = !proceed.test(next);
}
}
@Override
public boolean hasNext() {
return !terminated;
}
@Override
public Integer next() {
return nextInt();
}
@Override
public int nextInt() {
if (terminated) {
throw new NoSuchElementException("Iterated beyond terminal condition.");
}
int ret = next;
next = step.applyAsInt(next);
terminated = !proceed.test(next);
return ret;
}
}
private static final class UntilIterator implements PrimitiveIterator.OfInt {
private int next;
private final IntPredicate until;
private final IntUnaryOperator step;
private boolean terminated;
public UntilIterator(int next, IntPredicate until, IntUnaryOperator step) {
this.next = next;
this.until = until;
this.step = step;
}
@Override
public void forEachRemaining(IntConsumer action) {
while (!terminated) {
action.accept(next);
if (until.test(next)) {
terminated = true;
} else {
next = step.applyAsInt(next);
}
}
}
@Override
public boolean hasNext() {
return !terminated;
}
@Override
public Integer next() {
return nextInt();
}
@Override
public int nextInt() {
if (terminated) {
throw new NoSuchElementException("Iterated beyond terminal condition.");
}
int ret = next;
terminated = !until.test(next);
next = step.applyAsInt(next);
return ret;
}
}
public static IntStream of(int seed, IntPredicate allow, IntUnaryOperator step) {
PrimitiveIterator.OfInt it = new WhileIterator(seed, allow, step);
return StreamSupport.intStream(Spliterators.spliterator(it, Long.MAX_VALUE, Spliterator.ORDERED), false);
}
public static IntStream until(int seed, IntPredicate terminator, IntUnaryOperator step) {
PrimitiveIterator.OfInt it = new UntilIterator(seed, terminator, step);
return StreamSupport.intStream(Spliterators.spliterator(it, Long.MAX_VALUE, Spliterator.ORDERED), false);
}
}
if you want to experiment with it, the following will give some hints:
public static void main(String[] args) {
ForIntStream.of(1, i -> i != 10, i -> i + 1).forEach(System.out::println);
ForIntStream.until(1, i -> i == 10, i -> i + 1).forEach(System.out::println);
int a = 10;
int b = 5;
IntUnaryOperator op = a < b
? i -> i + 1
: i -> i - 1;
ForIntStream.until(a, i -> i == b, op).forEach(System.out::println);
}
For another example of how this can be used... to print a collatz conjecture sequence (hailstone problem), you can stream like:
ForIntStream.until(10, i -> i == 1, i -> i % 2 == 0 ? i / 2 : (3 * i + 1) ) .forEach(System.out::println);