/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.core.sql.functions.stat;
import com.orientechnologies.common.collection.OMultiValue;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Computes the percentile for a field. Nulls are ignored in the calculation.
*
* @author Fabrizio Fortino
*/
public class OSQLFunctionPercentile extends OSQLFunctionAbstract {
public static final String NAME = "percentile";
protected List<Double> quantiles = new ArrayList<Double>();
private List<Number> values = new ArrayList<Number>();
public OSQLFunctionPercentile() {
this(NAME, 2, -1);
}
public OSQLFunctionPercentile(final String iName, final int iMinParams, final int iMaxParams) {
super(iName, iMaxParams, iMaxParams);
}
@Override
public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, Object[] iParams,
OCommandContext iContext) {
if (quantiles.isEmpty()) { // set quantiles once
for (int i = 1; i < iParams.length; ++i) {
this.quantiles.add(Double.parseDouble(iParams[i].toString()));
}
}
if (iParams[0] instanceof Number) {
addValue((Number) iParams[0]);
} else if (OMultiValue.isMultiValue(iParams[0])) {
for (Object n : OMultiValue.getMultiValueIterable(iParams[0])) {
addValue((Number) n);
}
}
return null;
}
@Override
public boolean aggregateResults() {
return true;
}
@Override
public Object getResult() {
if (returnDistributedResult()) {
return values;
} else {
return this.evaluate(this.values);
}
}
@SuppressWarnings("unchecked")
@Override
public Object mergeDistributedResult(List<Object> resultsToMerge) {
if (returnDistributedResult()) {
List<Number> dValues = new ArrayList<Number>();
for (Object iParameter : resultsToMerge) {
dValues.addAll((List<Number>) iParameter);
}
return this.evaluate(dValues);
}
if (!resultsToMerge.isEmpty())
return resultsToMerge.get(0);
return null;
}
@Override
public String getSyntax() {
return NAME + "(<field>, <quantile> [,<quantile>*])";
}
private void addValue(Number value) {
if (value != null) {
this.values.add(value);
}
}
private Object evaluate(List<Number> iValues) {
if (iValues.isEmpty()) { // result set is empty
return null;
}
if (quantiles.size() > 1) {
List<Number> results = new ArrayList<Number>();
for (Double q : this.quantiles) {
results.add(this.evaluate(iValues, q));
}
return results;
} else {
return this.evaluate(iValues, this.quantiles.get(0));
}
}
private Number evaluate(List<Number> iValues, double iQuantile) {
Collections.sort(iValues, new Comparator<Number>() {
@Override
public int compare(Number o1, Number o2) {
Double d1 = o1.doubleValue();
Double d2 = o2.doubleValue();
return d1.compareTo(d2);
}
});
double n = iValues.size();
double pos = iQuantile * (n + 1);
if (pos < 1) {
return iValues.get(0);
}
if (pos >= n) {
return iValues.get((int) n - 1);
}
double fpos = Math.floor(pos);
int intPos = (int) fpos;
double dif = pos - fpos;
double lower = iValues.get(intPos - 1).doubleValue();
double upper = iValues.get(intPos).doubleValue();
return lower + dif * (upper - lower);
}
}