【问题标题】:Sorting a List<Number>排序列表<Number>
【发布时间】:2011-05-10 15:54:28
【问题描述】:

如何对List&lt;Number&gt; 进行排序?

示例:

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

【问题讨论】:

    标签: java list sorting collections


    【解决方案1】:

    试试我的 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());
            }
        }
    }
    

    【讨论】:

      【解决方案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);
          }
      });
      

      请查看Andreas_D'sanswer 进行解释。在上面的代码中,所有空值和+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's constrained list不允许输入 null 或不受支持的类型):

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

      【讨论】:

      • 请注意,如果双精度值相距太远,则会失败。我会转换为 Double 值并在它们上调用 compareTo,或者使用大于/小于运算符。
      • @Andreas_D:null 无论如何都会抛出异常。你有更好的处理方法吗?
      • @Emil: 在这里设置一个条件,所有null 最终都会成功。
      • 这是我的想法:在compare 方法中,检查o1 和o2 是否是instanceofComparable。如果是,那么 Java 代码已经实现了数字类型的最佳比较 - 使用它(通过调用 o1.compareTo(o2)。如果它没有实现 Comparable,我建议使用 BigDecimal 而不是 Double .
      • 好问题 +1 但不好的答案,-1。使用 aioobe 提供的解决方案 - 有效,但无效!
      【解决方案3】:

      正如jarnbjohis answer 中指出的那样,没有办法正确实现Comparator&lt;Number&gt;,因为Number 的实例很可能代表大于Double.MAX_VALUE 的数字(不幸的是,就Number 接口allows us to "see")。 Number 大于 Double.MAX_VALUE 的示例是

      new BigDecimal("" + Double.MAX_VALUE).multiply(BigDecimal.TEN)
      

      然而,下面的解决方案处理

      • Bytes、Shorts、Integers、Longs、Floats 和 Doubles

      • 任意大BigIntegers

      • 任意大BigDecimals

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

        请注意,即使BigDecimal.doubleValue 可能返回Double.NEGATIVE_INFINITYDouble.POSITIVE_INFINITY,它们也应始终出现在任何BigDecimal 之前/之后

      • null元素

      • 以上所有的混合,

      • Number 的未知实现也实现了Comparable

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

      ...打印(我删除了几个零):

      -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 的任何新实现,并且在同一个比较器中处理所有情况是不切实际的.此外,我们无法从数字实例中获得大于两倍的数字。检查我更新的答案。
      • 好吧,你问的是一种对Number s 进行排序的方法,而不是一种对IntegerDoubleFloatShortByte 的混合进行排序的方法, 正确的?我声称它不能完全解决,但你可以走得很远。您说,“此外,我们无法从数字实例中获得大于两倍的数字。” 取决于您是否指的是 class Number。 (当然我们可以从Numbers 得到大于两倍的Numbers。)
      • aioobe:是的,你的权利。我确实问过,但后来我不知道 BigInteger 和 BigDecimals 也实现了 Number。无论如何感谢您的回答。
      • @Emil,现在你知道了 :-) 现在你知道在排序Numbers 方面你能走多远。当输入奇怪的Numbers 时,我们的两个解决方案都可能在运行时失败,但我的解决方案会让你比你的解决方案更进一步。
      • aioobe:您的实现不需要非常奇怪的数字即可失败。对于大值,Long 的精度比 double 高得多,因此使用 doubleValue() 作为后备将无法进行比较,例如Long.MAX_VALUE 和 (Long.MAX_VALUE-1),因为它们在转换为 double 后是等价的。
      【解决方案4】:

      简单的答案:你不能。专有的 Number 实现可能比通过为 Number 接口中的实际值定义的 getXXX() 方法提供的精度更高或值范围更大。

      【讨论】:

      • @Emil:不是没有写很多明显的代码。如果您有两个内部值“Double.MAX_VALUE * 2”和“Double.MAX_VALUE * 3”的 Number 实例怎么办。他们的 getDouble() 实现必须截断值以适应 double 的范围,因此可能都返回 Double.MAX_VALUE,因此无法为这些类型实现通用比较器。
      • @jarnbjo:你说的是 BigDecimal 还是 BigInteger 的 Number 实例?
      • @Emil:它们只是示例。任何人都可以编写自己的 Number 接口实现,您不必将考虑仅限于标准 API 中的类。
      • @jarnbjo:是的,我明白了。那么有什么方法可以限制使用泛型的特定数量的实现?
      • 很遗憾没有。即使是标准 API 中的 Number 实现也没有任何其他通用类型可用作通用限制。
      【解决方案5】:

      您需要针对 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
      猜你喜欢
      • 1970-01-01
      • 2015-10-17
      • 2013-08-14
      • 1970-01-01
      • 2011-01-08
      • 1970-01-01
      • 1970-01-01
      • 2014-03-13
      • 2016-06-16
      相关资源
      最近更新 更多