Сортировать с одним элементом в конце - PullRequest
15 голосов
/ 11 марта 2019

У меня есть список объектов, и я хочу отсортировать его в алфавитном порядке по атрибуту. Но я хочу добавить правило исключения, если этот атрибут соответствует определенной строке. Например:

public class Car {
  String name;
}
List<Car> cars = asList(
    new Car("Unassigned"), 
    new Car("Nissan"), 
    new Car("Yamaha"), 
    new Car("Honda"));

List<Car> sortedCars = cars
  .stream
  .sorted(Comparator.comparing(Car::getName))
  .collect(Collectors.toList());

Если cars.name == "Unassigned", тогда этот автомобиль должен остаться в конце списка, и результат будет:

[Car<Honda>, Car<Nissan>, Car<Yamaha>, Car<Unassigned>]

Ответы [ 5 ]

15 голосов
/ 11 марта 2019
  List<Car> sortedCars = cars
        .stream()
        .sorted(Comparator.comparing(
            Car::getName,
            Comparator.comparing((String x) -> x.equals("Unassigned"))
                      .thenComparing(Comparator.naturalOrder())))
        .collect(Collectors.toList());

Здесь много чего происходит.Сначала я использую Comparator.comparing(Function, Comparator);затем (String x) -> x.equals("Unassigned"), который фактически сравнивает Boolean (то есть Comparable);затем тот факт, что используется (String x) - поскольку этот тип используется для правильного вывода типов ...

8 голосов
/ 11 марта 2019

Самое прямое и простое для чтения решение - это, вероятно, написать собственный компаратор, который реализует вашу логику сортировки.

Вы все еще можете использовать метод Comparator.comparing, чтобы сделать его немного более симпатичным, хотя:

public static final String UNASSIGNED = "Unassigned";

List<Car> cars = List.of(
    new Car("Unassigned"), 
    new Car("Nissan"), 
    new Car("Yamaha"), 
    new Car("Honda"));

List<Car> sortedCars = cars.stream()
    .sorted(Comparator.comparing(Car::getName, (name1, name2) -> {
            if (name1.equals(name2)) return 0;
            if (name1.equals(UNASSIGNED)) return 1;
            if (name2.equals(UNASSIGNED)) return -1;
            return name1.compareTo(name2);
    }))
    .collect(Collectors.toList());

Также рекомендуется использовать именованную константу для специальных значений, таких как "Unassigned".


Также обратите внимание, что если вам не нужно хранить несортированный список cars, вы можете отсортировать этот список вместо использования потока:

cars.sort(UNASSIGNED_COMPARATOR);
1 голос
/ 11 марта 2019

Один из возможных способов сделать это - использовать разбиение как:

Map<Boolean, List<Car>> partitionedCars = cars
        .stream()
        .collect(Collectors.partitioningBy(a -> a.getName().equals("Unassigned")));

List<Car> sortedCars = Stream.concat(partitionedCars.get(Boolean.FALSE).stream()
        .sorted(Comparator.comparing(Car::getName)), partitionedCars.get(Boolean.TRUE).stream())
        .collect(Collectors.toList());
0 голосов
/ 11 марта 2019

В качестве альтернативы, просто удалите все экземпляры "Unassigned" и добавьте сколько угодно к концу List после. Например:

int numberOfUnassigned = 0;
for (Iterator<Car> iterator = sortedCars.iterator(); iterator.hasNext();) {
    String str = iterator.next().getName();
    if (str.equals("Unassigned")) {
        iterator.remove();
        numberOfUnassigned++;
    }
}

А затем добавьте число numberOfUnassigned в конец.

0 голосов
/ 11 марта 2019

Вы можете просто заменить "Unassigned" строкой конца алфавита в вашем компараторе.

Comparator.comparing(car -> car.getName().equals("Unassigned") ? "ZZZ" : car.getName())
...