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