/*
* Copyright 2015, 2016 Tagir Valeev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package one.util.streamex;
import static java.util.Arrays.asList;
import static one.util.streamex.TestHelpers.*;
import static org.junit.Assert.*;
import java.io.StringReader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Collector;
import java.util.stream.Stream;
import org.junit.Test;
/**
* Tests/examples for StreamEx.headTail() method
*
* @author Tagir Valeev
*/
public class StreamExHeadTailTest {
// ///////////////////////
// JDK-8 intermediate ops
// Stream.skip (TSO)
static <T> StreamEx<T> skip(StreamEx<T> input, int n) {
return input.headTail((head, tail) -> n > 0 ? skip(tail, n - 1) : tail.prepend(head));
}
// Stream.limit (TSO)
static <T> StreamEx<T> limit(StreamEx<T> input, int n) {
return input.headTail((head, tail) -> n > 1 ? limit(tail, n - 1).prepend(head) : Stream.of(head));
}
// Stream.filter (TSO)
static <T> StreamEx<T> filter(StreamEx<T> input, Predicate<T> predicate) {
return input.<T> headTail((head, tail) -> predicate.test(head) ? filter(tail, predicate).prepend(head) : filter(tail, predicate));
}
// Stream.distinct
static <T> StreamEx<T> distinct(StreamEx<T> input) {
return input.headTail((head, tail) -> distinct(tail.filter(n -> !Objects.equals(head, n))).prepend(head));
}
// Stream.distinct (TSO)
static <T> StreamEx<T> distinctTSO(StreamEx<T> input) {
return distinctTSO(input, new HashSet<>());
}
private static <T> StreamEx<T> distinctTSO(StreamEx<T> input, Set<T> observed) {
return input.headTail((head, tail) -> observed.add(head) ? distinctTSO(tail, observed).prepend(head)
: distinctTSO(tail, observed));
}
// Stream.map (TSO)
static <T, R> StreamEx<R> map(StreamEx<T> input, Function<T, R> mapper) {
return input.headTail((head, tail) -> map(tail, mapper).prepend(mapper.apply(head)));
}
// Stream.peek (TSO)
static <T> StreamEx<T> peek(StreamEx<T> input, Consumer<T> consumer) {
return input.headTail((head, tail) -> {
consumer.accept(head);
return peek(tail, consumer).prepend(head);
});
}
// Stream.flatMap (TSO)
static <T, R> StreamEx<R> flatMap(StreamEx<T> input, Function<T, Stream<R>> mapper) {
return input.headTail((head, tail) -> flatMap(tail, mapper).prepend(mapper.apply(head)));
}
// Stream.sorted
static <T> StreamEx<T> sorted(StreamEx<T> input) {
return sorted(input, new ArrayList<>());
}
private static <T> StreamEx<T> sorted(StreamEx<T> input, List<T> cur) {
return input.headTail((head, tail) -> {
cur.add(head);
return sorted(tail, cur);
}, () -> {
cur.sort(null);
return cur.stream();
});
}
// ///////////////////////
// JDK-9 intermediate ops
// Stream.takeWhile (TSO)
static <T> StreamEx<T> takeWhile(StreamEx<T> input, Predicate<T> predicate) {
return input.headTail((head, tail) -> predicate.test(head) ? takeWhile(tail, predicate).prepend(head) : null);
}
// Stream.dropWhile (TSO)
static <T> StreamEx<T> dropWhile(StreamEx<T> input, Predicate<T> predicate) {
return input.headTail((head, tail) -> predicate.test(head) ? dropWhile(tail, predicate) : tail.prepend(head));
}
// ///////////////////////
// Other intermediate ops
// Filters the input stream of natural numbers (2, 3, 4...) leaving only
// prime numbers (lazy)
static StreamEx<Integer> sieve(StreamEx<Integer> input) {
return input.headTail((head, tail) -> sieve(tail.filter(n -> n % head != 0)).prepend(head));
}
// Creates a reversed stream
static <T> StreamEx<T> reverse(StreamEx<T> input) {
return input.headTail((head, tail) -> reverse(tail).append(head));
}
// Creates a reversed stream (TSO)
static <T> StreamEx<T> reverseTSO(StreamEx<T> input) {
return reverseTSO(input, new ArrayDeque<>());
}
private static <T> StreamEx<T> reverseTSO(StreamEx<T> input, Deque<T> buf) {
return input.headTail((head, tail) -> {
buf.addFirst(head);
return reverseTSO(tail, buf);
}, buf::stream);
}
// Creates a stream which consists of this stream and this reversed stream
static <T> StreamEx<T> mirror(StreamEx<T> input) {
return input.headTail((head, tail) -> mirror(tail).append(head).prepend(head));
}
// Creates a reversed stream (TSO)
static <T> StreamEx<T> mirrorTSO(StreamEx<T> input) {
return mirrorTSO(input, new ArrayDeque<>());
}
private static <T> StreamEx<T> mirrorTSO(StreamEx<T> input, Deque<T> buf) {
return input.headTail((head, tail) -> {
buf.addFirst(head);
return mirrorTSO(tail, buf).prepend(head);
}, buf::stream);
}
// Creates an infinitely cycled stream
static <T> StreamEx<T> cycle(StreamEx<T> input) {
return input.headTail((head, tail) -> cycle(tail.append(head)).prepend(head));
}
// Creates a n-times cycled stream (TSO)
static <T> StreamEx<T> cycleTSO(StreamEx<T> input, int n) {
return cycleTSO(input, n, new ArrayList<>());
}
private static <T> StreamEx<T> cycleTSO(StreamEx<T> input, int n, List<T> buf) {
return input.headTail((head, tail) -> {
buf.add(head);
return cycleTSO(tail, n, buf).prepend(head);
}, () -> IntStreamEx.range(n-1).flatMapToObj(i -> buf.stream()));
}
// mapFirst (TSO)
static <T> StreamEx<T> mapFirst(StreamEx<T> input, UnaryOperator<T> operator) {
return input.headTail((head, tail) -> tail.prepend(operator.apply(head)));
}
// Creates lazy scanLeft stream (TSO)
static <T> StreamEx<T> scanLeft(StreamEx<T> input, BinaryOperator<T> operator) {
return input.headTail((head, tail) -> scanLeft(tail.mapFirst(cur -> operator.apply(head, cur)), operator)
.prepend(head));
}
static <T> UnaryOperator<StreamEx<T>> scanLeft(BinaryOperator<T> operator) {
return stream -> scanLeft(stream, operator);
}
// takeWhileClosed: takeWhile+first element violating the predicate (TSO)
static <T> StreamEx<T> takeWhileClosed(StreamEx<T> input, Predicate<T> predicate) {
return input.headTail((head, tail) -> predicate.test(head) ? takeWhileClosed(tail, predicate).prepend(head)
: Stream.of(head));
}
// take every nth stream element (starting from the first) (TSO)
static <T> StreamEx<T> every(StreamEx<T> input, int n) {
return input.headTail((head, tail) -> every(skip(tail, n - 1), n).prepend(head));
}
static <T> StreamEx<T> every3(StreamEx<T> input) {
return input.headTail(
(first, tail1) -> tail1.<T>headTail(
(second, tail2) -> tail2.headTail(
(third, tail3) -> every3(tail3))).prepend(first));
}
// maps every couple of elements using given mapper (in non-sliding manner)
static <T, R> StreamEx<R> couples(StreamEx<T> input, BiFunction<T, T, R> mapper) {
return input.headTail((left, tail1) -> tail1.headTail((right, tail2) -> couples(tail2, mapper).prepend(
mapper.apply(left, right))));
}
// maps every pair of elements using given mapper (in sliding manner)
static <T, R> StreamEx<R> pairs(StreamEx<T> input, BiFunction<T, T, R> mapper) {
return input.headTail((left, tail1) -> tail1.headTail((right, tail2) -> pairs(tail2.prepend(right), mapper)
.prepend(mapper.apply(left, right))));
}
// Stream of fixed size batches (TSO)
static <T> StreamEx<List<T>> batches(StreamEx<T> input, int size) {
return batches(input, size, Collections.emptyList());
}
private static <T> StreamEx<List<T>> batches(StreamEx<T> input, int size, List<T> cur) {
return input.headTail((head, tail) -> cur.size() >= size
? batches(tail, size, asList(head)).prepend(cur)
: batches(tail, size, StreamEx.of(cur).append(head).toList()),
() -> Stream.of(cur));
}
// Stream of single element lists -> stream of sliding windows (TSO)
static <T> StreamEx<List<T>> sliding(StreamEx<List<T>> input, int size) {
return input.headTail((head, tail) -> head.size() == size ? sliding(
tail.mapFirst(next -> StreamEx.of(head.subList(1, size), next).toFlatList(l -> l)), size).prepend(head)
: sliding(tail.mapFirst(next -> StreamEx.of(head, next).toFlatList(l -> l)), size));
}
// Stream of dominators (removes immediately following elements which the
// current element dominates on) (TSO)
static <T> StreamEx<T> dominators(StreamEx<T> input, BiPredicate<T, T> isDominator) {
return input.headTail((head, tail) -> dominators(dropWhile(tail, e -> isDominator.test(head, e)), isDominator)
.prepend(head));
}
// Stream of mappings of (index, element) (TSO)
static <T, R> StreamEx<R> withIndices(StreamEx<T> input, BiFunction<Integer, T, R> mapper) {
return withIndices(input, 0, mapper);
}
private static <T, R> StreamEx<R> withIndices(StreamEx<T> input, int idx, BiFunction<Integer, T, R> mapper) {
return input.headTail((head, tail) -> withIndices(tail, idx + 1, mapper).prepend(mapper.apply(idx, head)));
}
// Appends the stream of Integer with their sum
static StreamEx<Integer> appendSum(StreamEx<Integer> input) {
return reduceLast(input.append(0), Integer::sum);
}
private static <T> StreamEx<T> reduceLast(StreamEx<T> input, BinaryOperator<T> op) {
return input.headTail((head, tail) -> reduceLast(tail, op).prepend(head).mapLast(last -> op.apply(head, last)));
}
// Append the result of reduction of given stream (TCO)
static <T> StreamEx<T> appendReduction(StreamEx<T> input, T identity, BinaryOperator<T> op) {
return input.headTail((head, tail) -> appendReduction(tail, op.apply(identity, head), op).prepend(head),
() -> Stream.of(identity));
}
// Returns stream filtered by f1, then concatenated with the original stream filtered by f2
static <T> StreamEx<T> twoFilters(StreamEx<T> input, Predicate<T> f1, Predicate<T> f2) {
return twoFilters(input, f1, f2, Stream.builder());
}
private static <T> StreamEx<T> twoFilters(StreamEx<T> input, Predicate<T> f1, Predicate<T> f2, Stream.Builder<T> buf) {
return input.headTail((head, tail) -> {
StreamEx<T> res = twoFilters(tail, f1, f2, buf);
if(f2.test(head)) buf.add(head);
return f1.test(head) ? res.prepend(head) : res;
}, buf::build);
}
static <T> StreamEx<T> skipLast(Stream<T> input, int n) {
return skipLast(StreamEx.of(input), n, new ArrayDeque<>());
}
private static <T> StreamEx<T> skipLast(StreamEx<T> input, int n, Deque<T> buf) {
return input.headTail((head, tail) -> {
buf.addLast(head);
return buf.size() > n ? skipLast(tail, n, buf).prepend(buf.pollFirst()) : skipLast(tail, n, buf);
});
}
static <T> UnaryOperator<StreamEx<T>> limitSorted(Comparator<T> comparator, int n) {
@SuppressWarnings("unchecked")
Collector<T, Object, List<T>> collector = (Collector<T, Object, List<T>>) MoreCollectors.least(comparator, n);
return stream -> collectAndStream(stream, collector.supplier().get(), collector.accumulator(), collector
.finisher().andThen(StreamEx::of));
}
private static <T, A, R> StreamEx<R> collectAndStream(StreamEx<T> input, A buf, BiConsumer<A, T> accumulator, Function<A, StreamEx<R>> finisher) {
return input.headTail((head, tail) -> {
accumulator.accept(buf, head);
return collectAndStream(tail, buf, accumulator, finisher);
}, () -> finisher.apply(buf));
}
static <T> UnaryOperator<StreamEx<T>> moveToEnd(Predicate<T> pred) {
return stream -> moveToEnd(stream, pred, Stream.builder());
}
private static <T> StreamEx<T> moveToEnd(StreamEx<T> input, Predicate<T> pred, Stream.Builder<T> buf) {
return input.headTail((head, tail) ->
pred.test(head) ? moveToEnd(tail, pred, buf.add(head)) : moveToEnd(tail, pred, buf).prepend(head),
buf::build);
}
static <T> UnaryOperator<StreamEx<T>> moveToEndOrMap(Predicate<T> pred, UnaryOperator<T> mapper) {
return stream -> moveToEndOrMap(stream, pred, mapper, Stream.builder());
}
private static <T> StreamEx<T> moveToEndOrMap(StreamEx<T> input, Predicate<T> pred, UnaryOperator<T> mapper, Stream.Builder<T> buf) {
return input.headTail((head, tail) ->
pred.test(head)
? moveToEndOrMap(tail, pred, mapper, buf.add(head))
: moveToEndOrMap(tail, pred, mapper, buf).prepend(mapper.apply(head)),
buf::build);
}
// ///////////////////////
// Terminal ops
// Returns either the first stream element matching the predicate or just
// the first element if nothing matches
private static <T> T firstMatchingOrFirst(StreamEx<T> stream, Predicate<T> predicate) {
return stream.headTail((head, tail) -> tail.prepend(head).filter(predicate).append(head)).findFirst().get();
}
// ///////////////////////
// Tests
@Test
public void testHeadTailRecursive() {
streamEx(() -> StreamEx.iterate(2, x -> x + 1), s -> assertEquals(asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97), s.get().chain(StreamExHeadTailTest::sieve).takeWhile(
x -> x < 100).toList()));
emptyStreamEx(Integer.class, s -> {
assertEquals(0L, s.get().headTail((first, head) -> {
throw new IllegalStateException();
}).count());
assertFalse(s.get().headTail((first, head) -> {
throw new IllegalStateException();
}).findFirst().isPresent());
});
streamEx(() -> IntStreamEx.range(100).boxed(), s -> assertEquals(IntStreamEx.rangeClosed(99, 0, -1).boxed()
.toList(), reverse(s.get()).toList()));
streamEx(() -> IntStreamEx.range(100).boxed(), s -> assertEquals(IntStreamEx.rangeClosed(99, 0, -1).boxed()
.toList(), reverseTSO(s.get()).toList()));
streamEx(() -> StreamEx.of(1, 2), s -> assertEquals(0, s.get().headTail((head, stream) -> null).count()));
streamEx(() -> StreamEx.iterate(1, x -> x + 1), s -> assertEquals(asList(1, 3, 6, 10, 15, 21, 28, 36, 45, 55,
66, 78, 91), s.get().chain(scanLeft(Integer::sum)).takeWhile(x -> x < 100).toList()));
streamEx(() -> StreamEx.iterate(1, x -> x + 1), s -> assertEquals(IntStreamEx.range(1, 100).boxed().toList(),
takeWhile(s.get(), x -> x < 100).toList()));
streamEx(() -> StreamEx.iterate(1, x -> x + 1), s -> assertEquals(IntStreamEx.rangeClosed(1, 100).boxed()
.toList(), s.get().chain(str -> takeWhileClosed(str, x -> x < 100)).toList()));
streamEx(() -> IntStreamEx.range(1000).boxed(), s -> assertEquals(IntStreamEx.range(0, 1000, 20).boxed()
.toList(), every(s.get(), 20).toList()));
// http://stackoverflow.com/q/34395943/4856258
int[] input = { 1, 2, 3, -1, 3, -10, 9, 100, 1, 100, 0 };
AtomicInteger counter = new AtomicInteger();
assertEquals(5, IntStreamEx.of(input).peek(x -> counter.incrementAndGet()).boxed()
.chain(scanLeft(Integer::sum)).indexOf(x -> x < 0).getAsLong());
assertEquals(6, counter.get());
assertEquals(4, (int) firstMatchingOrFirst(StreamEx.of(1, 2, 3, 4, 5), x -> x > 3));
assertEquals(1, (int) firstMatchingOrFirst(StreamEx.of(1, 2, 3, 4, 5), x -> x > 5));
assertEquals(1, (int) firstMatchingOrFirst(StreamEx.of(1, 2, 3, 4, 5), x -> x > 0));
assertEquals(asList(1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3), cycle(StreamEx.of(1, 2, 3, 4, 5))
.limit(18).toList());
assertEquals(asList(1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5), cycleTSO(StreamEx.of(1, 2, 3, 4, 5), 3)
.toList());
assertEquals(asList(1, 2, 3, 4, 5, 5, 4, 3, 2, 1), mirror(StreamEx.of(1, 2, 3, 4, 5)).toList());
assertEquals(asList(9, 13, 17), StreamEx.of(1, 3, 5, 7, 9).headTail(
(head, tail) -> tail.pairMap((a, b) -> a + b + head)).toList());
assertEquals("[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19]]", batches(
IntStreamEx.range(20).boxed(), 4).toList().toString());
assertEquals("[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19], [20]]", batches(
IntStreamEx.range(21).boxed(), 4).toList().toString());
assertEquals(
"[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8], [6, 7, 8, 9]]",
sliding(IntStreamEx.range(10).mapToObj(Collections::singletonList), 4).toList().toString());
assertEquals(IntStreamEx.range(50, 100).boxed().toList(), dropWhile(IntStreamEx.range(100).boxed(),
i -> i != 50).toList());
assertEquals(asList(1, 3, 4, 7, 10), dominators(
StreamEx.of(1, 3, 4, 2, 1, 7, 5, 3, 4, 0, 4, 6, 7, 10, 4, 3, 2, 1), (a, b) -> a >= b).toList());
assertEquals(asList(1, 3, 7, 5, 10), distinct(
StreamEx.of(1, 1, 3, 1, 3, 7, 1, 3, 1, 7, 3, 5, 1, 3, 5, 5, 7, 7, 7, 10, 5, 3, 7, 1)).toList());
assertEquals(asList("key1=1", "key2=2", "key3=3"), couples(StreamEx.of("key1", 1, "key2", 2, "key3", 3),
(k, v) -> k + "=" + v).toList());
assertEquals(asList("key1=1", "1=key2", "key2=2", "2=key3", "key3=3"), pairs(
StreamEx.of("key1", 1, "key2", 2, "key3", 3), (k, v) -> k + "=" + v).toList());
assertEquals(asList("1. Foo", "2. Bar", "3. Baz"), withIndices(StreamEx.of("Foo", "Bar", "Baz"),
(idx, e) -> (idx + 1) + ". " + e).toList());
assertEquals(asList(1,2,3,4,10), appendSum(StreamEx.of(1,2,3,4)).toList());
assertFalse(appendSum(StreamEx.of(1,2,3,4)).has(11));
assertEquals(asList(0, 3, 6, 9, 12, 15, 18, 0, 4, 8, 12, 16), twoFilters(IntStreamEx.range(20).boxed(),
x -> x % 3 == 0, x -> x % 4 == 0).toList());
assertEquals(asList(5, 10, 1, 6, 7), skipLast(Stream.of(5, 10, 1, 6, 7, 15, -1, 10), 3).toList());
assertEquals(asList(0, 3, 6, 9, 12, 15, 18), every3(IntStreamEx.range(20).boxed()).toList());
assertEquals(asList(0, 1, 2, 3, 3), StreamEx.of(0, 1, 4, 2, 10, 3, 5, 10, 3, 15).chain(
limitSorted(Comparator.<Integer> naturalOrder(), 5)).toList());
assertEquals(asList(1, 3, 7, 9, 2, 4, 11, 17, 5, 10),
StreamEx.of(1, 3, 5, 7, 9, 2, 4, 10, 11, 17).chain(moveToEnd(x -> x % 5 == 0)).toList());
assertEquals(asList(2, 4, 8, 10, 3, 5, 12, 18, 5, 10),
StreamEx.of(1, 3, 5, 7, 9, 2, 4, 10, 11, 17).chain(moveToEndOrMap(x -> x % 5 == 0, x -> x + 1)).toList());
assertEquals(asList(11, 21, 41, 51, 30),
StreamEx.of(10, 20, 30, 40, 50).chain(moveToEndOrMap(x -> x == 30, x -> x + 1)).toList());
}
@Test
public void testHeadTailTCO() {
assertTrue(couples(IntStreamEx.range(20000).boxed(), (a, b) -> b - a).allMatch(x -> x == 1));
assertTrue(pairs(IntStreamEx.range(20000).boxed(), (a, b) -> b - a).allMatch(x -> x == 1));
// 20001+20002+...+40000
assertEquals(600010000, limit(skip(StreamEx.iterate(1, x -> x + 1), 20000), 20000).mapToInt(Integer::intValue)
.sum());
// 1+3+5+...+39999
assertEquals(400000000, limit(every(StreamEx.iterate(1, x -> x + 1), 2), 20000).mapToInt(Integer::intValue)
.sum());
assertEquals(400000000, limit(filter(StreamEx.iterate(1, x -> x + 1), x -> x % 2 != 0), 20000).mapToInt(
Integer::intValue).sum());
// 1+2+...+10000
assertEquals(50005000, (int) limit(scanLeft(StreamEx.iterate(1, x -> x + 1), Integer::sum), 10000).reduce(
(a, b) -> b).get());
assertEquals(50005000, (int) limit(flatMap(StreamEx.iterate(1, x -> x + 3), (Integer x) -> StreamEx.of(x, x + 1, x + 2)),
10000).reduce(Integer::sum).get());
assertEquals(asList(50005000), skip(
appendReduction(IntStreamEx.rangeClosed(1, 10000).boxed(), 0, Integer::sum), 10000).toList());
AtomicInteger sum = new AtomicInteger();
assertEquals(10000, peek(IntStreamEx.rangeClosed(1, 10000).boxed(), sum::addAndGet).count());
assertEquals(50005000, sum.get());
assertEquals(400020000, (int) limit(map(StreamEx.iterate(1, x -> x + 1), x -> x * 2), 20000).reduce(
Integer::sum).get());
assertEquals(19999, takeWhile(StreamEx.iterate(1, x -> x + 1), x -> x < 20000).count());
assertTrue(takeWhile(StreamEx.iterate(1, x -> x + 1), x -> x < 20000).has(19999));
assertEquals(20000, takeWhileClosed(StreamEx.iterate(1, x -> x + 1), x -> x < 20000).count());
assertTrue(takeWhileClosed(StreamEx.iterate(1, x -> x + 1), x -> x < 20000).has(20000));
assertEquals(IntStreamEx.range(20000, 40000).boxed().toList(), dropWhile(IntStreamEx.range(40000).boxed(),
i -> i != 20000).toList());
assertEquals(5000, batches(IntStreamEx.range(20000).boxed(), 4).count());
assertEquals(4, batches(IntStreamEx.range(20000).boxed(), 5000).count());
assertEquals(19997, sliding(IntStreamEx.range(20000).mapToObj(Collections::singletonList), 4).count());
assertEquals(IntStreamEx.range(40000).boxed().toList(), dominators(IntStreamEx.range(40000).boxed(),
(a, b) -> a >= b).toList());
assertEquals(15, dominators(IntStreamEx.of(new Random(1)).boxed(), (a, b) -> a >= b).takeWhile(
x -> x < Integer.MAX_VALUE - 100000).count());
assertEquals(IntStreamEx.of(new Random(1), 10000).boxed().sorted().toList(), sorted(
IntStreamEx.of(new Random(1), 10000).boxed()).toList());
assertEquals(10000, withIndices(IntStreamEx.of(new Random(1), 10000).boxed(), (idx, e) -> idx + ": " + e)
.count());
assertEquals(10000, limit(distinctTSO(IntStreamEx.of(new Random(1)).boxed()), 10000).toSet().size());
assertEquals(IntStreamEx.range(10000).boxed().toList(), sorted(limit(
distinctTSO(IntStreamEx.of(new Random(1), 0, 10000).boxed()), 10000)).toList());
assertEquals(IntStreamEx.rangeClosed(9999, 0, -1).boxed()
.toList(), reverseTSO(IntStreamEx.range(10000).boxed()).toList());
assertEquals(IntStreamEx.range(10000).append(IntStreamEx.rangeClosed(9999, 0, -1)).boxed().toList(),
mirrorTSO(IntStreamEx.range(10000).boxed()).toList());
}
@Test
public void testHeadTailClose() {
AtomicBoolean origClosed = new AtomicBoolean();
AtomicBoolean internalClosed = new AtomicBoolean();
AtomicBoolean finalClosed = new AtomicBoolean();
StreamEx<Integer> res = StreamEx.of(1, 2, 3).onClose(() -> origClosed.set(true)).<Integer> headTail(
(head, stream) -> stream.onClose(() -> internalClosed.set(true)).map(x -> x + head)).onClose(
() -> finalClosed.set(true));
assertEquals(asList(3, 4), res.toList());
res.close();
assertTrue(origClosed.get());
assertTrue(internalClosed.get());
assertTrue(finalClosed.get());
res = StreamEx.<Integer> empty().headTail((head, tail) -> tail);
assertEquals(0, res.count());
res.close();
}
// Test simple non-recursive scenarios
@Test
public void testHeadTailSimple() {
repeat(10, i -> {
// Header mapping
String input = "name,type,value\nID,int,5\nSurname,string,Smith\nGiven name,string,John";
List<Map<String, String>> expected = asList(EntryStream.of("name", "ID", "type", "int", "value", "5")
.toMap(), EntryStream.of("name", "Surname", "type", "string", "value", "Smith").toMap(),
EntryStream.of("name", "Given name", "type", "string", "value", "John").toMap());
streamEx(() -> StreamEx.ofLines(new StringReader(input)), s -> assertEquals(expected, s.get().map(
str -> str.split(",")).headTail(
(header, stream) -> stream.map(row -> EntryStream.zip(header, row).toMap())).toList()));
});
streamEx(() -> StreamEx.of("a", "b", "c", "d"), s -> assertEquals(Collections.singletonMap("a", asList("b",
"c", "d")), s.get().headTail((x, str) -> str.mapToEntry(e -> x, e -> e)).mapToEntry(Entry::getKey,
Entry::getValue).grouping()));
assertEquals(asList("b:c", "c:d"), StreamEx.of(":", "b", "c", "d").headTail(
(head, tail) -> tail.pairMap((left, right) -> left + head + right)).toList());
assertEquals(asList("b:", "c", "d"), StreamEx.of(":", "b", "c", "d").headTail(
(head, tail) -> tail.mapFirst(first -> first + head)).toList());
}
@Test
public void testSpliterator() {
Spliterator<Integer> spltr = map(StreamEx.of(1,2,3,4), x -> x*2).spliterator();
assertTrue(spltr.hasCharacteristics(Spliterator.ORDERED));
assertEquals(4, spltr.estimateSize());
assertTrue(spltr.tryAdvance(x -> assertEquals(2, (int)x)));
assertEquals(3, spltr.estimateSize());
assertTrue(spltr.tryAdvance(x -> assertEquals(4, (int)x)));
assertEquals(2, spltr.estimateSize());
assertTrue(spltr.tryAdvance(x -> assertEquals(6, (int)x)));
assertEquals(1, spltr.estimateSize());
assertTrue(spltr.tryAdvance(x -> assertEquals(8, (int)x)));
assertFalse(spltr.tryAdvance(x -> fail("Should not be called")));
assertEquals(0, spltr.estimateSize());
}
}