jOOλ is our second most popular library. It implements a set of useful extensions to the JDK’s Stream API, which are useful especially when streams are sequential only, which according to our assumptions is how most people use streams in Java.
Such extensions include:
// (1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, ...)
Seq.of(1, 2, 3).cycle();
// tuple((1, 2, 3), (1, 2, 3))
Seq.of(1, 2, 3).duplicate();
// (1, 0, 2, 0, 3, 0, 4)
Seq.of(1, 2, 3, 4).intersperse(0);
// (4, 3, 2, 1)
Seq.of(1, 2, 3, 4).reverse();
… and many more.
Collectors
But that’s not the only thing jOOλ offers. It also ships with a set of useful
Collectors, which can be used both with JDK streams, as well as with jOOλ’s Seq type. Most of them are available from the
org.jooq.lambda.Agg
type, where
Agg
stands for aggregations.
Just like the rest of jOOλ, these collectors are inspired by SQL, and you will find quite a few SQL aggregate functions represented in this class.
Here are some of these collectors:
Counting
While the JDK has
Collectors.counting()
, jOOλ also has a way to count distinct values, just like SQL:
// A simple wrapper for two values:
class A {
final String s;
final long l;
A(String s, long l) {
this.s = s;
this.l = l;
}
static A A(String s, long l) {
return new A(s, l);
}
}
@Test
public void testCount() {
assertEquals(7L, (long)
Stream.of(1, 2, 3, 3, 4, 4, 5)
.collect(Agg.count()));
assertEquals(5L, (long)
Stream.of(1, 2, 3, 3, 4, 4, 5)
.collect(Agg.countDistinct()));
assertEquals(5L, (long)
Stream.of(A("a", 1),
A("b", 2),
A("c", 3),
A("d", 3),
A("e", 4),
A("f", 4),
A("g", 5))
.collect(Agg.countDistinctBy(a -> a.l)));
assertEquals(7L, (long)
Stream.of(A("a", 1),
A("b", 2),
A("c", 3),
A("d", 3),
A("e", 4),
A("f", 4),
A("g", 5))
.collect(Agg.countDistinctBy(a -> a.s)));
}
These are pretty self explanatory, I think.
Percentiles
Just recently, I’ve blogged about the
usefulness of SQL’s percentile functions, and
how to emulate them if they’re unavailable.
Percentiles can also be nicely calculated on streams. Why not? As soon as a Stream’s contents implements
Comparable
, or if you supply your custom
Comparator
, percentiles are easy to calculate:
// Assuming a static import of Agg.percentile:
assertEquals(
Optional.empty(),
Stream.<Integer> of().collect(percentile(0.25)));
assertEquals(
Optional.of(1),
Stream.of(1).collect(percentile(0.25)));
assertEquals(
Optional.of(1),
Stream.of(1, 2).collect(percentile(0.25)));
assertEquals(
Optional.of(1),
Stream.of(1, 2, 3).collect(percentile(0.25)));
assertEquals(
Optional.of(1),
Stream.of(1, 2, 3, 4).collect(percentile(0.25)));
assertEquals(
Optional.of(2),
Stream.of(1, 2, 3, 4, 10).collect(percentile(0.25)));
assertEquals(
Optional.of(2),
Stream.of(1, 2, 3, 4, 10, 9).collect(percentile(0.25)));
assertEquals(
Optional.of(2),
Stream.of(1, 2, 3, 4, 10, 9, 3).collect(percentile(0.25)));
assertEquals(
Optional.of(2),
Stream.of(1, 2, 3, 4, 10, 9, 3, 3).collect(percentile(0.25)));
assertEquals(
Optional.of(3),
Stream.of(1, 2, 3, 4, 10, 9, 3, 3, 20).collect(percentile(0.25)));
assertEquals(
Optional.of(3),
Stream.of(1, 2, 3, 4, 10, 9, 3, 3, 20, 21).collect(percentile(0.25)));
assertEquals(
Optional.of(3),
Stream.of(1, 2, 3, 4, 10, 9, 3, 3, 20, 21, 22).collect(percentile(0.25)));
Notice that jOOλ implements SQL’s percentile_disc semantics. Also, there are 3 “special” percentiles that deserve their own names:
A variety of overloads allows for calculating:
- The percentile of the values contained in the stream
- The percentile of the values contained in the stream, if sorted by another value mapped by a function
- The percentile of the values mapped to another value by a function
Mode
Speaking of statistics. What about the mode? I.e. the value that appears the most often in a stream? Easy, with
Agg.mode()
assertEquals(
Optional.of(1),
Stream.of(1, 1, 1, 2, 3, 4).collect(Agg.mode()));
assertEquals(
Optional.of(1),
Stream.of(1, 1, 2, 2, 3, 4).collect(Agg.mode()));
assertEquals(
Optional.of(2),
Stream.of(1, 1, 2, 2, 2, 4).collect(Agg.mode()));
Other useful collectors
Other collectors that can be useful occasionally are:
Combine the aggregations
And one last important feature
when working with jOOλ is the capability of combining aggregations, just like in SQL. Following the examples above, I can easily calculate several percentiles in one go:
// Unfortunately, Java's type inference might need
// a little help here
var percentiles =
Stream.of(1, 2, 3, 4, 10, 9, 3, 3).collect(
Tuple.collectors(
Agg.<Integer>percentile(0.0),
Agg.<Integer>percentile(0.25),
Agg.<Integer>percentile(0.5),
Agg.<Integer>percentile(0.75),
Agg.<Integer>percentile(1.0)
)
);
System.out.println(percentiles);
The result being:
(Optional[1], Optional[2], Optional[3], Optional[4], Optional[10])
Like this:
Like Loading...
Cool stuff, especially the Tuple.collectors.
Does median work with a stream like (1, 1, 3, 3) ? In general, it can’t work, but with numbers (or anything having an average function), it can.
Indeed, we could implement percentile_cont instead of percentile_disc semantics for numbers. We currently don’t. You could create a feature request: https://github.com/jOOQ/jOOL/issues/new