如何使用自定义比较器对整数数组进行排序?


76

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


您是否只想按降序对数组进行排序还是要执行更复杂的操作?
罗马2010年

更复杂的东西。我想使用绝对值作为键对int进行排序。
亚历山德鲁

Answers:


62

如果您无法更改输入数组的类型,则可以执行以下操作:

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

这可以使用ArrayUtilscommons-lang项目轻松地在int[]和之间进行转换Integer[],创建数组的副本,进行排序,然后将排序后的数据复制到原始副本上。


3
为什么不使用Arrays.sort而不是转换array-> list-> array?
南达

我已经更新了一点,好玩的是与公地基元打交道,但实际上并没有做任何有用的事情
Jon Freedman 2010年

我不知道commons-lang。谢谢你的提示。
亚历山德鲁

4
return o2.compareTo(o1);这个对吗?我相信这样的顺序将像我们期望的那样逆转...
Pedro Dusso 2014年

1
是的,顺序是相反的,我选择证明该顺序与自然顺序不同int
Jon Freedman 2015年

33

如何使用流(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
    );

6
我无法在IntStream上进行排序(IntComparator),这使我感到烦恼。
Trejkaz

9
请勿(a, b) -> b - a用于倒序。该比较器可能溢出。注意Comparator.reverseOrder()……的存在
Holger

1
完全错过了潜在的溢出。改编了答案。谢谢霍尔格!
user3669782

8

如果您不想复制数组(例如,数组很大),则可能要创建一个List<Integer>可以用于排序的包装器:

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

    };

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


我比接受的回复好得多。无需复制或转换数组内容,只需利用列表的自定义实现。
OB1

2
@ OB1:看起来很整洁,但是标准sort实现将整个列表复制到一个数组中,对其进行排序并写回。并且由于此列表未实现RandomAccess标记,因此写回将使用aListIterator而不是仅调用set
Holger

哇,霍尔格(Holger)对副本是正确的。我什至没有考虑检查此问题,因为我认为没有人会如此死心地去做副本。
user1460736 2016年

1
@ user1460736 javadocs说这是故意完成的,因为列表实现对于随机访问可能效率不高。例如LinkedList,直接排序将非常糟糕,因此他们会进行复制。他们为什么不检查的RandomAccess原因尚不清楚,我想几乎没人知道这个标记界面。
德米特里·阿夫托诺莫夫

RandomAccess如果在将来的某个时候执行此优化,则扩展不会受到损害。但是,当前该方法无法实现其设置。
Maarten Bodewes,


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


0

这是完成工作的辅助方法。

首先,您将需要一个新的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 []),而我仅使用针对小型数组进行了优化的版本。对于一个真正的实现你可能想看看内部方法的源代码sort1(int[], offset, length)中的数组类。


5
Arrays.sort()似乎使用quicksort查看其代码,而建议的sort似乎使用插入排序。它会渐近变慢吗?
Sudarshan S

是的,除非阵列很短,否则它的速度慢得令人无法接受
Stefan Reich

0

我试图最大程度地将比较器与原始类型一起使用。最后我得出结论,没有办法欺骗比较器。这是我的实现。

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取决于期望的顺序。
罗马2010年

@Emil:实际上,我刚刚展示的实现可能已损坏(首先应将int强制转换为long),但在上下文中并不重要。
罗马2010年

您的意思是说,除了升序和降序排序外,不需要整数比较器吗?
埃米尔(Emil)2010年

@Emil:几乎是的,但是我说只有我无法想象另一种情况。
罗曼2010年

@罗马:我在答案后面附加了一个示例,我不知道这是否是您所期望的。
埃米尔(Emil)2010年

0

这是一些无需任何装箱/拆箱操作的代码(实际上不是我最初想的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);
}

0

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());
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.