【问题标题】:How to sort an array of ints using a custom comparator?如何使用自定义比较器对整数数组进行排序?
【发布时间】:2011-04-11 13:53:47
【问题描述】:

我需要使用自定义比较器对整数数组进行排序,但 Java 的库没有为带有比较器的整数提供排序功能(比较器只能用于对象)。有什么简单的方法可以做到这一点?

【问题讨论】:

  • 您只是想对数组进行降序排序还是要执行更复杂的操作?
  • 更复杂的事情。我想使用绝对值作为键对 int 进行排序。

标签: java sorting


【解决方案1】:

如果您对性能和减少途中创建的对象数量感兴趣,请考虑使用来自eclipse collections 的实现。

它使用自定义IntComparator,它对原语进行操作,因此不需要装箱。

【讨论】:

    【解决方案2】:

    Java 8:

    Arrays.stream(new int[]{10,4,5,6,1,2,3,7,9,8}).boxed().sorted((e1,e2)-> e2-e1).collect(Collectors.toList());
    

    【讨论】:

      【解决方案3】:

      这里有一些代码(它实际上不是我最初认为的 Timsort,但它确实工作得很好)可以在没有任何装箱/拆箱的情况下完成这个技巧。在我的测试中,它的运行速度比使用 Collections.sort 和数组周围的 List 包装器快 3-4 倍。

      // This code has been contributed by 29AjayKumar 
      // from: https://www.geeksforgeeks.org/sort/
      
      static final int sortIntArrayWithComparator_RUN = 32; 
      
      // this function sorts array from left index to  
      // to right index which is of size atmost RUN  
      static void sortIntArrayWithComparator_insertionSort(int[] arr, IntComparator comparator, int left, int right) { 
          for (int i = left + 1; i <= right; i++)  
          { 
              int temp = arr[i]; 
              int j = i - 1; 
              while (j >= left && comparator.compare(arr[j], temp) > 0)
              { 
                  arr[j + 1] = arr[j]; 
                  j--; 
              } 
              arr[j + 1] = temp; 
          } 
      } 
      
      // merge function merges the sorted runs  
      static void sortIntArrayWithComparator_merge(int[] arr, IntComparator comparator, int l, int m, int r) { 
          // original array is broken in two parts  
          // left and right array  
          int len1 = m - l + 1, len2 = r - m; 
          int[] left = new int[len1]; 
          int[] right = new int[len2]; 
          for (int x = 0; x < len1; x++)  
          { 
              left[x] = arr[l + x]; 
          } 
          for (int x = 0; x < len2; x++)  
          { 
              right[x] = arr[m + 1 + x]; 
          } 
      
          int i = 0; 
          int j = 0; 
          int k = l; 
      
          // after comparing, we merge those two array  
          // in larger sub array  
          while (i < len1 && j < len2)  
          { 
              if (comparator.compare(left[i], right[j]) <= 0)
              { 
                  arr[k] = left[i]; 
                  i++; 
              } 
              else 
              { 
                  arr[k] = right[j]; 
                  j++; 
              } 
              k++; 
          } 
      
          // copy remaining elements of left, if any  
          while (i < len1) 
          { 
              arr[k] = left[i]; 
              k++; 
              i++; 
          } 
      
          // copy remaining element of right, if any  
          while (j < len2)  
          { 
              arr[k] = right[j]; 
              k++; 
              j++; 
          } 
      } 
      
      // iterative sort function to sort the  
      // array[0...n-1] (similar to merge sort)  
      static void sortIntArrayWithComparator(int[] arr, IntComparator comparator) { sortIntArrayWithComparator(arr, lIntArray(arr), comparator); }
      static void sortIntArrayWithComparator(int[] arr, int n, IntComparator comparator) { 
          // Sort individual subarrays of size RUN  
          for (int i = 0; i < n; i += sortIntArrayWithComparator_RUN)  
          { 
              sortIntArrayWithComparator_insertionSort(arr, comparator, i, Math.min((i + 31), (n - 1))); 
          } 
      
          // start merging from size RUN (or 32). It will merge  
          // to form size 64, then 128, 256 and so on ....  
          for (int size = sortIntArrayWithComparator_RUN; size < n; size = 2 * size)  
          { 
                
              // pick starting point of left sub array. We  
              // are going to merge arr[left..left+size-1]  
              // and arr[left+size, left+2*size-1]  
              // After every merge, we increase left by 2*size  
              for (int left = 0; left < n; left += 2 * size)  
              { 
                    
                  // find ending point of left sub array  
                  // mid+1 is starting point of right sub array  
                  int mid = Math.min(left + size - 1, n - 1);
                  int right = Math.min(left + 2 * size - 1, n - 1); 
      
                  // merge sub array arr[left.....mid] &  
                  // arr[mid+1....right]  
                  sortIntArrayWithComparator_merge(arr, comparator, left, mid, right); 
              } 
          } 
      }
      
      static int lIntArray(int[] a) {
        return a == null ? 0 : a.length;
      }
      
      static interface IntComparator {
        int compare(int a, int b);
      }
      

      【讨论】:

        【解决方案4】:

        您不需要外部库:

        Integer[] input = Arrays.stream(arr).boxed().toArray(Integer[]::new);
        Arrays.sort(input, (a, b) -> b - a); // reverse order
        return Arrays.stream(input).mapToInt(Integer::intValue).toArray();
        

        【讨论】:

        【解决方案5】:

        如果您不想复制数组(假设它非常大),您可能需要创建一个可用于排序的包装器List&lt;Integer&gt;

        final int[] elements = {1, 2, 3, 4};
        List<Integer> wrapper = new AbstractList<Integer>() {
        
                @Override
                public Integer get(int index) {
                    return elements[index];
                }
        
                @Override
                public int size() {
                    return elements.length;
                }
        
                @Override
                public Integer set(int index, Integer element) {
                    int v = elements[index];
                    elements[index] = element;
                    return v;
                }
        
            };
        

        现在您可以使用自定义比较器对此包装器列表进行排序。

        【讨论】:

        • 我比接受的回复更喜欢这个。无需复制或转换数组内容,只需利用 Lists 的自定义实现。
        • @OB1:它看起来很整洁,但是标准的sort 实现将整个列表复制到一个数组中,对其进行排序并将其写回。由于这个列表没有实现RandomAccess 标记,回写将使用ListIterator 而不是仅仅调用set
        • 哇,Holger 对副本的看法是正确的。我什至没有考虑检查这个,因为我认为没有人会如此脑残来复制。
        • @user1460736 javadocs 说这是故意的,因为列表实现对于随机访问可能效率低下。例如。 LinkedList 直接排序会非常糟糕,因此他们会复制。为什么他们不检查RandomAccess 不清楚,我想根本没有多少人知道这个标记界面。
        • 扩展 RandomAccess 不会有什么坏处,以防将来某个时间执行此优化。然而,目前该方法并没有达到它的目的。
        【解决方案6】:

        您可以使用 fastutil 库中的 IntArrays.quickSort(array, comparator)

        【讨论】:

          【解决方案7】:

          如果您无法更改输入数组的类型,以下将起作用:

          final int[] data = new int[] { 5, 4, 2, 1, 3 };
          final Integer[] sorted = ArrayUtils.toObject(data);
          Arrays.sort(sorted, new Comparator<Integer>() {
              public int compare(Integer o1, Integer o2) {
                  // Intentional: Reverse order for this demo
                  return o2.compareTo(o1);
              }
          });
          System.arraycopy(ArrayUtils.toPrimitive(sorted), 0, data, 0, sorted.length);
          

          这使用 commons-lang 项目中的 ArrayUtilsint[]Integer[] 之间轻松转换,创建数组的副本,进行排序,然后将排序后的数据复制到原始数据上。

          【讨论】:

          • 为什么不用Arrays.sort而不是转换数组->列表->数组?
          • 好点,我已经更新,正在玩 commons-primitives,但实际上并没有做任何有用的事情
          • 我不知道 commons-lang。感谢您的提示。
          • return o2.compareTo(o1); 这是正确的吗?我相信这样排序会如我们预期的那样颠倒......
          • 是的,排序是颠倒的,我选择它是为了证明排序与int的自然排序不同
          【解决方案8】:

          如何使用流(Java 8)?

          int[] ia = {99, 11, 7, 21, 4, 2};
          ia = Arrays.stream(ia).
              boxed().
              sorted((a, b) -> b.compareTo(a)). // sort descending
              mapToInt(i -> i).
              toArray();
          

          或就地:

          int[] ia = {99, 11, 7, 21, 4, 2};
          System.arraycopy(
                  Arrays.stream(ia).
                      boxed().
                      sorted((a, b) -> b.compareTo(a)). // sort descending
                      mapToInt(i -> i).
                      toArray(),
                  0,
                  ia,
                  0,
                  ia.length
              );
          

          【讨论】:

          • 我们无法在 IntStream 上进行排序(IntComparator),这让我很困扰。
          • 请勿使用(a, b) -&gt; b - a 进行逆序。该比较器可能溢出。注意Comparator.reverseOrder()的存在...
          • 完全错过了潜在的溢出。改编了答案。谢谢霍尔格!
          【解决方案9】:

          我尝试最大限度地使用原始类型本身的比较器。最后我得出结论,没有办法欺骗比较器。这是我的实现。

          public class ArrSortComptr {
              public static void main(String[] args) {
          
                   int[] array = { 3, 2, 1, 5, 8, 6 };
                   int[] sortedArr=SortPrimitiveInt(new intComp(),array);
                   System.out.println("InPut "+ Arrays.toString(array));
                   System.out.println("OutPut "+ Arrays.toString(sortedArr));
          
              }
           static int[] SortPrimitiveInt(Comparator<Integer> com,int ... arr)
           {
              Integer[] objInt=intToObject(arr);
              Arrays.sort(objInt,com);
              return intObjToPrimitive(objInt);
          
           }
           static Integer[] intToObject(int ... arr)
           {
              Integer[] a=new Integer[arr.length];
              int cnt=0;
              for(int val:arr)
                a[cnt++]=new Integer(val);
              return a;
           }
           static int[] intObjToPrimitive(Integer ... arr)
           {
               int[] a=new int[arr.length];
               int cnt=0;
               for(Integer val:arr)
                   if(val!=null)
                       a[cnt++]=val.intValue();
               return a;
          
           }
          
          }
          class intComp implements Comparator<Integer>
          {
          
              @Override //your comparator implementation.
              public int compare(Integer o1, Integer o2) {
                  // TODO Auto-generated method stub
                  return o1.compareTo(o2);
              }
          
          }
          

          @罗曼: 我不能说这是一个很好的例子,但既然你问这就是我想到的。 假设在一个数组中,您只想根据它们的绝对值对数字进行排序。

          Integer d1=Math.abs(o1);
          Integer d2=Math.abs(o2);
          return d1.compareTo(d2);
          

          另一个示例可能就像您只想对大于 100 的数字进行排序。这实际上取决于情况。我想不出更多的情况。也许 Alexandru 可以举出更多示例,因为他说他想使用比较器对于 int 数组。

          【讨论】:

          • @Emil:抱歉有点离题,但我很好奇,能否请您给我看一个用于对整数数组进行排序的比较器示例?我无法想象除了return sign * (i1 - i2); 之外没有其他实现,其中sign 是-1 或+1,具体取决于所需的顺序。
          • @Emil:实际上,我刚刚展示的实现可能已损坏(最初应该将 int 转换为 long),但在上下文中并不重要。
          • 你的意思是说除了升序和降序排序之外不需要整数的比较器?
          • @Emil:几乎是的,但我说只有我无法想象另一种情况。
          • @Roman:我在答案中附加了一个示例。我不知道这是否符合您的预期。
          【解决方案10】:

          这是一个辅助方法来完成这项工作。

          首先你需要一个新的 Comparator 接口,因为Comparator 不支持原语:

          public interface IntComparator{
              public int compare(int a, int b);
          }
          

          (你当然可以通过自动装箱/拆箱来做到这一点,但我不会去那里,这很难看)

          然后,这是一个使用此比较器对 int 数组进行排序的辅助方法:

          public static void sort(final int[] data, final IntComparator comparator){
              for(int i = 0; i < data.length + 0; i++){
                  for(int j = i; j > 0
                      && comparator.compare(data[j - 1], data[j]) > 0; j--){
                      final int b = j - 1;
                      final int t = data[j];
                      data[j] = data[b];
                      data[b] = t;
                  }
              }
          }
          

          这是一些客户端代码。一个愚蠢的比较器,它将所有仅包含数字“9”的数字排序到前面(再次按大小排序),然后是其余的(不管是什么好):

          final int[] data =
              { 4343, 544, 433, 99, 44934343, 9999, 32, 999, 9, 292, 65 };
          sort(data, new IntComparator(){
          
              @Override
              public int compare(final int a, final int b){
                  final boolean onlyNinesA = this.onlyNines(a);
                  final boolean onlyNinesB = this.onlyNines(b);
                  if(onlyNinesA && !onlyNinesB){
                      return -1;
                  }
                  if(onlyNinesB && !onlyNinesA){
                      return 1;
                  }
          
                  return Integer.valueOf(a).compareTo(Integer.valueOf(b));
              }
          
              private boolean onlyNines(final int candidate){
                  final String str = String.valueOf(candidate);
                  boolean nines = true;
                  for(int i = 0; i < str.length(); i++){
                      if(!(str.charAt(i) == '9')){
                          nines = false;
                          break;
                      }
                  }
                  return nines;
              }
          });
          
          System.out.println(Arrays.toString(data));
          

          输出:

          [9, 99, 999, 9999, 32, 65, 292, 433, 544, 4343, 44934343]
          

          排序代码取自Arrays.sort(int[]),我只使用了针对微型数组优化的版本。对于真正的实现,您可能需要查看 Arrays 类中的内部方法 sort1(int[], offset, length) 的源代码。

          【讨论】:

          • Arrays.sort() 似乎使用快速排序查看其代码,而建议的排序似乎使用插入排序。会不会越来越慢?
          • 是的,除非数组很短,否则速度慢得让人无法接受
          【解决方案11】:

          通过将您的 int 数组转换为 Integer 数组,然后使用 public static <T> void Arrays.sort(T[] a, Comparator<? super T> c)(仅需要第一步,因为我担心自动装箱可能会在数组上工作)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-04-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-02-05
            相关资源
            最近更新 更多