Skip to content

Commit

Permalink
Add DENSE_RANK aggregate function (#1484)
Browse files Browse the repository at this point in the history
* Add DENSE_RANK aggregate function

Initial implementation and unit tests.
  • Loading branch information
claesrosell authored Nov 7, 2023
1 parent 2125a61 commit 3954093
Show file tree
Hide file tree
Showing 7 changed files with 535 additions and 366 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,6 @@ public interface IBuildInAggregation {
String TOTAL_CONCATENATE_FUNC = "CONCATENATE";//$NON-NLS-1$
String TOTAL_RANGE_FUNC = "RANGE";//$NON-NLS-1$

String TOTAL_DENSERANK_FUNC = "DENSERANK";//$NON-NLS-1$

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ TotalCount.description=function Total.COUNT()
TotalCount.displayName=COUNT
TotalCountDistinct.description=function Total.COUNTDISTINCT()
TotalCountDistinct.displayName=COUNTDISTINCT
TotalDenseRank.description=function Total.DENSERANK()
TotalDenseRank.displayName=DENSERANK
TotalFirst.description=function Total.FIRST()
TotalFirst.displayName=FIRST
TotalIrr.description=function Total.IRR()
Expand Down Expand Up @@ -91,7 +93,7 @@ TotalQuartile.param.quart=&Quart
TotalRank.description=function Total.RANK()
TotalRank.displayName=RANK
TotalRank.param.ascending=&Ascending
TotalConcatenate.description=funtion Total.CONCATENATE()
TotalConcatenate.description=function Total.CONCATENATE()
TotalConcatenate.displayName=CONCATENATE
TotalConcatenate.param.separator=Separat&or
TotalConcatenate.param.maxLength=Ma&x length
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Map;

import org.eclipse.birt.data.aggregation.api.IBuildInAggregation;
import org.eclipse.birt.data.aggregation.impl.rank.TotalDenseRank;
import org.eclipse.birt.data.aggregation.impl.rank.TotalIsBottomN;
import org.eclipse.birt.data.aggregation.impl.rank.TotalIsBottomNPercent;
import org.eclipse.birt.data.aggregation.impl.rank.TotalIsTopN;
Expand Down Expand Up @@ -139,6 +140,10 @@ private void populateAggregations() {
final TotalRange totalRange = new TotalRange();
aggrMap.put(IBuildInAggregation.TOTAL_RANGE_FUNC, totalRange);
aggregations.add(totalRange);

final TotalDenseRank totalDenseRank = new TotalDenseRank();
aggrMap.put(IBuildInAggregation.TOTAL_DENSERANK_FUNC, totalDenseRank);
aggregations.add(totalDenseRank);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package org.eclipse.birt.data.aggregation.impl.rank;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.eclipse.birt.core.data.DataType;
import org.eclipse.birt.data.aggregation.i18n.Messages;
import org.eclipse.birt.data.aggregation.impl.AggrFunction;
import org.eclipse.birt.data.aggregation.impl.Constants;
import org.eclipse.birt.data.aggregation.impl.ParameterDefn;
import org.eclipse.birt.data.aggregation.impl.RunningAccumulator;
import org.eclipse.birt.data.aggregation.impl.SupportedDataTypes;
import org.eclipse.birt.data.engine.api.aggregation.IParameterDefn;
import org.eclipse.birt.data.engine.core.DataException;

/**
* @since 3.3
*
*/
abstract class BaseTotalRank extends AggrFunction {


/*
* (non-Javadoc)
*
* @see org.eclipse.birt.data.engine.aggregation.Aggregation#getType()
*/
@Override
public int getType() {
return RUNNING_AGGR;
}

/*
* (non-Javadoc)
*
* @see org.eclipse.birt.data.engine.api.aggregation.IAggregation#getDateType()
*/
@Override
public int getDataType() {
return DataType.INTEGER_TYPE;
}

/*
* (non-Javadoc)
*
* @see org.eclipse.birt.data.engine.aggregation.Aggregation#getParameterDefn()
*/
@Override
public IParameterDefn[] getParameterDefn() {
return new IParameterDefn[] {
new ParameterDefn(Constants.EXPRESSION_NAME, Constants.EXPRESSION_DISPLAY_NAME, false, true,
SupportedDataTypes.CALCULATABLE, ""), //$NON-NLS-1$
new ParameterDefn("ascending", Messages.getString("TotalRank.param.ascending"), true, false, //$NON-NLS-1$ //$NON-NLS-2$
SupportedDataTypes.ANY, "") //$NON-NLS-1$
};
}

/*
*
*/
@Override
public int getNumberOfPasses() {
return 2;
}

static class TotalRankAccumulator extends RunningAccumulator {

private Integer sum;
private List<Object> cachedValues;
private Map<Object, Integer> rankMap;
private boolean asc;
private boolean denseRank;
private boolean hasInitialized;
private int passCount = 0;
private Comparator<Object> comparator;

/**
* @param denseRank If the dense rank algorithm should be used or not
*
*/
public TotalRankAccumulator(boolean denseRank) {
this.denseRank = denseRank;
}

@Override
public void start() {
if (passCount == 0) {
cachedValues = new ArrayList<>();
rankMap = new HashMap<>();
sum = 0;
asc = true;
comparator = RankObjComparator.INSTANCE; // Sorting for ascending. Can change later
hasInitialized = false;
}
passCount++;
}

/*
* (non-Javadoc)
*
* @see
* org.eclipse.birt.data.engine.aggregation.Accumulator#onRow(java.lang.Object[]
* )
*/
@Override
public void onRow(Object[] args) throws DataException {
assert (args.length > 0);
if (passCount == 1) {
if (args[0] != null) {
cachedValues.add(args[0]);
} else {
cachedValues.add(RankAggregationUtil.getNullObject());
}
if ((!hasInitialized) && args[1] != null) {
// Here, the ascending/descending sort order can change from defaults depending
// on the second argument
hasInitialized = true;
if (args[1].toString().equals("false")) { //$NON-NLS-1$
asc = false;
} else if (args[1] instanceof Double && ((Double) args[1]).equals(Double.valueOf(0))) {
asc = false;
} else {
asc = true;
}

// Reverse the sorting if the sorting is not ascending
if (!this.asc) {
comparator = RankObjComparator.INSTANCE.reversed();
}
}
} else {
Object compareValue;
if (args[0] != null) {
compareValue = args[0];
} else {
compareValue = RankAggregationUtil.getNullObject();
}

Integer calculatedRank = this.rankMap.get(compareValue);
sum = calculatedRank != null ? calculatedRank.intValue() : -1;
}
}

@Override
public void finish() throws DataException {
if (this.passCount == 1) {
Collections.sort(cachedValues, comparator);
calculateRank(cachedValues, this.denseRank);
}
}

/**
* Precondition: The parameter <code>objs</code> should be sorted acsending
* previously. Note: rank is 1-based. ex.
* <code>The following table give details:
* Value | Rank
* 20 | 4
* 10 | 5
* 30 | 2
* 30 | 2
* 40 | 1
* </code>
*
* @param key
* @return rank
*/
private void calculateRank(List<Object> sortedList, boolean useDenseRank) {

int currentRank = 1;
int currentDenseRank = 1;
Object currentValue = sortedList.get(0);

for (int i = 0; i < sortedList.size(); i++) {
Object integer = sortedList.get(i);
if (!Objects.equals(integer, currentValue)) {

rankMap.put(currentValue, useDenseRank ? currentDenseRank : currentRank);
currentValue = integer;
currentDenseRank++;
currentRank = i + 1;
}

}

// Handle the last integer in the list
rankMap.put(currentValue, useDenseRank ? currentDenseRank : currentRank);
}

/*
* (non-Javadoc)
*
* @see org.eclipse.birt.data.engine.api.aggregation.Accumulator#getValue()
*/
@Override
public Object getValue() throws DataException {
return sum;
}
}

/*
*
*/
static class RankObjComparator implements Comparator<Object> {

public static RankObjComparator INSTANCE = new RankObjComparator();

private RankObjComparator() {
// No need
}

@Override
public int compare(Object o1, Object o2) {// for efficiency, we assure o1 and o2 can be just Comparable or
// NullObject
if (o1 instanceof Comparable) {
if (o2 instanceof Comparable) {// Comparable ? Comparable
@SuppressWarnings("unchecked")
Comparable<Object> obj1 = (Comparable<Object>) o1;
@SuppressWarnings("unchecked")
Comparable<Object> obj2 = (Comparable<Object>) o2;
return obj1.compareTo(obj2);
}
return 1;// Comparable > NullObject
} else if (o2 instanceof Comparable) {// NullObject < Comparable
return -1;
} else {// NullObject == NullObject
return 0;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.eclipse.birt.data.aggregation.impl.rank;

import org.eclipse.birt.data.aggregation.api.IBuildInAggregation;
import org.eclipse.birt.data.aggregation.i18n.Messages;
import org.eclipse.birt.data.engine.api.aggregation.Accumulator;

/**
* @since 3.3
*
*/
public class TotalDenseRank extends BaseTotalRank {

/*
* (non-Javadoc)
*
* @see org.eclipse.birt.data.engine.aggregation.Aggregation#getName()
*/
@Override
public String getName() {
return IBuildInAggregation.TOTAL_DENSERANK_FUNC;
}

/*
* (non-Javadoc)
*
* @see org.eclipse.birt.data.engine.aggregation.Aggregation#newAccumulator()
*/
@Override
public Accumulator newAccumulator() {
return new TotalRankAccumulator(true);
}

/*
* (non-Javadoc)
*
* @see
* org.eclipse.birt.data.engine.api.aggregation.IAggrFunction#getDescription()
*/
@Override
public String getDescription() {
return Messages.getString("TotalDenseRank.description"); //$NON-NLS-1$
}

/*
* (non-Javadoc)
*
* @see
* org.eclipse.birt.data.engine.api.aggregation.IAggrFunction#getDisplayName()
*/
@Override
public String getDisplayName() {
return Messages.getString("TotalDenseRank.displayName"); //$NON-NLS-1$
}
}
Loading

0 comments on commit 3954093

Please sign in to comment.