Сортировка массива Java по своим правилам методом `Arrays.sort()`

Например, имеется массив int: {0, -14, 191, 161, 19, 144, 195, 1}.

Хочется, используя метод Arrays.sort() отсортировать массив по абсолютному значению, возможно ли это сделать с помощью компаратора?


Ответы (2 шт):

Автор решения: Nowhere Man

Сортировка массивов при помощи компаратора НЕ доступна для массивов примитивных типов int[], long[], double[] и т.д., поэтому для кастомной сортировки придётся преобразовать массив либо в список List<Integer> либо в массив Integer[], отсортировать его при помощи метода Arrays.sort(T[] arr, Comparator<? super T>) c, и затем преобразовать обратно в массив примитивов.

При этом вместо компаратора следует передавать функцию для конвертации Integer в примитив, на основании которой можно построить компаратор при помощи Comparator.comparingInt(ToIntFunction<? super T>) fn.

public static void sortIntArray(int[] arr, ToIntFunction<Integer> c) {
    Integer[] ints = new Integer[arr.length];
    Arrays.setAll(ints, i -> arr[i]);
    Arrays.sort(ints, Comparator.comparingInt(c));
    Arrays.setAll(arr, i -> ints[i]);
}

Также можно использовать Stream API и возвращать новый отсортированный массив, однако следует учесть, что IntStream также не поддерживает кастомную сортировку, и придется преобразовать его в Stream<Integer> и затем обратно при помощи Stream::mapToInt:

public static int[] sortInts(int[] arr, ToIntFunction<Integer> c) {
    return Arrays.stream(arr)                // IntStream
        .boxed()                             // Stream<Integer>
        .sorted(Comparator.comparingInt(c))  // Stream<Integer>
        .mapToInt(Integer::intValue)         // IntStream
        .toArray();                          // int[]
}

Тест:

int[] arr = {0, -14, 191, 161, 19, 144, 195, 1};
sortIntArray(arr, Math::abs);
// или arr = sortInts(arr, Math::abs);
System.out.println(Arrays.toString(arr));

Вывод:

[0, 1, -14, 19, 144, 161, 191, 195]

Аналогично, в метод для кастомной сортировки можно передавать IntUnaryOperator, принимающий и отдающий результат типа int, тогда при вызове компаратора нужно будет передать ссылку на соответствующий метод IntUnaryOperator::applyAsInt:

public static int[] sortInts(IntUnaryOperator fun, int... arr) {    
    return Arrays.stream(arr)
        .boxed()
        .sorted(Comparator.comparingInt(fun::applyAsInt))
        // аналогия с явным анбоксингом
        // .sorted(Comparator.comparingInt(i -> fun.applyAsInt(i.intValue())))
        .mapToInt(Integer::intValue)
        .toArray();
}
→ Ссылка
Автор решения: Stanislav Volodarskiy

Есть в Java такое скользкое место: нельзя сортировать массивы примитивов используя компаратор. Способы, которые предлагает стандартная библиотека, предполагают два копирования содержимого массива. Копируете данные в массив или коллекцию объектов, сортируете, копируете обратно. Хочется решить задачу без копирований.

Такой способ есть: создадим наследника AbstractList, который будет выглядеть как список Integer, но данные будет хранить в массиве. Если такого наследника отсортировать, упорядоченным окажется массив в котором хранятся данные.

import java.util.Arrays;
import java.util.AbstractList;
import java.util.Comparator;

public class Temp {
    public static void main(String[] args) {
        int[] array = {0, -14, 191, 161, 19, 144, 195, 1};
    
        System.out.println(Arrays.toString(array));
        sortArray(array, Comparator.comparingInt(Math::abs));
        System.out.println(Arrays.toString(array));
    }

    public static void sortArray(int[] array, Comparator<? super Integer> c) {
        new IntList(array).sort(c);
    }

    private static class IntList extends AbstractList<Integer> {
        private final int[] array;

        public IntList(int[] array) { this.array = array; }

        @Override
        public Integer get(int i) { return array[i]; }

        @Override
        public int size() { return array.length; }

        @Override
        public Integer set(int i, Integer v) {
            int old = array[i];
            array[i] = v;
            return old;
        }
    }
}

Всё работает:

$ javac Temp.java && java Temp
[0, -14, 191, 161, 19, 144, 195, 1]
[0, 1, -14, 19, 144, 161, 191, 195]

Если вы не боитесь анонимных классов, код можно записать короче:

import java.util.Arrays;
import java.util.AbstractList;
import java.util.Comparator;

public class Temp {
    public static void main(String[] args) {
        int[] array = {0, -14, 191, 161, 19, 144, 195, 1};
    
        System.out.println(Arrays.toString(array));
        sortArray(array, Comparator.comparingInt(Math::abs));
        System.out.println(Arrays.toString(array));
    }

    public static void sortArray(int[] array, Comparator<? super Integer> c) {
        new AbstractList<Integer>() {
            @Override
            public Integer get(int i) { return array[i]; }

            @Override
            public int size() { return array.length; }

            @Override
            public Integer set(int i, Integer v) {
                int old = array[i];
                array[i] = v;
                return old;
            }
        }.sort(c);
    }
}
→ Ссылка