How to sort a List<Number>
?
Example:
List<Number> li = new ArrayList<Number>(); //list of numbers
li.add(new Integer(20));
li.add(new Double(12.2));
li.add(new Float(1.2));
Collections.sort(li,new Comparator<Number>() {
@Override
public int compare(Number o1, Number o2) {
Double d1 = (o1 == null) ? Double.POSITIVE_INFINITY : o1.doubleValue();
Double d2 = (o2 == null) ? Double.POSITIVE_INFINITY : o2.doubleValue();
return d1.compareTo(d2);
}
});
Have a look at Andreas_D's answer for explanation.In the above code all null values and +Infinity values are handled such that they move to the end.
Update 1:
As jarnbjo and aioobe points out a flaw in the above implementation.So I thought it's better to restrict the implementation's of Number.
Collections.sort(li, new Comparator<Number>() {
HashSet<Class<? extends Number>> allowedTypes;
{
allowedTypes = new HashSet<Class<? extends Number>>();
allowedTypes.add(Integer.class);
allowedTypes.add(Double.class);
allowedTypes.add(Float.class);
allowedTypes.add(Short.class);
allowedTypes.add(Byte.class);
}
@Override
public int compare(Number o1, Number o2) {
Double d1 = (o1 == null) ? Double.POSITIVE_INFINITY : o1.doubleValue();
Double d2 = (o2 == null) ? Double.POSITIVE_INFINITY : o2.doubleValue();
if (o1 != null && o2 != null) {
if (!(allowedTypes.contains(o1.getClass()) && allowedTypes.contains(o2.getClass()))) {
throw new UnsupportedOperationException("Allowed Types:" + allowedTypes);
}
}
return d1.compareTo(d2);
}
});
Update 2:
Using guava's constrained list (will not allow entry of null or unsupported type to list):
List<Number> li = Constraints.constrainedList(new ArrayList<Number>(),
new Constraint<Number>() {
HashSet<Class<? extends Number>> allowedTypes;
{
allowedTypes = new HashSet<Class<? extends Number>>();
allowedTypes.add(Integer.class);
allowedTypes.add(Double.class);
allowedTypes.add(Float.class);
allowedTypes.add(Short.class);
allowedTypes.add(Byte.class);
}
@Override
public Number checkElement(Number arg0) {
if (arg0 != null) {
if (allowedTypes.contains(arg0.getClass())) {
return arg0;
}
}
throw new IllegalArgumentException("Type Not Allowed");
}
}
);
li.add(Double.POSITIVE_INFINITY);
li.add(new Integer(20));
li.add(new Double(12.2));
li.add(new Float(1.2));
li.add(Double.NEGATIVE_INFINITY);
li.add(Float.NEGATIVE_INFINITY);
// li.add(null); //throws exception
// li.add(new BigInteger("22"); //throws exception
li.add(new Integer(20));
System.out.println(li);
Collections.sort(li, new Comparator<Number>() {
@Override
public int compare(Number o1, Number o2) {
Double d1 = o1.doubleValue();
Double d2 = o2.doubleValue();
return d1.compareTo(d2);
}
});
System.out.println(li);
As jarnbjo points out in his answer, there is no way to implement a Comparator<Number>
correctly, as instances of Number
may very well represent numbers larger than Double.MAX_VALUE
(and that's unfortunately as far as the Number
interface allows us to "see"). An example of a Number
larger than Double.MAX_VALUE
is
new BigDecimal("" + Double.MAX_VALUE).multiply(BigDecimal.TEN)
The solution below however, handles
Byte
s, Short
s, Integer
s, Long
s, Float
s and Double
s
Arbitrary large BigInteger
s
Arbitrary large BigDecimal
s
Instances of {Double, Float}.NEGATIVE_INFINITY
and {Double, Float}.POSITIVE_INFINITY
Note that these should always come before/after any BigDecimal
even though the BigDecimal.doubleValue
may return Double.NEGATIVE_INFINITY
or Double.POSITIVE_INFINITY
null
elements
A mixture of all of the above, and
Unknown implementations of Number
that also implements Comparable
.
(This seems to be a reasonable assumption since all Number
s in the standard API implements Comparable.)
@SuppressWarnings("unchecked")
class NumberComparator implements Comparator<Number> {
// Special values that are treated as larger than any other.
private final static List<?> special =
Arrays.asList(Double.NaN, Float.NaN, null);
private final static List<?> largest =
Arrays.asList(Double.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
private final static List<?> smallest =
Arrays.asList(Double.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
public int compare(Number n1, Number n2) {
// Handle special cases (including null)
if (special.contains(n1)) return 1;
if (special.contains(n2)) return -1;
if (largest.contains(n1) || smallest.contains(n2)) return 1;
if (largest.contains(n2) || smallest.contains(n1)) return -1;
// Promote known values (Byte, Integer, Long, Float, Double and
// BigInteger) to BigDecimal, as this is the most generic known type.
BigDecimal bd1 = asBigDecimal(n1);
BigDecimal bd2 = asBigDecimal(n2);
if (bd1 != null && bd2 != null)
return bd1.compareTo(bd2);
// Handle arbitrary Number-comparisons if o1 and o2 are of same class
// and implements Comparable.
if (n1 instanceof Comparable<?> && n2 instanceof Comparable<?>)
try {
return ((Comparable) n1).compareTo((Comparable) n2);
} catch (ClassCastException cce) {
}
// If the longValue()s differ between the two numbers, trust these.
int longCmp = ((Long) n1.longValue()).compareTo(n2.longValue());
if (longCmp != 0)
return longCmp;
// Pray to god that the doubleValue()s differ between the two numbers.
int doubleCmp = ((Double) n1.doubleValue()).compareTo(n2.doubleValue());
if (doubleCmp != 0)
return longCmp;
// Die a painful death...
throw new UnsupportedOperationException(
"Cannot compare " + n1 + " with " + n2);
}
// Convert known Numbers to BigDecimal, and the argument n otherwise.
private BigDecimal asBigDecimal(Number n) {
if (n instanceof Byte) return new BigDecimal((Byte) n);
if (n instanceof Integer) return new BigDecimal((Integer) n);
if (n instanceof Short) return new BigDecimal((Short) n);
if (n instanceof Long) return new BigDecimal((Long) n);
if (n instanceof Float) return new BigDecimal((Float) n);
if (n instanceof Double) return new BigDecimal((Double) n);
if (n instanceof BigInteger) return new BigDecimal((BigInteger) n);
if (n instanceof BigDecimal) return (BigDecimal) n;
return null;
}
}
Here is a small test program (here is an ideone.com demo):
public class Main {
public static void main(String[] args) {
List<Number> li = new ArrayList<Number>();
// Add an Integer, a Double, a Float, a Short, a Byte and a Long.
li.add(20); li.add((short) 17);
li.add(12.2); li.add((byte) 100);
li.add(0.2f); li.add(19518926L);
li.add(Double.NaN); li.add(Double.NEGATIVE_INFINITY);
li.add(Float.NaN); li.add(Double.POSITIVE_INFINITY);
// A custom Number
li.add(new BoolNumber(1));
li.add(new BoolNumber(0));
// Add two BigDecimal that are larger than Double.MAX_VALUE.
BigDecimal largeDec = new BigDecimal("" + Double.MAX_VALUE);
li.add(largeDec/*.multiply(BigDecimal.TEN)*/);
li.add(largeDec.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN));
// Add two BigInteger that are larger than Double.MAX_VALUE.
BigInteger largeInt = largeDec.toBigInteger().add(BigInteger.ONE);
li.add(largeInt.multiply(BigInteger.TEN));
li.add(largeInt.multiply(BigInteger.TEN).multiply(BigInteger.TEN));
// ...and just for fun...
li.add(null);
Collections.shuffle(li);
Collections.sort(li, new NumberComparator());
for (Number num : li)
System.out.println(num);
}
static class BoolNumber extends Number {
boolean b;
public BoolNumber(int i) { b = i != 0; }
public double doubleValue() { return b ? 1d : 0d; }
public float floatValue() { return b ? 1f : 0f; }
public int intValue() { return b ? 1 : 0; }
public long longValue() { return b ? 1L : 0L; }
public String toString() { return b ? "1" : "0"; }
}
}
...which prints (I removed a few zeros):
-Infinity
0
0.2
1
12.2
17
20
100
19518926
1.7976931348623157E+308
17976931348623157000000000...00000000010
1.797693134862315700E+310
179769313486231570000000000000...00000100
Infinity
NaN
null
NaN
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With