Skip to content

Commit 2a232d9

Browse files
authored
Merge pull request jOOQ#385 from JingHuaMan/issue-221
jOOQ#221 Add percentileAll and medianAll
2 parents 82b5791 + 84c3610 commit 2a232d9

File tree

2 files changed

+333
-16
lines changed

2 files changed

+333
-16
lines changed

jOOL-java-8/src/main/java/org/jooq/lambda/Agg.java

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -940,8 +940,118 @@ else if (size == 1)
940940
}
941941

942942
/**
943-
* Get a {@link Collector} that calculates the common prefix of a set of strings.
943+
* CS304 Issue link: https://github.com/jOOQ/jOOL/issues/221
944+
* Get a {@link Collector} that calculates the derived <code>PERCENTILE_DISC(percentile)</code> function given a specific ordering, producing multiple results.
944945
*/
946+
public static <T extends Comparable<? super T>> Collector<T, ?, Seq<T>> percentileAll(double percentile) {
947+
return percentileAllBy(percentile, t -> t, naturalOrder());
948+
}
949+
950+
/**
951+
* CS304 Issue link: https://github.com/jOOQ/jOOL/issues/221
952+
* Get a {@link Collector} that calculates the derived <code>PERCENTILE_DISC(percentile)</code> function given a specific ordering, producing multiple results.
953+
*/
954+
public static <T> Collector<T, ?, Seq<T>> percentileAll(double percentile, Comparator<? super T> comparator) {
955+
return percentileAllBy(percentile, t -> t, comparator);
956+
}
957+
958+
/**
959+
* CS304 Issue link: https://github.com/jOOQ/jOOL/issues/221
960+
* Get a {@link Collector} that calculates the derived <code>PERCENTILE_DISC(percentile)</code> function given a specific ordering, producing multiple results.
961+
*/
962+
public static <T, U extends Comparable<? super U>> Collector<T, ?, Seq<T>> percentileAllBy(double percentile, Function<? super T, ? extends U> function) {
963+
return percentileAllBy(percentile, function, naturalOrder());
964+
}
965+
966+
/**
967+
* CS304 Issue link: https://github.com/jOOQ/jOOL/issues/221
968+
* Get a {@link Collector} that calculates the derived <code>PERCENTILE_DISC(percentile)</code> function given a specific ordering, producing multiple results.
969+
*/
970+
public static <T, U> Collector<T, ?, Seq<T>> percentileAllBy(double percentile, Function<? super T, ? extends U> function, Comparator<? super U> comparator) {
971+
if (percentile < 0.0 || percentile > 1.0)
972+
throw new IllegalArgumentException("Percentile must be between 0.0 and 1.0");
973+
974+
if (percentile == 0.0)
975+
// If percentile is 0, this is the same as taking the items with the minimum value.
976+
return minAllBy(function, comparator);
977+
else if (percentile == 1.0)
978+
// If percentile is 1, this is the same as taking the items with the maximum value.
979+
return maxAllBy(function, comparator);
980+
981+
return Collector.of(
982+
() -> new ArrayList<Tuple2<? extends T, U>>(),
983+
(l, v) -> l.add(tuple(v, function.apply(v))),
984+
(l1, l2) -> {
985+
l1.addAll(l2);
986+
return l1;
987+
},
988+
l -> {
989+
int size = l.size();
990+
991+
if (size == 0)
992+
return Seq.empty();
993+
else if (size == 1)
994+
return Seq.of(l.get(0).v1);
995+
996+
l.sort(Comparator.comparing(t -> t.v2, comparator));
997+
998+
int left = 0;
999+
int right = (int) -Math.round(-(size * percentile + 0.5)) - 1;
1000+
int mid;
1001+
1002+
List<T> result = new ArrayList<>();
1003+
U value = l.get(right).v2;
1004+
// Search the first value equal to the value at the position of PERCENTILE by binary search
1005+
while (left < right) {
1006+
mid = left + (right - left) / 2;
1007+
if (comparator.compare(l.get(mid).v2, value) < 0)
1008+
left = mid + 1;
1009+
else
1010+
right = mid;
1011+
}
1012+
1013+
for (; left < size && comparator.compare(l.get(left).v2, value) == 0; left++)
1014+
result.add(l.get(left).v1);
1015+
return Seq.seq(result);
1016+
}
1017+
);
1018+
}
1019+
1020+
/**
1021+
* CS304 Issue link: https://github.com/jOOQ/jOOL/issues/221
1022+
* Get a {@link Collector} that calculates the derived <code>PERCENTILE_DISC(percentile)</code> function given a specific ordering, producing multiple results.
1023+
*/
1024+
public static <T extends Comparable<? super T>> Collector<T, ?, Seq<T>> medianAll() {
1025+
return medianAllBy(t -> t, naturalOrder());
1026+
}
1027+
1028+
/**
1029+
* CS304 Issue link: https://github.com/jOOQ/jOOL/issues/221
1030+
* Get a {@link Collector} that calculates the derived <code>PERCENTILE_DISC(percentile)</code> function given a specific ordering, producing multiple results.
1031+
*/
1032+
public static <T> Collector<T, ?, Seq<T>> medianAll(Comparator<? super T> comparator) {
1033+
return medianAllBy(t -> t, comparator);
1034+
}
1035+
1036+
/**
1037+
* CS304 Issue link: https://github.com/jOOQ/jOOL/issues/221
1038+
* Get a {@link Collector} that calculates the derived <code>PERCENTILE_DISC(percentile)</code> function given a specific ordering, producing multiple results.
1039+
*/
1040+
public static <T, U extends Comparable<? super U>> Collector<T, ?, Seq<T>> medianAllBy(Function<? super T, ? extends U> function) {
1041+
return medianAllBy(function, naturalOrder());
1042+
}
1043+
1044+
/**
1045+
* CS304 Issue link: https://github.com/jOOQ/jOOL/issues/221
1046+
* Get a {@link Collector} that calculates the derived <code>MEDIAN()</code> function given natural ordering, producing multiple results.
1047+
*/
1048+
public static <T, U> Collector<T, ?, Seq<T>> medianAllBy(Function<? super T, ? extends U> function, Comparator<? super U> comparator) {
1049+
return percentileAllBy(0.5, function, comparator);
1050+
}
1051+
1052+
/**
1053+
* Get a {@link Collector} that calculates the common prefix of a set of strings.
1054+
*/
9451055
public static Collector<CharSequence, ?, String> commonPrefix() {
9461056
return Collectors.collectingAndThen(
9471057
Collectors.reducing((CharSequence s1, CharSequence s2) -> {

jOOL-java-8/src/test/java/org/jooq/lambda/CollectorTests.java

Lines changed: 222 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,14 @@
1515
*/
1616
package org.jooq.lambda;
1717

18-
import static org.jooq.lambda.Agg.allMatch;
19-
import static org.jooq.lambda.Agg.anyMatch;
20-
import static org.jooq.lambda.Agg.denseRank;
21-
import static org.jooq.lambda.Agg.denseRankBy;
22-
import static org.jooq.lambda.Agg.max;
23-
import static org.jooq.lambda.Agg.maxBy;
24-
import static org.jooq.lambda.Agg.median;
25-
import static org.jooq.lambda.Agg.min;
26-
import static org.jooq.lambda.Agg.minBy;
27-
import static org.jooq.lambda.Agg.noneMatch;
28-
import static org.jooq.lambda.Agg.percentRank;
29-
import static org.jooq.lambda.Agg.percentile;
30-
import static org.jooq.lambda.Agg.percentileBy;
31-
import static org.jooq.lambda.Agg.rank;
32-
import static org.jooq.lambda.Agg.rankBy;
18+
import static java.util.Arrays.asList;
19+
import static org.jooq.lambda.Agg.*;
3320
import static org.jooq.lambda.tuple.Tuple.tuple;
3421
import static org.junit.Assert.assertEquals;
3522

23+
import java.util.Comparator;
3624
import java.util.Optional;
25+
import java.util.function.Supplier;
3726
import java.util.stream.Collector;
3827
import java.util.stream.Stream;
3928
import java.util.function.Function;
@@ -456,7 +445,225 @@ public void testPercentileWithStringsAndFunctionWithDifferentValues2() {
456445
assertEquals(Optional.of("a"), Stream.of("a", "b", "c", "d", "j", "i", "c", "c", "t", "u", "v").collect(percentileBy(1.0, getMinusValueOfFirstLetter)));
457446
}
458447

448+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
459449
@Test
450+
public void testPercentileAllByWithComparator() {
451+
Supplier<Seq<String>> s = () -> Seq.of("lll1", "s1", "lll2", "mm1", "mm2", "lll3", "s2", "mm3", "s3", "lll4");
452+
453+
assertEquals(asList("lll1", "lll2", "lll3", "lll4"), s.get().collect(percentileAllBy(0.25, String::length, Comparator.comparingInt(o -> -o))).toList());
454+
assertEquals(asList("mm1", "mm2", "mm3"), s.get().collect(percentileAllBy(0.5, String::length, Comparator.comparingInt(o -> -o))).toList());
455+
assertEquals(asList("s1", "s2", "s3"), s.get().collect(percentileAllBy(0.75, String::length, Comparator.comparingInt(o -> -o))).toList());
456+
}
457+
458+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
459+
@Test
460+
public void testPercentileAllByWithComparatorInBoundaryConditions() {
461+
Supplier<Seq<String>> s = () -> Seq.of("lll1", "s1", "lll2", "mm1", "mm2", "lll3", "s2", "mm3", "s3", "lll4");
462+
463+
// Illegal args
464+
Utils.assertThrows(IllegalArgumentException.class, () -> s.get().collect(percentileAllBy(-1, String::length, Comparator.comparingInt(o -> -o))));
465+
Utils.assertThrows(IllegalArgumentException.class, () -> s.get().collect(percentileAllBy(2, String::length, Comparator.comparingInt(o -> -o))));
466+
467+
// MaxAllBy
468+
assertEquals(asList("s1", "s2", "s3"), s.get().collect(percentileAllBy(1.0, String::length, Comparator.comparingInt(o -> -o))).toList());
469+
// MinAllBy
470+
assertEquals(asList("lll1", "lll2", "lll3", "lll4"), s.get().collect(percentileAllBy(0.0, String::length, Comparator.comparingInt(o -> -o))).toList());
471+
}
472+
473+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
474+
@Test
475+
public void testPercentileAllByWithoutComparator() {
476+
Supplier<Seq<String>> s = () -> Seq.of("lll1", "s1", "lll2", "mm1", "mm2", "lll3", "s2", "mm3", "s3", "lll4");
477+
478+
assertEquals(asList("s1", "s2", "s3"), s.get().collect(percentileAllBy(0.25, String::length)).toList());
479+
assertEquals(asList("mm1", "mm2", "mm3"), s.get().collect(percentileAllBy(0.5, String::length)).toList());
480+
assertEquals(asList("lll1", "lll2", "lll3", "lll4"), s.get().collect(percentileAllBy(0.75, String::length)).toList());
481+
}
482+
483+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
484+
@Test
485+
public void testPercentileAllByWithoutComparatorInBoundaryConditions() {
486+
Supplier<Seq<String>> s = () -> Seq.of("lll1", "s1", "lll2", "mm1", "mm2", "lll3", "s2", "mm3", "s3", "lll4");
487+
488+
// Illegal args
489+
Utils.assertThrows(IllegalArgumentException.class, () -> s.get().collect(percentileAllBy(-1, String::length)));
490+
Utils.assertThrows(IllegalArgumentException.class, () -> s.get().collect(percentileAllBy(2, String::length)));
491+
492+
// MaxAllBy
493+
assertEquals(asList("lll1", "lll2", "lll3", "lll4"), s.get().collect(percentileAllBy(1.0, String::length)).toList());
494+
// MinAllBy
495+
assertEquals(asList("s1", "s2", "s3"), s.get().collect(percentileAllBy(0.0, String::length)).toList());
496+
}
497+
498+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
499+
@Test
500+
public void testPercentileAllWithComparator() {
501+
Supplier<Seq<Integer>> s = () -> Seq.of(1, 3, 2, 1, 2, 1, 2, 3, 3, 2);
502+
503+
assertEquals(asList(3, 3, 3), s.get().collect(percentileAll(0.25, Comparator.comparingInt(o -> -o))).toList());
504+
assertEquals(asList(2, 2, 2, 2), s.get().collect(percentileAll(0.5, Comparator.comparingInt(o -> -o))).toList());
505+
assertEquals(asList(1, 1, 1), s.get().collect(percentileAll(0.75, Comparator.comparingInt(o -> -o))).toList());
506+
}
507+
508+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
509+
@Test
510+
public void testPercentileAllWithComparatorInBoundaryConditions() {
511+
Supplier<Seq<Integer>> s = () -> Seq.of(1, 3, 2, 1, 2, 1, 2, 3, 3, 2);
512+
513+
// Illegal args
514+
Utils.assertThrows(IllegalArgumentException.class, () -> s.get().collect(percentileAll(-1, Comparator.comparingInt(o -> -o))));
515+
Utils.assertThrows(IllegalArgumentException.class, () -> s.get().collect(percentileAll(2, Comparator.comparingInt(o -> -o))));
516+
517+
// MaxAllBy
518+
assertEquals(asList(1, 1, 1), s.get().collect(percentileAll(1.0, Comparator.comparingInt(o -> -o))).toList());
519+
// MinAllBy
520+
assertEquals(asList(3, 3, 3), s.get().collect(percentileAll(0.0, Comparator.comparingInt(o -> -o))).toList());
521+
}
522+
523+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
524+
@Test
525+
public void testPercentileAllWithoutComparator() {
526+
Supplier<Seq<Integer>> s = () -> Seq.of(1, 3, 2, 1, 2, 1, 2, 3, 3, 2);
527+
528+
assertEquals(asList(1, 1, 1), s.get().collect(percentileAll(0.25)).toList());
529+
assertEquals(asList(2, 2, 2, 2), s.get().collect(percentileAll(0.5)).toList());
530+
assertEquals(asList(3, 3, 3), s.get().collect(percentileAll(0.75)).toList());
531+
}
532+
533+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
534+
@Test
535+
public void testPercentileAllWithoutComparatorInBoundaryConditions() {
536+
Supplier<Seq<Integer>> s = () -> Seq.of(1, 3, 2, 1, 2, 1, 2, 3, 3, 2);
537+
538+
// Illegal args
539+
Utils.assertThrows(IllegalArgumentException.class, () -> s.get().collect(percentileAll(-1)));
540+
Utils.assertThrows(IllegalArgumentException.class, () -> s.get().collect(percentileAll(2)));
541+
542+
// MaxAllBy
543+
assertEquals(asList(3, 3, 3), s.get().collect(percentileAll(1.0)).toList());
544+
// MinAllBy
545+
assertEquals(asList(1, 1, 1), s.get().collect(percentileAll(0.0)).toList());
546+
}
547+
548+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
549+
@Test
550+
public void testMedianAllByWithComparator() {
551+
assertEquals(asList("mm1", "mm2", "mm3"), Seq.of("lll1", "s1", "lll2", "mm1", "mm2", "lll3", "s2", "mm3", "s3", "lll4").collect(medianAllBy(String::length, Comparator.comparingInt(o -> -o))).toList());
552+
}
553+
554+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
555+
@Test
556+
public void testMedianAllByWithComparator2() {
557+
class Item {
558+
final int val;
559+
Item(int val) {
560+
this.val = val;
561+
}
562+
}
563+
564+
Item a = new Item(1), b = new Item(1), c = new Item(2), d = new Item(2),
565+
e = new Item(2), f = new Item(3), g = new Item(3), h = new Item(3),
566+
i = new Item(4), j = new Item(4), k = new Item(5), l = new Item(6),
567+
m = new Item(7), n = new Item(7), o = new Item(7), p = new Item(7);
568+
569+
assertEquals(asList(j, i), Seq.of(c, j, n, d, e, o, l, p, a, m, h, b, k, g, f, i).collect(medianAllBy(item -> item.val, Comparator.comparingInt(val -> -val))).toList());
570+
}
571+
572+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
573+
@Test
574+
public void testMedianAllByWithoutComparator() {
575+
assertEquals(asList("mm1", "mm2", "mm3"), Seq.of("lll1", "s1", "lll2", "mm1", "mm2", "lll3", "s2", "mm3", "s3", "lll4").collect(medianAllBy(String::length)).toList());
576+
}
577+
578+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
579+
@Test
580+
public void testMedianAllByWithoutComparator2() {
581+
class Item {
582+
final int val;
583+
Item(int val) {
584+
this.val = val;
585+
}
586+
}
587+
588+
Item a = new Item(1), b = new Item(1), c = new Item(2), d = new Item(2),
589+
e = new Item(2), f = new Item(3), g = new Item(3), h = new Item(3),
590+
i = new Item(4), j = new Item(4), k = new Item(5), l = new Item(6),
591+
m = new Item(7), n = new Item(7), o = new Item(7), p = new Item(7);
592+
593+
assertEquals(asList(h, g, f), Seq.of(c, j, n, d, e, o, l, p, a, m, h, b, k, g, f, i).collect(medianAllBy(item -> item.val)).toList());
594+
}
595+
596+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
597+
@Test
598+
public void testMedianAllWithComparator() {
599+
assertEquals(asList(2, 2, 2, 2), Seq.of(1, 3, 2, 1, 2, 1, 2, 3, 3, 2).collect(medianAll(Comparator.comparingInt(o -> -o))).toList());
600+
}
601+
602+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
603+
@Test
604+
public void testMedianAllWithComparator2() {
605+
class Item implements Comparable<Item>{
606+
final int val;
607+
Item(int val) {
608+
this.val = val;
609+
}
610+
611+
@Override
612+
public int compareTo(Item o) {
613+
return this.val - o.val;
614+
}
615+
616+
Item reverse() {
617+
return new Item(-this.val);
618+
}
619+
}
620+
621+
Item a = new Item(1), b = new Item(1), c = new Item(2), d = new Item(2),
622+
e = new Item(2), f = new Item(3), g = new Item(3), h = new Item(3),
623+
i = new Item(4), j = new Item(4), k = new Item(5), l = new Item(6),
624+
m = new Item(7), n = new Item(7), o = new Item(7), p = new Item(7);
625+
626+
assertEquals(asList(j, i), Seq.of(c, j, n, d, e, o, l, p, a, m, h, b, k, g, f, i).collect(medianAll(Comparator.comparing(Item::reverse))).toList());
627+
}
628+
629+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
630+
@Test
631+
public void testMedianAllWithoutComparator() {
632+
Supplier<Seq<Integer>> s = () -> Seq.of(1, 3, 2, 1, 2, 1, 2, 3, 3, 2);
633+
634+
assertEquals(asList(1, 1, 1), s.get().collect(percentileAll(0.25)).toList());
635+
assertEquals(asList(2, 2, 2, 2), s.get().collect(percentileAll(0.5)).toList());
636+
assertEquals(asList(3, 3, 3), s.get().collect(percentileAll(0.75)).toList());
637+
}
638+
639+
//CS304 (manually written) Issue link: https://github.com/jOOQ/jOOL/issues/221
640+
@Test
641+
public void testMedianAllWithoutComparator2() {
642+
class Item implements Comparable<Item>{
643+
final int val;
644+
Item(int val) {
645+
this.val = val;
646+
}
647+
648+
@Override
649+
public int compareTo(Item o) {
650+
return this.val - o.val;
651+
}
652+
653+
Item reverse() {
654+
return new Item(-this.val);
655+
}
656+
}
657+
658+
Item a = new Item(1), b = new Item(1), c = new Item(2), d = new Item(2),
659+
e = new Item(2), f = new Item(3), g = new Item(3), h = new Item(3),
660+
i = new Item(4), j = new Item(4), k = new Item(5), l = new Item(6),
661+
m = new Item(7), n = new Item(7), o = new Item(7), p = new Item(7);
662+
663+
assertEquals(asList(h, g, f), Seq.of(c, j, n, d, e, o, l, p, a, m, h, b, k, g, f, i).collect(medianAll()).toList());
664+
}
665+
666+
@Test
460667
public void testRank() {
461668

462669
// Values can be obtained from PostgreSQL, e.g. with this query:

0 commit comments

Comments
 (0)