[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:
Matthew Jason Benson 2011-07-17 04:22:51 +00:00
parent aee1b76fb6
commit 81b5435a3a
2 changed files with 191 additions and 0 deletions

View File

@ -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
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/** /**

View File

@ -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());
}
}
} }