110

I need to sort an array of ints using a custom comparator, but Java's library doesn't provide a sort function for ints with comparators (comparators can be used only with objects). Is there any easy way to do this?

3
  • do you just want to sort the array in descending order or do you want to perform something more complicated? Commented Sep 13, 2010 at 12:29
  • 1
    Something more complicated. I want to sort the int using absolute value as a key. Commented Sep 13, 2010 at 13:36
  • It is unimaginable for a high-level language to go through the nonsense for such a basic ask like this. Commented Apr 27 at 16:25

12 Answers 12

76

If you can't change the type of your input array the following will work:

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

This uses ArrayUtils from the commons-lang project to easily convert between int[] and Integer[], creates a copy of the array, does the sort, and then copies the sorted data over the original.

Sign up to request clarification or add additional context in comments.

5 Comments

Why don't you use Arrays.sort instead of converting array -> list -> array?
Good point, I've updated, was playing around with commons-primitives, but didn't actually do anything useful
I didn't know about commons-lang. Thanks for the tip.
return o2.compareTo(o1); is this correct? I believe this way the ordering will be reversed as we expect...
Yes the ordering is reversed, I chose that to prove that the ordering was different from the natural ordering of int
53

How about using streams (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();

Or in-place:

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

3 Comments

It bugs me that we can't have sorted(IntComparator) on IntStream.
Don’t use (a, b) -> b - a for reverse order. This comparator can overflow. Mind the existence of Comparator.reverseOrder()
Completely missed the potential overflow. Adapted the answer. Thanks Holger!
13

You can use IntArrays.quickSort(array, comparator) from fastutil library.

Comments

13

If you don't want to copy the array (say it is very large), you might want to create a wrapper List<Integer> that can be used in a sort:

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

    };

And now you can do a sort on this wrapper List using a custom comparator.

5 Comments

I like this much better than the accepted response. No need to copy or convert array content, just take advantage of custom implementation of Lists.
@OB1: it looks neat, but the standard sort implementation copies the entire list into an array, sorts it and writes it back. And since this list doesn’t implement the RandomAccess marker, the write-back will be using a ListIterator instead of just calling set.
Wow, Holger is right about the copy. I didn't even think about checking this as I assumed that nobody would be so braindead to do a copy.
@user1460736 The javadocs say this is done on purpose, because list implementations might be inefficient for random access. E.g. LinkedList would be super bad to sort directly, thus they do a copy. Why they don't check for RandomAccess is not clear, I guess not many people know about this marker interface at all.
Extending RandomAccess would not hurt in case this optimization is performed at sometime in the future. However, currently the method doesn't achieve what it was set up to do.
8

You don't need external library:

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

1 Comment

To quote Holgers comment on another answer: "Don’t use (a, b) -> b - a for reverse order. This comparator can overflow. Mind the existence of Comparator.reverseOrder()"
3

By transforming your int array into an Integer one and then using public static <T> void Arrays.sort(T[] a, Comparator<? super T> c) (the first step is only needed as I fear autoboxing may bot work on arrays).

Comments

3

If you are interested with performance and reducing number of object created on the way consider using implementation from eclipse collections.

It uses custom IntComparator, which operates on primitives thus no boxing is required.

Comments

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

Comments

1

Here is some code (it's actually not Timsort as I originally thought, but it does work well) that does the trick without any boxing/unboxing. In my tests, it works 3-4 times faster than using Collections.sort with a List wrapper around the array.

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

Comments

0

Here is a helper method to do the job.

First of all you'll need a new Comparator interface, as Comparator doesn't support primitives:

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

(You could of course do it with autoboxing / unboxing but I won't go there, that's ugly)

Then, here's a helper method to sort an int array using this comparator:

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

And here is some client code. A stupid comparator that sorts all numbers that consist only of the digit '9' to the front (again sorted by size) and then the rest (for whatever good that is):

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

Output:

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

The sort code was taken from Arrays.sort(int[]), and I only used the version that is optimized for tiny arrays. For a real implementation you'd probably want to look at the source code of the internal method sort1(int[], offset, length) in the Arrays class.

2 Comments

Arrays.sort() seems to use quicksort looking at its code whereas the proposed sort seems to use insertion sort. Wouldn't it be asymptotically slower?
Yes, it's unacceptably slow unless the array is very short
0

I tried maximum to use the comparator with primitive type itself. At-last i concluded that there is no way to cheat the comparator.This is my implementation.

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

}

@Roman: I can't say that this is a good example but since you asked this is what came to my mind. Suppose in an array you want to sort number's just based on their absolute value.

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

Another example can be like you want to sort only numbers greater than 100.It actually depends on the situation.I can't think of any more situations.Maybe Alexandru can give more examples since he say's he want's to use a comparator for int array.

7 Comments

@Emil: sorry for a little offtop, but I'm just curious, could you please show me an example of comparator you've used to sort an array of integers? I just can't imagine none implementation except for return sign * (i1 - i2); where sign is -1 or +1 depending on desirable order.
@Emil: actually, implementation I've just shown is probably broken (ints should be casted to long at first) but it doesn't matter in the context.
Do you mean to say that a comparator for integer is not required other than ascending and descending order sort?
@Emil: almost yes, but I said that only I can't imagine another case.
@Roman:I appended a sample to the answer.I don't know if this was what you expected.
|
0

I had the same requirement of sorting a primitive int array in descending order. After going through the solutions here, I came up with the below two solutions, which achieve the same outcome.

Version 1:

Using ArrayUtils class functions. It requires adding commons-lang external library (or importing org.apache.commons.lang package).

private int[] sortDescending(int[] source) {
    Integer[] intObjArr = ArrayUtils.toObject(source);
    Arrays.sort(intObjArr, Comparator.reverseOrder());

    return ArrayUtils.toPrimitive(intObjArr);
}

Version 2:

Using Arrays.stream() and available functions to box the int values, sort and, map them back to primitive type array.

private int[] sortDescending(int[] source) {
    return Arrays.stream(source)
            .boxed()
            .sorted(Comparator.reverseOrder())
            .mapToInt(num -> num)
            .toArray();
}

Instead of Comparator.reverseOrder(), we can also use the following.

Implementation of compareTo() method provided by Integer class.

(e1, e2) -> e2.compareTo(e1);

Manually implementing a sorting algorithm.

private int[] sortDescending(int[] source) {
       return Arrays.stream(source)
               .boxed()
               .sorted((e1, e2) -> {
                   /* This casting to long is to prevent Integer Overflow/Underflow issue. */
                   long diff = (long) e2 - (long) e1;
                   if (diff < 0) {
                       return -1;
                   } else if (diff > 0) {
                       return 1;
                   }
                   return 0;
               })
               .mapToInt(num -> num)
               .toArray();    
}

Thank you so much for this question and the prior answers.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.