From 81b5435a3a484cb81ac0e6ec70bc88c97d567d1c Mon Sep 17 00:00:00 2001 From: Matthew Jason Benson Date: Sun, 17 Jul 2011 04:22:51 +0000 Subject: [PATCH] [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 --- .../org/apache/commons/lang3/ObjectUtils.java | 82 +++++++++++++ .../apache/commons/lang3/ObjectUtilsTest.java | 109 ++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/src/main/java/org/apache/commons/lang3/ObjectUtils.java b/src/main/java/org/apache/commons/lang3/ObjectUtils.java index a0a8ecf1a..71ce839a1 100644 --- a/src/main/java/org/apache/commons/lang3/ObjectUtils.java +++ b/src/main/java/org/apache/commons/lang3/ObjectUtils.java @@ -20,8 +20,14 @@ import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; 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.mutable.MutableInt; /** *

Operations on {@code Object}.

@@ -410,6 +416,82 @@ public class ObjectUtils { 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 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 median(T... items) { + Validate.notEmpty(items); + Validate.noNullElements(items); + TreeSet sort = new TreeSet(); + 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 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 median(Comparator comparator, T... items) { + Validate.notEmpty(items, "null/empty items"); + Validate.noNullElements(items); + Validate.notNull(comparator, "null comparator"); + TreeSet sort = new TreeSet(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 + * @param items to check + * @return most populous T, {@code null} if non-unique or no items supplied + */ + public static T mode(T... items) { + if (ArrayUtils.isNotEmpty(items)) { + HashMap occurrences = new HashMap(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 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 //----------------------------------------------------------------------- /** diff --git a/src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java b/src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java index 71dffd0bb..4d0c839ba 100644 --- a/src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java @@ -23,6 +23,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.Comparator; import java.util.Date; import java.util.List; @@ -288,6 +289,75 @@ public class ObjectUtilsTest { 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. 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) 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. */ @@ -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 { + + public int compare(CharSequence o1, CharSequence o2) { + return o1.toString().compareTo(o2.toString()); + } + + } }