对一个 List<Number> 进行排序

6
如何对一个 List<Number> 进行排序?
示例:
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));
5个回答

12
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);
    }
});

请查看Andreas_D答案以获取解释。在上面的代码中,所有的null值和+Infinity值都被处理,以便它们移动到末尾。

更新1:

正如jarnbjoaioobe指出上述实现方案存在一个缺陷。因此,我认为最好限制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);

    }
});

更新2:

使用 guava受限制的列表不允许将空值或不支持的类型添加到列表中):

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);

3
请注意,如果双精度值相差太大,此方法将失败。我建议将它们转换为Double值并调用compareTo方法,或者使用大于/小于运算符。 - Jon Skeet
1
@Andreas_D:无论如何,空值都会抛出异常。你有更好的处理方法吗? - Emil
1
@Emil:在那里加入一个条件,这样所有的“null”都会放在最后。 - Adeel Ansari
1
这是我的想法:在compare方法中,检查o1和o2是否是Comparable的实例。如果是,则Java代码已经为数字类型实现了最佳比较 - 通过调用o1.compareTo(o2)来使用它。如果它没有实现Comparable,我建议使用BigDecimal而不是Double - madhurtanwani
1
好问题 +1 但是回答不好,**-1**。使用aioobe提供的解决方案 - 它有效,而这个不行! - dacwe
显示剩余8条评论

3
jarnbjo他的答案中指出的那样,没有办法正确实现一个Comparator<Number>,因为Number的实例可能表示大于Double.MAX_VALUE的数字(而这恰好是Number接口允许我们“看到”的最大值)。一个大于Double.MAX_VALUENumber示例为:
new BigDecimal("" + Double.MAX_VALUE).multiply(BigDecimal.TEN)

下面的解决方案可以处理:
  • ByteShortIntegerLongFloatDouble

  • 任意大的 BigInteger

  • 任意大的 BigDecimal

  • {Double, Float}.NEGATIVE_INFINITY{Double, Float}.POSITIVE_INFINITY 的实例

    请注意,即使 BigDecimal.doubleValue 可能会返回 Double.NEGATIVE_INFINITYDouble.POSITIVE_INFINITY,这些实例也应该在任何 BigDecimal 之前 / 之后。

  • null 元素

  • 上述所有元素的混合,以及

  • 未知实现 Comparable 接口的 Number 实现。

    (这似乎是一个合理的假设,因为标准 API 中的所有 Number 都实现了 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;
    }
}

这是一个小测试程序(这是一个 ideone.com演示):
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"; }
    }
}

...它会打印出以下结果(我去掉了一些零):

-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

@aioobe:你的实现很好,但我认为最好在运行时限制类型,因为可以创建任何新的Number实现,并且在同一个比较器中处理所有情况是不切实际的。此外,我们无法从数字实例中获取大于double的数字。请查看我的更新答案。 - Emil
好的,你要求一种对Number进行排序的方法,而不是对IntegerDoubleFloatShortByte等混合类型进行排序的方法,对吗?我认为这个问题无法完全解决,但你可以接近解决。你说:“此外,我们不能从数字实例中获取大于double的数字。”这取决于你是否指的是Number这个。(当然,我们可以从Number中获取大于double的数字。) - aioobe
aioobe:是的,你说得对。我确实这样问过,但当时我不知道BigInteger和BigDecimal也实现了Number接口。无论如何,感谢你的回答。 - Emil
@Emil,现在你知道了 :-) ,也知道了在排序“数字”方面你能走多远。我们两个的解决方案都可能在运行时遇到奇怪的“数字”而失败,但我的解决方案会比你的更进一步。 - aioobe
aioobe:你的实现不需要非常奇怪的数字就会失败。对于大值,长整型比双精度浮点数具有更高的精度,因此使用 doubleValue() 作为后备方案将无法比较 Long.MAX_VALUE 和 (Long.MAX_VALUE-1),因为它们在转换为双精度浮点数后是相等的。 - jarnbjo
显示剩余3条评论

2
简单回答:你无法做到。专有的数字实现可能具有比 Number 接口中定义的 getXXX() 方法提供的实际值更高的精度或更大的值范围。

@Emil:不写很多显然的代码是不可能的。如果您有两个内部值为“Double.MAX_VALUE * 2”和“Double.MAX_VALUE * 3”的Number实例,它们的getDouble()实现必须截断该值以适应double范围,因此可能都返回Double.MAX_VALUE,这使得无法为这些类型实现通用比较器。 - jarnbjo
@jarnbjo:你是在谈论BigDecimal或BigInteger的Number实例吗? - Emil
1
@Emil:这只是一些例子。任何人都可以编写自己的Number接口实现,你不必将考虑范围限制在标准API中的类上。 - jarnbjo
@jarnbjo:是的,我明白。那么有没有办法使用泛型来限制特定数量的实现? - Emil
很遗憾,即使是标准API中的Number实现也没有其他常见类型可用作通用限制。 - jarnbjo

2

由于集合中可能存在null值,因此您需要一个处理方案 - 您无法创建不接受null的对象集合。

所以,您可以检查是否为null并抛出IllegalArgumentException异常 - 副作用是您将无法对"污染"列表进行排序,并且必须在运行时处理这些异常。

另一种想法是将null转换为某种数字。我展示了这种方法(基于您自己的答案),通过惯例将任何null转换为Double.NaN。如果您希望将null值排序到两端,则还可以考虑将它们转换为0Double.POSITIVE_INFINITYDouble.NEGATIVE_INFINITY

Collections.sort(li,new Comparator<Number>() {
    @Override
    public int compare(Number o1, Number o2) {

        // null values converted to NaN by convention
        Double d1= (o1 == null) ? Double.NaN : o1.doubleValue();
        Double d2= (o2 == null) ? Double.NaN : o2.doubleValue();

        return  d1.compareTo(d2);
    }
});

更多信息

以下是一些代码,展示了如何通过“默认”处理这些特殊值:

Set<Double> doubles = new TreeSet<Double>();
doubles.add(0.);
// doubles.add(null);   // uncommenting will lead to an exception!
doubles.add(Double.NaN);
doubles.add(Double.POSITIVE_INFINITY);
doubles.add(Double.NEGATIVE_INFINITY);

for (Double d:doubles) System.out.println(d);

没有添加null的结果是:

-Infinity
0.0
Infinity
NaN

请注意,此实现可能会将 new BigDecimal("" + Double.MAX_VALUE).multiply(BigDecimal.TEN) 视为大于 Double.POSITIVE_INFINITY - aioobe

0

试试我的Java排序算法:

package drawFramePackage;

import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Random;

public class QuicksortAlgorithm {
    ArrayList<AffineTransform> affs;
    ListIterator<AffineTransform> li;
    Integer count, count2;

    /**
     * @param args
     */
    public static void main(String[] args) {
        new QuicksortAlgorithm();
    }

    public QuicksortAlgorithm(){
        count = new Integer(0);
        count2 = new Integer(1);
        affs = new ArrayList<AffineTransform>();

        for (int i = 0; i <= 128; i++) {
            affs.add(new AffineTransform(1, 0, 0, 1, new Random().nextInt(1024), 0));
        }

        affs = arrangeNumbers(affs);
        printNumbers();
    }

    public ArrayList<AffineTransform> arrangeNumbers(ArrayList<AffineTransform> list) {
        while (list.size() > 1 && count != list.size() - 1) {
            if (list.get(count2).getTranslateX() > list.get(count).getTranslateX()) {
                list.add(count, list.get(count2));
                list.remove(count2 + 1);
            }

            if (count2 == list.size() - 1) {
                count++;
                count2 = count + 1;
            } else {
                count2++;
            }
        }
        return list;
    }

    public void printNumbers(){
        li = affs.listIterator();

        while (li.hasNext()) {
            System.out.println(li.next());
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接