001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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.apache.commons.functor.aggregator.functions;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022
023import org.apache.commons.functor.Function;
024
025/**
026 * Aggregator function to be used with subclasses of
027 * {@link org.apache.commons.functor.aggregator.AbstractListBackedAggregator}
028 * which computes the <a href="http://en.wikipedia.org/wiki/Median">median</a>
029 * of all the numbers in the list.
030 */
031public final class IntegerMedianValueAggregatorFunction implements Function<List<Integer>, Integer> {
032    /**
033     * Flag to indicate whether we are going to operate on a copy of the list
034     * given or not. In order to compute the median, we need to sort the list
035     * first (and then choose the item in the middle). This function offers 2
036     * ways of doing the sorting:
037     * <ul>
038     * <li>by sorting (modifying) the original list (<code>useCopy=false</code>)
039     * </li>
040     * <li>by operating on a copy of the original list and leaving the original
041     * untouched (<code>useCopy=true</code>)</li>
042     * </ul>
043     * NOTE: While using a copy ensures the original list is untouched, it does
044     * mean we are creating a temporary list for the purpose of this computation
045     * so it will have an impact on memory!
046     */
047    private boolean useCopy;
048
049    /**
050     * By default create a function which will operate on a copy of the original
051     * list ({@link #useCopy} = true).
052     *
053     * @see #useCopy
054     */
055    public IntegerMedianValueAggregatorFunction() {
056        this(true);
057    }
058
059    /**
060     * Constructor which allows the caller to specify whether to operate on the
061     * original list or a copy of it.
062     *
063     * @param useCopy
064     *            Set to true to operate on a copy of the list or false to
065     *            operate on the original list.
066     * @see #useCopy
067     */
068    public IntegerMedianValueAggregatorFunction(boolean useCopy) {
069        this.useCopy = useCopy;
070    }
071
072    /**
073     * Getter for {@link #useCopy}.
074     *
075     * @return Current value of {@link #useCopy}.
076     * @see #useCopy
077     */
078    public boolean isUseCopy() {
079        return useCopy;
080    }
081
082    /**
083     * Sorts the given list and chooses the median value. The sorting can be
084     * carried out against the original list or a copy of it, based on the value
085     * of {@link #useCopy}.
086     *
087     * @param data
088     *            List to compute the median value for
089     * @return the median value of the given list or <code>null</code> if the
090     *         list is <code>null</code> or empty.
091     */
092    public Integer evaluate(List<Integer> data) {
093        if (data == null || data.size() == 0) {
094            return null;
095        }
096        // if only one element in it, it is the mean
097        if (data.size() == 1) {
098            return data.get(0);
099        }
100        List<Integer> copy = data;
101        if (useCopy) {
102            copy = new ArrayList<Integer>(data);
103        }
104        Collections.sort(copy);
105        int n = copy.size();
106        int middle = n / 2;
107        if (n % 2 == 0) {
108            // need to compute the mean of middle and middle-1 (zero based
109            // index!)
110            return (copy.get(middle) + copy.get(middle - 1)) / 2;
111        }
112
113        // we're already positioned on the element in the middle so just return
114        // it
115        return copy.get(middle);
116    }
117
118    @Override
119    public String toString() {
120        return IntegerMedianValueAggregatorFunction.class.getName();
121    }
122}