Как я могу сортировать массивы в коллекции?
У меня есть список объектов. Эти объекты имеют (в частности) частный массив int (если это помогает, я могу перенести его в список). Для этого массива существует публичный Getter. Все массивы имеют одинаковый размер.
Я хочу отсортировать объект на основе их массивов следующим образом:
Unsorted:
{[0, 1, 4, 5],
[0, 0, 2, 3],
[0, 1, 1, 2]}
Sorted:
{[0, 0, 2, 3],
[0, 1, 1, 2],
[0, 1, 4, 5]}
В словах (это называется лексикографией):
- сравнить первый int каждого массива
- если они равны, сравните следующий int каждого массива (и так далее)
- Если они не равны, результатом сравнения является конечный результат.
Мне удается найти их, например. только первый элемент массива с обычным компаратором, но я не знаю, как их искать для всех.
Ответы
Ответ 1
Хорошее решение для Java 8
static final Comparator<CustomObject> COMPARATOR = (o1, o2) -> {
int[] arr1 = o1.getArray();
int[] arr2 = o2.getArray();
return IntStream.range(0, arr1.length)
.map(i -> Integer.compare(arr1[i], arr2[i]))
.filter(i -> i != 0)
.findFirst()
.orElse(0);
};
Тогда, учитывая a List<CustomObject>
, вы можете сделать
list.sort(COMPARATOR);
(Comparator
работает только для массивов одинаковой длины. Возможно, вы захотите его изменить).
Ответ 2
У меня есть коллекция (предпочтительный какой-то Список) объектов [...] Теперь я хочу отсортировать объект на основе своих массивов
Чтобы он был значимым, рассматриваемый Collection
должен быть тем, который сохраняет порядок и позволяет изменять порядок элементов. В терминах интерфейсов коллекций высокого уровня только List
имеет необходимые свойства, поэтому допустим, что ваш Collection
, действительно, List
.
Стандартный способ сортировки a List
- использовать один из двух методов Collections.sort()
. Один требует, чтобы элементы списка реализовали Comparable
, а другой, более общий, требует, чтобы вы предоставили объект, реализующий Comparator
для использования при определении желаемого относительного порядка объектов.
Массивы не реализуют Comparable
(что эквивалентно утверждению, что у них нет "естественного порядка" ), но класс объектов, содержащих их, можно сделать так. Однако, вероятно, лучше создать отдельный класс Comparator
, который реализует нужный вам порядок, и использовать экземпляр этого класса.
Ответ 3
Если я правильно понимаю это, следует следовать прямолинейному подходу:
public class SortArrays {
public static void main(String[] args) {
List<int[]> listOfArrays = new ArrayList<>(4);
listOfArrays.add(new int[]{0, 1, 4, 5});
listOfArrays.add(new int[]{0, 0, 2, 3});
listOfArrays.add(new int[]{0, 1, 1, 2});
Collections.sort(listOfArrays, (o1, o2) -> {
for (int i = 0; i < o1.length; i++) {
if (o1[i] < o2[i])
return -1;
if (o1[i] > o2[i])
return 1;
}
return 0;
});
listOfArrays.forEach(a -> System.out.println(Arrays.toString(a)));
}
}
Он производит:
[0, 0, 2, 3]
[0, 1, 1, 2]
[0, 1, 4, 5]
и то, что вы ожидаете.
Ответ 4
Предположим, что ваш класс выглядит примерно так, и вы не можете его изменить.
final class MyClass
{
private final int[] key;
MyClass(int[] key)
{
this.key = key.clone();
}
public int[] getKey()
{
return key.clone();
}
}
Затем вы можете определить порядок для экземпляров MyClass
путем реализации интерфейса Comparator
. Чтобы быть более общим, я на самом деле собираюсь реализовать компаратор для int[]
:
final class IntArrayComparator
implements Comparator<int[]>
{
@Override
public int compare(int[] a, int[] b)
{
int n = Math.min(a.length, b.length);
for (int idx = 0; idx < n; ++idx) {
if (a[idx] != b[idx])
return (a[idx] < b[idx]) ? -1 : +1;
}
return a.length - b.length;
}
}
Учитывая этот новый компаратор и ваш существующий тип, его легко сортировать:
final class Test
{
public static void main(String... arg)
{
/* Create your list somehow... */
List<MyClass> list = new ArrayList<>();
list.add(new MyClass(new int[]{0, 1, 4, 5}));
list.add(new MyClass(new int[]{0, 0, 2, 3}));
list.add(new MyClass(new int[]{0, 1, 1, 2}));
/* Now sort the list. */
list.sort(Comparator.comparing(MyClass::getKey, new IntArrayComparator()));
/* Display the result... */
list.stream().map(MyClass::getKey).map(Arrays::toString).forEach(System.out::println);
}
}
Вы должны получить результат:
[0, 0, 2, 3]
[0, 1, 1, 2]
[0, 1, 4, 5]
Используемый мной метод comparing()
factory принимает два аргумента: a Function
для "извлечения" ключа из каждого объекта в списке и Comparator
, который может сравнивать извлеченные ключи. Если извлеченный ключ имеет естественный порядок и реализует Comparable
(например, это a String
), тогда нет необходимости указывать Comparator
.
В качестве альтернативы, если вы можете изменить MyClass
, вы можете дать ему естественный порядок, выполнив Comparable
и перемещая сравнение int[]
с его методом compareTo()
, как показано в других ответах. Затем вы можете использовать метод sort()
без аргументов.
Ответ 5
Чтобы отсортировать список массивов int, вам нужна функция, которая выполняет лексикографическое сравнение массива int. Обычно это выражается как Comparator<int[]>
в Java. Обычный способ реализации такой функции - сравнивать соответствующие значения слева направо, пока не будет обнаружено несоответствие; это несоответствие определяет порядок. Если конец массива достигнут без нахождения несоответствия, более короткий массив обычно считается менее длинным. Если длины равны, и все значения равны, массивы считаются равными.
Другие ответы использовали анонимные внутренние классы или лямбды для этой функции, но для таких вещей я предпочитаю обычный метод, который имеет правильную "форму", то есть принимает два аргумента int[]
и возвращает int
что является результатом сравнения. Это позволяет использовать его в качестве цели ссылки на метод.
int arrayCompare(int[] a, int[] b) {
int len = Math.min(a.length, b.length);
for (int i = 0; i < len; i++) {
int c = Integer.compare(a[i], b[i]);
if (c != 0) {
return c;
}
}
return a.length - b.length;
}
Обратите внимание на осторожное использование Integer.compare()
вместо вычитания, избегая потенциальных проблем с переполнением. Использование будет следующим:
List<int[]> arrays = Arrays.asList(
new int[] { 0, 1, 4, 5 },
new int[] { 0, 0, 2, 3 },
new int[] { 0, 1, 1, 2 }
);
arrays.sort(this::arrayCompare);
arrays.forEach(a -> System.out.println(Arrays.toString(a)));
В JDK 9 в класс Arrays
добавлены функции сравнения лексикографических массивов. См. Arrays.compare(int[], int[])
. Существуют также перегрузки для других примитивных типов и ссылочных типов. Перегрузка int[]
заменяет функцию arrayCompare
, которую я написал выше, поэтому вы можете переписать вызов сортировки следующим образом:
arrays.sort(Arrays::compare);
Преимущество функций сравнения в классе Arrays
состоит в том, что они могут обрабатываться специально JVM, что может, например, использовать векторные инструкции для ускорения сопоставлений массивов.
По вашей конкретной проблеме, похоже, что у вас нет списка int[]
, но у вас есть список объектов типа MyObject
, у которых есть int[]
, который вы хотите использовать в качестве ключа сортировки, Предположим, что способ получить массив - это вызвать метод getArray()
. Вы можете отсортировать список следующим образом:
myObjects.sort(Comparator.comparing(MyObject::getArray, Arrays::compare));
Ответ 6
Рассмотрим этот объект:
public class Foo {
private int[] values;
public Foo(int[] values) {
this.values = values;
}
}
Чтобы создать список объектов:
ArrayList<Foo> foos = new ArrayList<>();
foos.add(new Foo(new int[] {0, 1, 4, 5}));
foos.add(new Foo(new int[] {0, 0, 2, 3}));
foos.add(new Foo(new int[] {0, 1, 1, 2}));
Теперь мы хотим отсортировать наш список объектов, поэтому первое, что мы должны сделать, это сделать наш объект реализованным Comparable
. Вы должны прочитать что такое интерфейс Comparable, и как его реализовать.
public class Foo implements Comparable<Foo> {
...
}
В этот момент ваш компилятор будет жаловаться на то, что вы не реализовали все методы, требуемые интерфейсом. Так сделайте это.
public class Foo implements Comparable<Foo> {
...
public int compareTo(Foo f) {
// Return a negative integer if this < f
// Return zero if this == f
// Return a positive integer if this > f
}
}
Что касается фактической логики сравнения, ваш вопрос не очень специфичен в отношении точных правил, которые вы используете для сравнения двух объектов. Однако, из примера, я могу догадываться, что это ваши правила:
- Сравните первое число каждого массива.
- Если они равны, повторите этот процесс, используя следующее число каждого массива.
- В противном случае результатом этого сравнения будет конечный результат.
Вы не укажете, что делать, если один массив короче другого. Я оставлю вам реальный алгоритм (хотя другие ответы, похоже, сделали это для вас). Тем не менее, я укажу, что поскольку значения являются частным полем, вы не сможете использовать это поле для сравнения, если вы не реализуете getter для поля или не публикуете его.
public int[] getValues() { return values; }
Ответ 7
Если ваш массив private
и вы хотите предоставить метод доступа, тогда реализуйте Comparable
для класса и сделайте что-то вроде кода ниже.
Если у вас есть метод доступа, сделайте это в Comparator
.
Этот код does not assume arrays to be of same size
, поэтому он работает для разных массивов. Пожалуйста, измените в соответствии с вашими потребностями.
PS - не скомпилировано/протестировано
public int compareTo(Object o) {
CustomClass other = (CustomClass ) o;
int i = 0;
while (i <= numbers.length && i <= other.numbers.length) {
if (numbers[i] < other.numbers[i])
return -1;
else if (numbers[i] > other.numbers[i])
return 1;
++i;
}
if (numbers.length < other.numbers.length)
return -1;
else if (numbers.length > other.numbers.length)
return 1;
return 0;
}