001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 the ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package org.conqat.lib.commons.math;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022
023import org.conqat.lib.commons.assertion.CCSMAssert;
024
025/**
026 * Calculates a given percentile. E.g. PercentileAggregator(50) calculates the
027 * median
028 */
029public class PercentileAggregator implements IAggregator {
030
031        /** The percentile being calculated */
032        private final double percentile;
033
034        /** Constructor. */
035        public PercentileAggregator(double percentile) {
036                CCSMAssert.isTrue(percentile > 0 && percentile <= 100, "Percentile must be in the range ]0, 100].");
037                this.percentile = percentile;
038        }
039
040        /**
041         * Aggregates by finding the median.
042         * 
043         * @return {@link Double#NaN} for empty input collection
044         */
045        @Override
046        public double aggregate(Collection<? extends Number> values) {
047                if (values.isEmpty()) {
048                        return Double.NaN;
049                }
050
051                // convert to doubles, as Number is not comparable
052                ArrayList<Double> doubleValues = new ArrayList<Double>();
053                for (Number value : values) {
054                        doubleValues.add(value.doubleValue());
055                }
056                Collections.sort(doubleValues);
057
058                // this value is in ]0, size()]
059                double index = doubleValues.size() * percentile / 100.;
060                return doubleValues.get((int) Math.ceil(index) - 1);
061        }
062
063        /** Returns {@link Double#NaN}. */
064        @Override
065        public double getNeutralElement() {
066                return Double.NaN;
067        }
068}