ence.2.3.0.source-code.overview.html Maven / Gradle / Ivy
Show all versions of sequence Show documentation
A lightweight alternative to Java 8 sequential Stream
The Sequence library is a leaner alternative to sequential Java 8 Streams, used in similar ways but with a lighter
step, and with better integration with the rest of Java. It has no external dependencies and will not slow down your
build.
It aims to be roughly feature complete with sequential Streams, with additional convenience methods for advanced
traversal and transformation. In particular it allows easier collecting into common Collections without Collectors,
better handling of Maps with Pair and Map.Entry as first-class citizens, tighter integration with the rest of Java
by being implemented in terms of Iterable, and advanced partitioning, mapping and filtering methods, for example
allowing you to peek at previous or next elements to make decisions during traversal. Sequences go to great lengths
to be as lazy and late-evaluating as possible, with minimal overhead.
Sequences use Java 8 lambdas in much the same way as Streams do, but is based on readily available Iterables instead
of a black box pipeline, and is built for convenience and compatibility with the rest of Java. It's for programmers
wanting to perform every day data processing tasks on moderately sized collections. If you need parallel iteration
or are processing over 1 million or so entries, you might benefit from using a parallel Stream instead.
{@code
List evens = Sequence.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.filter(x -> x % 2 == 0)
.map(Object::toString)
.toList();
assertThat(evens, contains("2", "4", "6", "8"));
}
See also: {@link org.d2ab.sequence.Sequence#of(Object...)}, {@link org.d2ab.sequence.Sequence#from(Iterable)}
Overview
The main Sequence package is {@link org.d2ab.sequence} where all
the sequences reside. There are seven kinds of Sequences, each dealing with a different type of entry. The first is
the regular {@link org.d2ab.sequence.Sequence} which is the general purpose stream of items. {@link
org.d2ab.sequence.EntrySequence} and {@link org.d2ab.sequence.BiSequence} work directly on the constituent
components of {@link
java.util.Map.Entry} and {@link org.d2ab.util.Pair} objects. The last four are primitive sequences dealing
with {@code char}, {@code int}, {@code long} and {@code double} primitives; {@link org.d2ab.sequence.CharSeq},
{@link org.d2ab.sequence.IntSequence}, {@link org.d2ab.sequence.LongSequence}, and {@link
org.d2ab.sequence.DoubleSequence}. These work much the same as the regular {@link org.d2ab.sequence.Sequence} except
they're adapted to work directly on primitives.
Iterable
Because each {@link org.d2ab.sequence.Sequence} is an
{@link Iterable} you can re-use
them safely after you have already traversed them, as long as they're not backed by an
{@link java.util.Iterator} or
{@link java.util.stream.Stream}
which can only be traversed once.
{@code
Sequence digits = Sequence.ints(); // all integer digits starting at 1
// using sequence of ints first time to get 5 odd numbers
Sequence odds = digits.step(2).limit(5);
assertThat(odds, contains(1, 3, 5, 7, 9));
// re-using the same sequence of digits again to get squares of numbers between 4 and 8
Sequence squares = digits.startingFrom(4).endingAt(8).map(i -> i * i);
assertThat(squares, contains(16, 25, 36, 49, 64));
}
Foreach
Because each {@link org.d2ab.sequence.Sequence} is an {@link Iterable} they work beautifully in foreach
loops:
{@code
Sequence sequence = Sequence.ints().limit(5);
int expected = 1;
for (int each : sequence)
assertThat(each, is(expected++));
assertThat(expected, is(6));
}
FunctionalInterface
Because Sequence is a
{@link
FunctionalInterface} requiring only the
{@link
Iterable#iterator()}
method to be implemented, it's very easy to create your own full-fledged {@link org.d2ab.sequence.Sequence}
instances that can be operated on like any other {@link org.d2ab.sequence.Sequence} through the default methods on
the interface that carry the bulk of the burden. In fact, this is how {@code Sequence's} own factory methods work.
You could consider all of {@code Sequence} to be a smarter version of {@code Iterable}.
{@code
List list = Arrays.asList(1, 2, 3, 4, 5);
// Sequence as @FunctionalInterface of list's Iterator
Sequence sequence = list::iterator;
// Operate on sequence as any other sequence using default methods
Sequence transformed = sequence.map(Object::toString);
assertThat(transformed.limit(3), contains("1", "2", "3"));
}
Caching
Sequences can be created from Iterators or Streams but can then only be passed over once.
{@code
Iterator iterator = Arrays.asList(1, 2, 3, 4, 5).iterator();
Sequence sequence = Sequence.once(iterator);
assertThat(sequence, contains(1, 2, 3, 4, 5));
assertThat(sequence, is(emptyIterable()));
}
See also: {@link org.d2ab.sequence.Sequence#once(Iterator)}, {@link org.d2ab.sequence.Sequence#once(Stream)}
If you have an Iterator or Stream and wish to convert it to a full-fledged multi-iterable Sequence, use the caching
methods on Sequence.
{@code
Iterator iterator = Arrays.asList(1, 2, 3, 4, 5).iterator();
Sequence cached = Sequence.cache(iterator);
assertThat(cached, contains(1, 2, 3, 4, 5));
assertThat(cached, contains(1, 2, 3, 4, 5));
}
See also: {@link org.d2ab.sequence.Sequence#cache(Iterable)}, {@link org.d2ab.sequence.Sequence#cache(Iterator)},
{@link org.d2ab.sequence.Sequence#cache(Stream)}
Updating
{@code Sequences} have full support for updating the underlying collection where possible, through
{@code Iterator#remove()}, by modifying the underlying collection directly (in between iterations), and by using
{@code Collection} methods directly on the {@code Sequence} itself.
{@code
List list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Sequence.from(list).filter(x -> x % 2 != 0).clear();
assertThat(list, contains(2, 4));
}
{@code
List list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Sequence evenStrings = Sequence.from(list)
.filter(x -> x % 2 == 0)
// biMap allows adding back to underlying collection
.biMap(Object::toString, Integer::parseInt);
assertThat(evenStrings, contains("2", "4"));
evenStrings.add("6");
assertThat(evenStrings, contains("2", "4", "6"));
assertThat(list, contains(1, 2, 3, 4, 5, 6));
}
See also: {@link org.d2ab.sequence.Sequence#clear()}
#### Size
Since {@code Sequence} implements {@code Collection} it provides a fully functioning {@code size()} method, however
note that this method may degrade to {@code O(n)} performance if the size is not known a-priori and thus needs to
be calculated by traversing the {@code Sequence}. In some cases, the size can be calculated in advance and
{@code Sequence} makes full use of this:
{@code
Sequence repeated = Sequence.of(1, 2, 3).repeat().limit(5);
assertThat(repeated, contains(1, 2, 3, 1, 2));
assertThat(repeated.size(), is(5)); // immediate return value
assertThat(repeated.sizeIfKnown(), is(5)); // would return -1 if unknown in advance
}
While {@code Sequence} goes to great lengths to be able to detect the size in advance, in many cases the size can
not be calculated in advance and so {@code Sequence} must traverse the list of elements to calculate the size,
degrading to {@code O(n)} performance for the {@code size()} operation:
{@code
List growingList = new ArrayList() {
@Override
public Iterator iterator() {
add(size() + 1);
return super.iterator();
}
};
Sequence repeated = Sequence.from(growingList).repeat().limit(10);
assertThat(repeated, contains(1, 1, 2, 1, 2, 3, 1, 2, 3, 4));
assertThat(repeated.size(), is(10)); // O(n) traversal of elements required
assertThat(repeated.sizeIfKnown(), is(-1)); // cannot determine size in advance as collections can mutate
}
Streams
{@code Sequences} interoperate beautifully with {@code Stream}, through the {@code once(Stream)} and
{@code .stream()} methods.
{@code
Sequence paired = Sequence.once(Stream.of("a", "b", "c", "d")).pairs().flatten();
assertThat(paired.stream().collect(Collectors.toList()), contains("a", "b", "b", "c", "c", "d"));
}
See also: {@link org.d2ab.sequence.Sequence#once(Stream)}, {@link org.d2ab.sequence.Sequence#cache(Stream)}, {@link
org.d2ab.sequence.Sequence#stream()}
Recursion
There is full support for infinite recursive Sequences, including termination at a known value.
{@code
Sequence fibonacci = BiSequence.recurse(0, 1, (i, j) -> Pair.of(j, i + j)).toSequence((i, j) -> i);
assertThat(fibonacci.endingAt(34), contains(0, 1, 1, 2, 3, 5, 8, 13, 21, 34));
}
{@code
Exception exception = new IllegalStateException(new IllegalArgumentException(new NullPointerException()));
Sequence exceptionAndCauses = Sequence.recurse(exception, Throwable::getCause).untilNull();
assertThat(exceptionAndCauses, contains(instanceOf(IllegalStateException.class),
instanceOf(IllegalArgumentException.class),
instanceOf(NullPointerException.class)));
}
{@code
Iterator delimiter = Sequence.of("").append(Sequence.of(", ").repeat()).iterator();
StringBuilder joined = new StringBuilder();
for (String number : Arrays.asList("One", "Two", "Three"))
joined.append(delimiter.next()).append(number);
assertThat(joined.toString(), is("One, Two, Three"));
}
{@code
CharSeq hexGenerator = CharSeq.random("0-9", "A-F").limit(8);
String hexNumber1 = hexGenerator.asString();
String hexNumber2 = hexGenerator.asString();
assertTrue(hexNumber1.matches("[0-9A-F]{8}"));
assertTrue(hexNumber2.matches("[0-9A-F]{8}"));
assertThat(hexNumber1, is(not(hexNumber2)));
}
See also:
- {@link org.d2ab.sequence.Sequence#recurse(Object, UnaryOperator)}
- {@link org.d2ab.sequence.Sequence#recurse(Object, Function, Function)}
- {@link org.d2ab.sequence.Sequence#generate(Supplier)}
- {@link org.d2ab.sequence.Sequence#until(Object)}
- {@link org.d2ab.sequence.Sequence#until(Predicate)}
- {@link org.d2ab.sequence.Sequence#untilNull()}
- {@link org.d2ab.sequence.Sequence#endingAt(Object)}
- {@link org.d2ab.sequence.Sequence#endingAt(Predicate)}
- {@link org.d2ab.sequence.Sequence#endingAtNull()}
Reduction
The standard reduction operations are available as per Stream:
{@code
Sequence thirteen = Sequence.longs().limit(13);
long factorial = thirteen.reduce(1L, (r, i) -> r * i);
assertThat(factorial, is(6227020800L));
}
See also: {@link org.d2ab.sequence.Sequence#reduce(BinaryOperator)}, {@link org.d2ab.sequence.Sequence#reduce(Object,
BinaryOperator)}
Maps
Maps are handled as Sequences of Entry, with special transformation methods that convert to/from Maps.
{@code
Sequence keys = Sequence.of(1, 2, 3);
Sequence values = Sequence.of("1", "2", "3");
Map map = keys.interleave(values).toMap();
assertThat(map, is(equalTo(Maps.builder(1, "1").put(2, "2").put(3, "3").build())));
}
See also:
- {@link org.d2ab.sequence.Sequence#interleave(Iterable)}
- {@link org.d2ab.sequence.Sequence#pairs()}
- {@link org.d2ab.sequence.Sequence#toMap()}
- {@link org.d2ab.sequence.Sequence#toMap(Function, Function)}
- {@link org.d2ab.sequence.Sequence#toMap(Supplier)}
- {@link org.d2ab.sequence.Sequence#toMap(Supplier, Function, Function)}
- {@link org.d2ab.sequence.Sequence#toSortedMap()}
- {@link org.d2ab.sequence.Sequence#toSortedMap(Function, Function)}
You can also map Entry Sequences to Pairs which allows more expressive transformation and filtering.
{@code
Map map = Maps.builder("1", 1).put("2", 2).put("3", 3).put("4", 4).build();
Sequence> sequence = Sequence.from(map)
.map(Pair::from)
.filter(pair -> pair.test((s, i) -> i != 2))
.map(pair -> pair.map((s, i) -> Pair.of(s + " x 2", i * 2)));
assertThat(sequence.toMap(), is(equalTo(Maps.builder("1 x 2", 2).put("3 x 2", 6).put("4 x 2", 8).build())));
}
See also: {@link org.d2ab.util.Pair}
You can also work directly on Entry keys and values using EntrySequence.
{@code
Map original = Maps.builder("1", 1).put("2", 2).put("3", 3).put("4", 4).build();
EntrySequence oddsInverted = EntrySequence.from(original)
.filter((k, v) -> v % 2 != 0)
.map((k, v) -> Maps.entry(v, k));
assertThat(oddsInverted.toMap(), is(equalTo(Maps.builder(1, "1").put(3, "3").build())));
}
See also: {@link org.d2ab.sequence.EntrySequence}
Pairs
When iterating over sequences of Pairs of item, BiSequence provides native operators and transformations:
{@code
BiSequence presidents = BiSequence.ofPairs("Abraham Lincoln", 1861, "Richard Nixon", 1969,
"George Bush", 2001, "Barack Obama", 2005);
Sequence joinedOffice = presidents.toSequence((n, y) -> n + " (" + y + ")");
assertThat(joinedOffice, contains("Abraham Lincoln (1861)", "Richard Nixon (1969)", "George Bush (2001)",
"Barack Obama (2005)"));
}
See also: {@link org.d2ab.sequence.BiSequence}
Primitive
There are also primitive versions of Sequence for char, int, long and double processing: CharSeq, IntSequence,
LongSequence and DoubleSequence.
{@code
CharSeq snakeCase = CharSeq.from("Hello Lexicon").map(c -> (c == ' ') ? '_' : c).map(Character::toLowerCase);
assertThat(snakeCase.asString(), is("hello_lexicon"));
}
{@code
IntSequence squares = IntSequence.positive().map(i -> i * i);
assertThat(squares.limit(5), contains(1, 4, 9, 16, 25));
}
{@code
LongSequence negativeOdds = LongSequence.negative().step(2);
assertThat(negativeOdds.limit(5), contains(-1L, -3L, -5L, -7L, -9L));
}
{@code
DoubleSequence squareRoots = IntSequence.positive().toDoubles().map(Math::sqrt);
assertThat(squareRoots.limit(3), contains(sqrt(1), sqrt(2), sqrt(3)));
}
See also: {@link org.d2ab.sequence.CharSeq}, {@link org.d2ab.sequence.IntSequence}, {@link
org.d2ab.sequence.LongSequence}, {@link org.d2ab.sequence.DoubleSequence}
Peeking
Sequences also have mapping and filtering methods that peek on the previous and next elements:
{@code
CharSeq titleCase = CharSeq.from("hello_lexicon")
.mapBack('_', (prev, x) -> prev == '_' ? toUpperCase(x) : x)
.map(c -> (c == '_') ? ' ' : c);
assertThat(titleCase.asString(), is("Hello Lexicon"));
}
See also:
- {@link org.d2ab.sequence.Sequence#peekBack(BiConsumer)}
- {@link org.d2ab.sequence.Sequence#peekBack(Object, BiConsumer)}
- {@link org.d2ab.sequence.Sequence#peekForward(BiConsumer)}
- {@link org.d2ab.sequence.Sequence#peekForward(Object, BiConsumer)}
- {@link org.d2ab.sequence.Sequence#filterBack(BiPredicate)}
- {@link org.d2ab.sequence.Sequence#filterBack(Object, BiPredicate)}
- {@link org.d2ab.sequence.Sequence#filterForward(BiPredicate)}
- {@link org.d2ab.sequence.Sequence#filterForward(Object, BiPredicate)}
- {@link org.d2ab.sequence.Sequence#mapBack(BiFunction)}
- {@link org.d2ab.sequence.Sequence#mapBack(Object, BiFunction)}
- {@link org.d2ab.sequence.Sequence#mapForward(BiFunction)}
- {@link org.d2ab.sequence.Sequence#mapForward(Object, BiFunction)}
Partitioning
Both regular and primitive Sequences have advanced windowing and partitioning methods, allowing you to divide up
Sequences in various ways, including a partitioning method that uses a binary predicate to determine which elements
to create a batch between.
{@code
Sequence> batched = Sequence.of(1, 2, 3, 4, 5, 6, 7, 8, 9).batch(3);
assertThat(batched, contains(contains(1, 2, 3), contains(4, 5, 6), contains(7, 8, 9)));
}
{@code
String vowels = "aeoiuy";
Sequence consonantsVowels = CharSeq.from("terrain")
.batch((a, b) -> (vowels.indexOf(a) < 0) != (vowels.indexOf(b) < 0))
.map(CharSeq::asString);
assertThat(consonantsVowels, contains("t", "e", "rr", "ai", "n"));
}
See also:
- {@link org.d2ab.sequence.Sequence#window(int)}
- {@link org.d2ab.sequence.Sequence#window(int, int)}
- {@link org.d2ab.sequence.Sequence#batch(int)}
- {@link org.d2ab.sequence.Sequence#batch(BiPredicate)}
- {@link org.d2ab.sequence.Sequence#split(Object)}
- {@link org.d2ab.sequence.Sequence#split(Predicate)}
Reading
Primitive sequences can be read from {@code Readers} or {@code InputStreams} into a {@code CharSeq} or
{@code IntSequence} respective. These can also be converted back to {@code Readers} and {@code InputStreams}
respectively, allowing for filtering or transformation of these streams.
{@code
Reader reader = new StringReader("hello world\ngoodbye world\n");
Sequence titleCase = CharSeq.read(reader)
.mapBack('\n', (prev, x) -> isWhitespace(prev) ? toUpperCase(x) : x)
.split('\n')
.map(phrase -> phrase.append('!'))
.map(CharSeq::asString);
assertThat(titleCase, contains("Hello World!", "Goodbye World!"));
reader.close(); // sequence does not close reader
}
{@code
String original = "hello world\ngoodbye world\n";
BufferedReader transformed = new BufferedReader(CharSeq.from(original).map(Character::toUpperCase).asReader());
assertThat(transformed.readLine(), is("HELLO WORLD"));
assertThat(transformed.readLine(), is("GOODBYE WORLD"));
transformed.close();
}
{@code
InputStream inputStream = new ByteArrayInputStream(new byte[]{0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF});
String hexString = IntSequence.read(inputStream)
.toSequence(Integer::toHexString)
.map(String::toUpperCase)
.join();
assertThat(hexString, is("DEADBEEF"));
inputStream.close();
}
See also: {@link org.d2ab.sequence.CharSeq#read(java.io.Reader)}, {@link
org.d2ab.sequence.IntSequence#read(java.io.InputStream)}