This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 5ddf871  Support built-in aggregate function `median`
5ddf871 is described below

commit 5ddf871fc03f07acbf2ff6f4a785d575daee934c
Author: Daniel Sun <[email protected]>
AuthorDate: Sun Dec 13 18:24:03 2020 +0800

    Support built-in aggregate function `median`
---
 .../ginq/provider/collection/GinqAstWalker.groovy  |  3 +-
 .../provider/collection/runtime/Queryable.java     |  9 ++++
 .../collection/runtime/QueryableCollection.java    | 28 +++++++++++-
 .../groovy-ginq/src/spec/doc/ginq-userguide.adoc   |  9 +++-
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 17 ++++++-
 .../runtime/QueryableCollectionTest.groovy         | 52 ++++++++++++++++++++++
 6 files changed, 112 insertions(+), 6 deletions(-)

diff --git 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstWalker.groovy
 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstWalker.groovy
index 2d0819e..bdb1678 100644
--- 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstWalker.groovy
+++ 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstWalker.groovy
@@ -1077,8 +1077,9 @@ class GinqAstWalker implements 
GinqAstVisitor<Expression>, SyntaxErrorReportable
     private static final String FUNCTION_MAX = 'max'
     private static final String FUNCTION_SUM = 'sum'
     private static final String FUNCTION_AVG = 'avg'
+    private static final String FUNCTION_MEDIAN = 'median'
     private static final String FUNCTION_AGG = 'agg'
-    private static final List<String> AGG_FUNCTION_NAME_LIST = 
[FUNCTION_COUNT, FUNCTION_MIN, FUNCTION_MAX, FUNCTION_SUM, FUNCTION_AVG, 
FUNCTION_AGG]
+    private static final List<String> AGG_FUNCTION_NAME_LIST = 
[FUNCTION_COUNT, FUNCTION_MIN, FUNCTION_MAX, FUNCTION_SUM, FUNCTION_AVG, 
FUNCTION_MEDIAN, FUNCTION_AGG]
 
     private static final String NAMEDRECORD_CLASS_NAME = NamedRecord.class.name
 
diff --git 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Queryable.java
 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Queryable.java
index a7c9608..7cc2bb8 100644
--- 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Queryable.java
+++ 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Queryable.java
@@ -375,6 +375,15 @@ public interface Queryable<T> {
     <U extends Comparable<? super U>> U max(Function<? super T, ? extends U> 
mapper);
 
     /**
+     * Aggregate function {@code median}, similar to SQL's {@code median}
+     *
+     * @param mapper choose the field to median
+     * @return median result
+     * @since 4.0.0
+     */
+    BigDecimal median(Function<? super T, ? extends Number> mapper);
+
+    /**
      * The most powerful aggregate function in GINQ, it will receive the 
grouped result({@link Queryable} instance) and apply any processing
      *
      * @param mapper map the grouped result({@link Queryable} instance) to 
aggregate result
diff --git 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollection.java
 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollection.java
index 17cd911..c3169e0 100644
--- 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollection.java
+++ 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollection.java
@@ -58,6 +58,7 @@ import static 
org.apache.groovy.ginq.provider.collection.runtime.Queryable.from;
 @Internal
 class QueryableCollection<T> implements Queryable<T>, Serializable {
     private static final long serialVersionUID = -5067092453136522893L;
+    public static final BigDecimal BD_TWO = BigDecimal.valueOf(2);
     private Iterable<T> sourceIterable;
     private Stream<T> sourceStream;
 
@@ -314,7 +315,7 @@ class QueryableCollection<T> implements Queryable<T>, 
Serializable {
 
     @Override
     public BigDecimal avg(Function<? super T, ? extends Number> mapper) {
-        Object[] result = agg(q -> this.stream()
+        Object[] result = agg(q -> q.stream()
                 .map(mapper)
                 .filter(Objects::nonNull)
                 .map(NumberMath::toBigDecimal)
@@ -347,6 +348,31 @@ class QueryableCollection<T> implements Queryable<T>, 
Serializable {
     }
 
     @Override
+    public BigDecimal median(Function<? super T, ? extends Number> mapper) {
+        List<BigDecimal> sortedNumList = agg(q -> q.stream()
+                .map(mapper)
+                .filter(Objects::nonNull)
+                .map(NumberMath::toBigDecimal)
+                .sorted()
+                .collect(Collectors.toList())
+        );
+
+        int size = sortedNumList.size();
+        if (0 == size) {
+            return null;
+        }
+
+        int index = size / 2;
+        BigDecimal num = sortedNumList.get(index);
+
+        if (0 == size % 2) {
+            return num.add(sortedNumList.get(index - 1)).divide(BD_TWO);
+        }
+
+        return num;
+    }
+
+    @Override
     public <U> U agg(Function<? super Queryable<? extends T>, ? extends U> 
mapper) {
         return mapper.apply(this);
     }
diff --git a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc 
b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
index 78dcaf5..1d3b82d 100644
--- a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
+++ b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
@@ -281,9 +281,9 @@ 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_09,in
 
 ===== Aggregate Functions
 GINQ supports some built-in aggregate functions, e.g.
-`count`, `min`, `max`, `sum`, `avg` and the most powerful function `agg`.
+`count`, `min`, `max`, `sum`, `avg`, `median` and the most powerful function 
`agg`.
 [NOTE]
-`count(...)`, `min(...)` and `max(...)` just operate on non-`null` values,
+`count(...)`, `min(...)`, `max(...)`, `avg(...)` and `median(...)` just 
operate on non-`null` values,
 and `count()` is similar to `count(*)` in SQL.
 [source, sql]
 ----
@@ -312,6 +312,11 @@ 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_11,in
 
 [source, sql]
 ----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_12,indent=0]
+----
+
+[source, sql]
+----
 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_03,indent=0]
 ----
 [NOTE]
diff --git 
a/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy 
b/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
index f8944ee..3ba1932 100644
--- 
a/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
+++ 
b/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
@@ -2793,6 +2793,19 @@ class GinqTest {
     }
 
     @Test
+    void "testGinq - from groupby select - 30"() {
+        assertGinqScript '''
+            assert [[1, 1], [3, 3], [6, 6]] == GQ {
+// tag::ginq_grouping_12[]
+                from n in [1, 1, 3, 3, 6, 6, 6]
+                groupby n
+                select n, median(n)
+// end::ginq_grouping_12[]
+            }.toList()
+        '''
+    }
+
+    @Test
     void "testGinq - from where groupby select - 1"() {
         assertGinqScript '''
             assert [[1, 2], [6, 3]] == GQ {
@@ -3792,9 +3805,9 @@ class GinqTest {
     void "testGinq - agg function - 1"() {
         assertGinqScript '''
 // tag::ginq_aggfunction_01[]
-            assert [[1, 3, 2, 6, 3, 3, 6]] == GQ {
+            assert [[1, 3, 2, 2, 6, 3, 3, 6]] == GQ {
                 from n in [1, 2, 3]
-                select min(n), max(n), avg(n), sum(n), count(n), count(), 
+                select min(n), max(n), avg(n), median(n), sum(n), count(n), 
count(), 
                         agg(_g.stream().map(r -> r.n).reduce(BigDecimal.ZERO, 
BigDecimal::add))
             }.toList()
 // end::ginq_aggfunction_01[]
diff --git 
a/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollectionTest.groovy
 
b/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollectionTest.groovy
index 93e64ac..7a9bfa7 100644
--- 
a/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollectionTest.groovy
+++ 
b/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollectionTest.groovy
@@ -825,6 +825,58 @@ class QueryableCollectionTest {
     }
 
     @Test
+    @CompileDynamic
+    void testGroupBySelect10() {
+        def nums = [1, 3, 2]
+        def result =
+                from(nums).groupBy(e -> 1)
+                        .select(e ->
+                                e.v2.median(n -> n)
+                        )
+                        .toList()
+        assert [2] == result
+    }
+
+    @Test
+    @CompileDynamic
+    void testGroupBySelect11() {
+        def nums = [1, 3, 2, 4]
+        def result =
+                from(nums).groupBy(e -> 1)
+                        .select(e ->
+                                e.v2.median(n -> n)
+                        )
+                        .toList()
+        assert [2.5] == result
+    }
+
+    @Test
+    @CompileDynamic
+    void testGroupBySelect12() {
+        def nums = [1]
+        def result =
+                from(nums).groupBy(e -> 1)
+                        .select(e ->
+                                e.v2.median(n -> n)
+                        )
+                        .toList()
+        assert [1] == result
+    }
+
+    @Test
+    @CompileDynamic
+    void testGroupBySelect13() {
+        def nums = []
+        def result =
+                from(nums).groupBy(e -> 1)
+                        .select(e ->
+                                e.v2.median(n -> n)
+                        )
+                        .toList()
+        assert [] == result
+    }
+
+    @Test
     void testOrderBy() {
         Person daniel = new Person('Daniel', 35)
         Person peter = new Person('Peter', 10)

Reply via email to