You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/01/23 09:38:34 UTC

[groovy] branch master updated: Add built-in `percentRank` and `cumeDist` window functions to GINQ

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 68bc355  Add built-in `percentRank` and `cumeDist` window functions to GINQ
68bc355 is described below

commit 68bc355befd77d8277911e5edeb187c6a63bac23
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Jan 23 17:37:26 2021 +0800

    Add built-in `percentRank` and `cumeDist` window functions to GINQ
---
 .../ginq/provider/collection/GinqAstWalker.groovy  |  6 ++--
 .../ginq/provider/collection/runtime/Window.java   | 17 ++++++++++
 .../provider/collection/runtime/WindowImpl.java    | 37 ++++++++++++++++++++--
 .../groovy-ginq/src/spec/doc/ginq-userguide.adoc   | 17 +++++++++-
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 28 +++++++++++++++-
 5 files changed, 99 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 7cd0fed..5956b78 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
@@ -753,7 +753,7 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, SyntaxErrorReportable
                             if (windowFunctionMethodCallExpression.methodAsString in WINDOW_FUNCTION_LIST) {
                                 def argumentListExpression = (ArgumentListExpression) windowFunctionMethodCallExpression.arguments
                                 List<Expression> argumentExpressionList = []
-                                if (windowFunctionMethodCallExpression.methodAsString !in [FUNCTION_ROW_NUMBER, FUNCTION_RANK, FUNCTION_DENSE_RANK] && argumentListExpression.expressions) {
+                                if (windowFunctionMethodCallExpression.methodAsString !in [FUNCTION_ROW_NUMBER, FUNCTION_RANK, FUNCTION_DENSE_RANK, FUNCTION_PERCENT_RANK, FUNCTION_CUME_DIST] && argumentListExpression.expressions) {
                                     def windowFunctionLambdaCode = argumentListExpression.getExpression(0)
                                     def windowFunctionLambdaName = '__wfp'
                                     def rootObjectExpression = findRootObjectExpression(windowFunctionLambdaCode)
@@ -1566,9 +1566,11 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, SyntaxErrorReportable
     private static final String FUNCTION_NTH_VALUE = 'nthValue'
     private static final String FUNCTION_RANK = 'rank'
     private static final String FUNCTION_DENSE_RANK = 'denseRank'
+    private static final String FUNCTION_PERCENT_RANK = 'percentRank'
+    private static final String FUNCTION_CUME_DIST = 'cumeDist'
     private static final String FUNCTION_NTILE = 'ntile'
     private static final List<String> WINDOW_FUNCTION_LIST = [FUNCTION_COUNT, FUNCTION_MIN, FUNCTION_MAX, FUNCTION_SUM, FUNCTION_AVG, FUNCTION_MEDIAN, FUNCTION_STDEV, FUNCTION_STDEVP, FUNCTION_VAR, FUNCTION_VARP,
-                                                              FUNCTION_ROW_NUMBER, FUNCTION_LEAD, FUNCTION_LAG, FUNCTION_FIRST_VALUE, FUNCTION_LAST_VALUE, FUNCTION_NTH_VALUE, FUNCTION_RANK, FUNCTION_DENSE_RANK, FUNCTION_NTILE]
+                                                              FUNCTION_ROW_NUMBER, FUNCTION_LEAD, FUNCTION_LAG, FUNCTION_FIRST_VALUE, FUNCTION_LAST_VALUE, FUNCTION_NTH_VALUE, FUNCTION_RANK, FUNCTION_DENSE_RANK, FUNCTION_PERCENT_RANK, FUNCTION_CUME_DIST, FUNCTION_NTILE]
 
     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/Window.java b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Window.java
index c707735..5487098 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Window.java
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Window.java
@@ -18,6 +18,7 @@
  */
 package org.apache.groovy.ginq.provider.collection.runtime;
 
+import java.math.BigDecimal;
 import java.util.function.Function;
 
 /**
@@ -158,6 +159,22 @@ public interface Window<T> extends Queryable<T> {
     Long denseRank();
 
     /**
+     * Returns the percent rank in the window
+     *
+     * @return the percent rank
+     * @since 4.0.0
+     */
+    BigDecimal percentRank();
+
+    /**
+     * Returns the cumulative distribution of a value in the window
+     *
+     * @return the cumulative distribution of a value
+     * @since 4.0.0
+     */
+    BigDecimal cumeDist();
+
+    /**
      * Distributes rows of an ordered window into a pre-defined number of roughly equal buckets
      *
      * @param bucketCnt bucket count
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowImpl.java b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowImpl.java
index 31941c3..a914a6c 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowImpl.java
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowImpl.java
@@ -22,6 +22,7 @@ import groovy.lang.Tuple2;
 import org.apache.groovy.util.ReversedList;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -131,7 +132,7 @@ class WindowImpl<T, U extends Comparable<? super U>> extends QueryableCollection
     @Override
     public Long rank() {
         if (null == value || null == order) {
-            return null;
+            return 0L;
         }
 
         long result = 1L;
@@ -141,12 +142,14 @@ class WindowImpl<T, U extends Comparable<? super U>> extends QueryableCollection
             }
         }
         return result;
+
     }
 
+
     @Override
     public Long denseRank() {
         if (null == value || null == order) {
-            return null;
+            return 0L;
         }
 
         long result = 1L;
@@ -161,6 +164,36 @@ class WindowImpl<T, U extends Comparable<? super U>> extends QueryableCollection
     }
 
     @Override
+    public BigDecimal percentRank() {
+        final int size = list.size();
+        if (1 == size) {
+            return BigDecimal.ONE;
+        }
+
+        if (null == value || null == order) {
+            return BigDecimal.ZERO;
+        }
+
+        Long r = rank();
+        if (null == r) {
+            return BigDecimal.ZERO;
+        }
+
+        return toBigDecimal(r - 1).divide(toBigDecimal(size - 1), 16, RoundingMode.HALF_UP);
+    }
+
+    @Override
+    public BigDecimal cumeDist() {
+        if (null == value || null == order) {
+            return BigDecimal.ZERO;
+        }
+        long cnt = list.stream()
+                        .filter(e -> comparator.compare(currentRecord.getV1(), e) >= 0)
+                        .count();
+        return toBigDecimal(cnt).divide(toBigDecimal(list.size()), 16, RoundingMode.HALF_UP);
+    }
+
+    @Override
     public long ntile(long bucketCnt) {
         return bucketCnt * rowNumber() / list.size();
     }
diff --git a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
index 74603a6..1f00e54 100644
--- a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
+++ b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
@@ -552,6 +552,16 @@ Also, GINQ provides some built-in window functions:
 |java.lang.Long
 |rank of the current row without gaps
 
+|percentRank()
+|
+|java.math.BigDecimal
+|relative rank of the current row: (rank - 1) / (total rows - 1)
+
+|cumeDist()
+|
+|java.math.BigDecimal
+|relative rank of the current row: (number of rows preceding or peer with current row) / (total rows)
+
 |ntile(_expression_)
 |java.lang.Long
 |java.lang.Long
@@ -654,7 +664,7 @@ include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_34
 [NOTE]
 The parentheses around the window function is required.
 
-===== `rank`, `denseRank` and `ntile`
+===== `rank`, `denseRank`, `percentRank`, `cumeDist` and `ntile`
 [source, groovy]
 ----
 include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_25,indent=0]
@@ -662,6 +672,11 @@ include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_25
 
 [source, groovy]
 ----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_41,indent=0]
+----
+
+[source, groovy]
+----
 include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_39,indent=0]
 ----
 
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 bd944f1..9eb19cd 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
@@ -5815,7 +5815,7 @@ class GinqTest {
 
     @Test
     void "testGinq - window - 84"() {
-        assertScript '''
+        assertGinqScript '''
             assert [[3, 1, 1], [3, 1, 1], [2, 3, 2], [2, 3, 2], [1, 5, 3]] == GQ {
                 from n in [3, 3, 2, 2, 1]
                 select n, 
@@ -5826,6 +5826,32 @@ class GinqTest {
     }
 
     @Test
+    void "testGinq - window - 85"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_41[]
+            assert [[60, 0, 0.4], [60, 0, 0.4], [80, 0.5, 0.8], [80, 0.5, 0.8], [100, 1, 1]] == GQ {
+                from n in [60, 60, 80, 80, 100]
+                select n,
+                    (percentRank() over(orderby n)),
+                    (cumeDist() over(orderby n))
+            }.toList()
+// end::ginq_winfunction_41[]
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 86"() {
+        assertGinqScript '''
+            assert [[100, 0, 0.2], [80, 0.25, 0.6], [80, 0.25, 0.6], [60, 0.75, 1], [60, 0.75, 1]] == GQ {
+                from n in [100, 80, 80, 60, 60]
+                select n,
+                    (percentRank() over(orderby n in desc)),
+                    (cumeDist() over(orderby n in desc))
+            }.toList()
+        '''
+    }
+
+    @Test
     void "testGinq - shutdown - 0"() {
         assertScript '''
             import org.apache.groovy.ginq.provider.collection.runtime.QueryableHelper