[LANG-723] Add mode and median Comparable... methods to ObjectUtils
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1147522 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
aee1b76fb6
commit
81b5435a3a
|
@ -20,8 +20,14 @@ import java.io.Serializable;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.apache.commons.lang3.exception.CloneFailedException;
|
import org.apache.commons.lang3.exception.CloneFailedException;
|
||||||
|
import org.apache.commons.lang3.mutable.MutableInt;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Operations on {@code Object}.</p>
|
* <p>Operations on {@code Object}.</p>
|
||||||
|
@ -410,6 +416,82 @@ public class ObjectUtils {
|
||||||
return c1.compareTo(c2);
|
return c1.compareTo(c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the "best guess" middle value among comparables. If there is an even
|
||||||
|
* number of total values, the lower of the two middle values will be returned.
|
||||||
|
* @param <T> type of values processed by this method
|
||||||
|
* @param items to compare
|
||||||
|
* @return T at middle position
|
||||||
|
* @throws NullPointerException if items is {@code null}
|
||||||
|
* @throws IllegalArgumentException if items is empty or contains {@code null} values
|
||||||
|
*/
|
||||||
|
public static <T extends Comparable<? super T>> T median(T... items) {
|
||||||
|
Validate.notEmpty(items);
|
||||||
|
Validate.noNullElements(items);
|
||||||
|
TreeSet<T> sort = new TreeSet<T>();
|
||||||
|
Collections.addAll(sort, items);
|
||||||
|
@SuppressWarnings("unchecked") //we know all items added were T instances
|
||||||
|
T result = (T) sort.toArray()[(sort.size() - 1) / 2];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the "best guess" middle value among comparables. If there is an even
|
||||||
|
* number of total values, the lower of the two middle values will be returned.
|
||||||
|
* @param <T> type of values processed by this method
|
||||||
|
* @param comparator to use for comparisons
|
||||||
|
* @param items to compare
|
||||||
|
* @return T at middle position
|
||||||
|
* @throws NullPointerException if items or comparator is {@code null}
|
||||||
|
* @throws IllegalArgumentException if items is empty or contains {@code null} values
|
||||||
|
*/
|
||||||
|
public static <T> T median(Comparator<T> comparator, T... items) {
|
||||||
|
Validate.notEmpty(items, "null/empty items");
|
||||||
|
Validate.noNullElements(items);
|
||||||
|
Validate.notNull(comparator, "null comparator");
|
||||||
|
TreeSet<T> sort = new TreeSet<T>(comparator);
|
||||||
|
Collections.addAll(sort, items);
|
||||||
|
@SuppressWarnings("unchecked") //we know all items added were T instances
|
||||||
|
T result = (T) sort.toArray()[(sort.size() - 1) / 2];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Find the most frequently occurring item.
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* @param items to check
|
||||||
|
* @return most populous T, {@code null} if non-unique or no items supplied
|
||||||
|
*/
|
||||||
|
public static <T> T mode(T... items) {
|
||||||
|
if (ArrayUtils.isNotEmpty(items)) {
|
||||||
|
HashMap<T, MutableInt> occurrences = new HashMap<T, MutableInt>(items.length);
|
||||||
|
for (T t : items) {
|
||||||
|
MutableInt count = occurrences.get(t);
|
||||||
|
if (count == null) {
|
||||||
|
occurrences.put(t, new MutableInt(1));
|
||||||
|
} else {
|
||||||
|
count.increment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
T result = null;
|
||||||
|
int max = 0;
|
||||||
|
for (Map.Entry<T, MutableInt> e : occurrences.entrySet()) {
|
||||||
|
int cmp = e.getValue().intValue();
|
||||||
|
if (cmp == max) {
|
||||||
|
result = null;
|
||||||
|
} else if (cmp > max) {
|
||||||
|
max = cmp;
|
||||||
|
result = e.getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// cloning
|
// cloning
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -288,6 +289,75 @@ public class ObjectUtilsTest {
|
||||||
assertEquals("one two true", -1, ObjectUtils.compare(one, two, true));
|
assertEquals("one two true", -1, ObjectUtils.compare(one, two, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMedian() {
|
||||||
|
assertEquals("foo", ObjectUtils.median("foo"));
|
||||||
|
assertEquals("bar", ObjectUtils.median("foo", "bar"));
|
||||||
|
assertEquals("baz", ObjectUtils.median("foo", "bar", "baz"));
|
||||||
|
assertEquals("baz", ObjectUtils.median("foo", "bar", "baz", "blah"));
|
||||||
|
assertEquals("blah", ObjectUtils.median("foo", "bar", "baz", "blah", "wah"));
|
||||||
|
assertEquals(Integer.valueOf(5),
|
||||||
|
ObjectUtils.median(Integer.valueOf(1), Integer.valueOf(5), Integer.valueOf(10)));
|
||||||
|
assertEquals(
|
||||||
|
Integer.valueOf(7),
|
||||||
|
ObjectUtils.median(Integer.valueOf(5), Integer.valueOf(6), Integer.valueOf(7), Integer.valueOf(8),
|
||||||
|
Integer.valueOf(9)));
|
||||||
|
assertEquals(Integer.valueOf(6),
|
||||||
|
ObjectUtils.median(Integer.valueOf(5), Integer.valueOf(6), Integer.valueOf(7), Integer.valueOf(8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testMedian_nullItems() {
|
||||||
|
ObjectUtils.median((String[]) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testMedian_emptyItems() {
|
||||||
|
ObjectUtils.<String> median();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorMedian() {
|
||||||
|
CharSequenceComparator cmp = new CharSequenceComparator();
|
||||||
|
NonComparableCharSequence foo = new NonComparableCharSequence("foo");
|
||||||
|
NonComparableCharSequence bar = new NonComparableCharSequence("bar");
|
||||||
|
NonComparableCharSequence baz = new NonComparableCharSequence("baz");
|
||||||
|
NonComparableCharSequence blah = new NonComparableCharSequence("blah");
|
||||||
|
NonComparableCharSequence wah = new NonComparableCharSequence("wah");
|
||||||
|
assertSame(foo, ObjectUtils.median(cmp, foo));
|
||||||
|
assertSame(bar, ObjectUtils.median(cmp, foo, bar));
|
||||||
|
assertSame(baz, ObjectUtils.median(cmp, foo, bar, baz));
|
||||||
|
assertSame(baz, ObjectUtils.median(cmp, foo, bar, baz, blah));
|
||||||
|
assertSame(blah, ObjectUtils.median(cmp, foo, bar, baz, blah, wah));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testComparatorMedian_nullComparator() {
|
||||||
|
ObjectUtils.median((Comparator<CharSequence>) null, new NonComparableCharSequence("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testComparatorMedian_nullItems() {
|
||||||
|
ObjectUtils.median(new CharSequenceComparator(), (CharSequence[]) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testComparatorMedian_emptyItems() {
|
||||||
|
ObjectUtils.median(new CharSequenceComparator());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void testMode() {
|
||||||
|
assertNull(ObjectUtils.mode((Object[]) null));
|
||||||
|
assertNull(ObjectUtils.mode());
|
||||||
|
assertNull(ObjectUtils.mode("foo", "bar", "baz"));
|
||||||
|
assertNull(ObjectUtils.mode("foo", "bar", "baz", "foo", "bar"));
|
||||||
|
assertEquals("foo", ObjectUtils.mode("foo", "bar", "baz", "foo"));
|
||||||
|
assertEquals(Integer.valueOf(9),
|
||||||
|
ObjectUtils.mode("foo", "bar", "baz", Integer.valueOf(9), Integer.valueOf(10), Integer.valueOf(9)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link ObjectUtils#clone(Object)} with a cloneable object.
|
* Tests {@link ObjectUtils#clone(Object)} with a cloneable object.
|
||||||
*/
|
*/
|
||||||
|
@ -396,4 +466,43 @@ public class ObjectUtilsTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static final class NonComparableCharSequence implements CharSequence {
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new NonComparableCharSequence instance.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
public NonComparableCharSequence(String value) {
|
||||||
|
super();
|
||||||
|
Validate.notNull(value);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char charAt(int arg0) {
|
||||||
|
return value.charAt(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int length() {
|
||||||
|
return value.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence subSequence(int arg0, int arg1) {
|
||||||
|
return value.subSequence(arg0, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class CharSequenceComparator implements Comparator<CharSequence> {
|
||||||
|
|
||||||
|
public int compare(CharSequence o1, CharSequence o2) {
|
||||||
|
return o1.toString().compareTo(o2.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue