Як відсортувати масив ints за допомогою нестандартного компаратора?


76

Мені потрібно відсортувати масив ints, використовуючи власний компаратор, але бібліотека Java не надає функції сортування для ints із компараторами (компаратори можна використовувати лише з об’єктами). Чи є простий спосіб зробити це?


ви просто хочете відсортувати масив за спаданням чи ви хочете виконати щось більш складне?
Роман

Щось більш складне. Я хочу відсортувати int, використовуючи абсолютне значення як ключ.
Александру

Відповіді:


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

Це використовує ArrayUtilsпроект commons-lang для легкого перетворення між int[]і Integer[], створює копію масиву, виконує сортування, а потім копіює відсортовані дані над оригіналом.


3
Чому б вам не використовувати Arrays.sort замість перетворення масиву -> список -> масив?
нанда

Хороший момент, я оновив, бавився із загальними примітивами, але насправді не робив нічого корисного
Джон Фрідман,

Я не знав про commons-lang. Дякую за підказку.
Александру

4
return o2.compareTo(o1);це правильно? Я вірю, що таким чином порядок буде скасовано, як ми очікуємо ...
Педро Дуссо

1
Так, порядок скасовано, я вибрав це, щоб довести, що порядок відрізнявся від природного впорядкуванняint
Джон Фрідман,

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
Мене турбує те, що ми не можемо сортувати (IntComparator) на IntStream.
Трейказ

9
Не використовуйте (a, b) -> b - aдля зворотного порядку. Цей компаратор може переповнюватися. Згадайте про існування Comparator.reverseOrder()...
Холгер

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маркер, зворотний запис буде використовувати ListIteratorзамість просто дзвінка set.
Холгер

Ого, Холгер має рацію щодо копії. Я навіть не замислювався про перевірку цього, оскільки припускав, що ніхто не буде так мозок, щоб зробити копію.
user1460736

1
@ user1460736 Явадоки кажуть, що це робиться навмисно, оскільки реалізації списків можуть бути неефективними для довільного доступу. Наприклад, LinkedListбуло б дуже погано сортувати безпосередньо, тому вони роблять копію. Чому вони не перевіряють RandomAccess, незрозуміло, я думаю, мало хто взагалі знає про цей маркерний інтерфейс.
Дмитро Автономов

Розширення RandomAccessне зашкодить, якщо ця оптимізація буде виконана в майбутньому. Однак в даний час метод не дозволяє досягти того, що було створено.
Маартен Бодеус


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)в класі Arrays .


5
Arrays.sort (), здається, використовує швидку сортування, дивлячись на його код, тоді як пропоноване сортування, схоже, використовує вставку сортування. Чи не буде це асимптотично повільнішим?
Sudarshan S

Так, це неприпустимо повільно, якщо масив не дуже короткий
Штефан Рейх

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

}

@Roman: Я не можу сказати, що це хороший приклад, але оскільки ви запитали це, мені це спало на думку. Припустимо, в масиві ви хочете сортувати числа лише на основі їх абсолютного значення.

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

Інший приклад може бути схожий на те, що ви хочете сортувати лише числа, більші за 100. Це насправді залежить від ситуації. Я більше не можу думати про ситуації. Можливо, Александру може навести більше прикладів, оскільки каже, що хоче використовувати компаратор для масиву int .


@Emil: вибачте за невеликий офтоп, але мені просто цікаво, не могли б ви показати мені приклад компаратора, за яким ви сортували масив цілих чисел? Я просто не уявляю жодної реалізації, крім випадків, return sign * (i1 - i2);коли sign-1 або +1 залежно від бажаного порядку.
Роман

@Emil: насправді, реалізація, яку я щойно показав, напевно порушена (спочатку ints слід розливати), але це не має значення в контексті.
Роман

Ви хочете сказати, що порівняння для цілого числа не потрібно, окрім сортування за зростанням та спаданням?
Еміль

@Emil: майже так, але я сказав, що лише я не можу уявити інший випадок.
Роман

@Roman: Я додав зразок до відповіді. Не знаю, чи це було те, що ви очікували.
Еміль

0

Ось деякий код (насправді це не Timsort, як я спочатку думав, але він працює добре), який робить трюк без жодного боксу / розпакування. У моїх тестах це працює в 3-4 рази швидше, ніж використання Collections.sort із обгорткою List навколо масиву.

// 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());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.