Comparator .comparing().reversed() странное поведение / работает не так, как ожидалось

#java #sorting #lambda #comparator #comparable

#java #сортировка #лямбда #компаратор #сопоставимый

Вопрос:

Насколько мне известно, Comparator.comparingInt() следует сортировать в порядке возрастания и Comparator.comparingInt().reversed следует сортировать в порядке убывания. Но я нашел сценарий, в котором это обратное.

Это лучше объяснить на примере. Ниже приведен мой код.

Класс Amount:

 class Amount
{
    int lineNum;
    int startIndex;
    Double value;
//Getters , setters and toString.
}
  

Основной метод:

 public static void main( String[] args )
{
    List<Amount> amounts = new ArrayList<>();
    amounts.add( new Amount( 1.0, 5, 10 ) ); //LINE_NUM 5
    amounts.add( new Amount( 3.0, 9, 30 ) );
    amounts.add( new Amount( 2.0, 3, 40 ) );
    amounts.add( new Amount( 9.0, 5, 20 ) ); //LINE_NUM 5
    amounts.add( new Amount( 6.0, 1, 50 ) );
    amounts.add( new Amount( 4.0, 5, 20 ) ); //LINE_NUM 5
    System.out.println( ".............BEFORE SORTING.........." );
    amounts.forEach( System.out::println );


    amounts.sort( 
                 Comparator.comparingInt( Amount::getLineNum )   //NOTE THIS
        .           .thenComparingInt( Amount::getStartIndex ).reversed()
                      .thenComparingDouble( Amount::getValue ) );

    System.out.println( "nn.............AFTER SORTING.........." );

    amounts.forEach( System.out::println );
}
  

Я хотел, чтобы список сумм был отсортирован по возрастанию lineNum, по убыванию startIndex и по возрастанию значения.

Итак, мое ожидание было таким.

………….ПОСЛЕ СОРТИРОВКИ……….(ОЖИДАНИЕ)

Количество [lineNum =1, startIndex = 50, value = 6.0]

Количество [lineNum =3, startIndex = 40, value = 2.0]

Количество [lineNum =5, startIndex = 20, value = 4.0]

Количество [lineNum =5, startIndex = 20, value = 9.0]

Количество [lineNum =5, startIndex = 10, value = 1.0]

Количество [lineNum = 9, startIndex = 30, value = 3.0]

………….ПОСЛЕ СОРТИРОВКИ……….(АКТУАЛЬНО)

Количество [lineNum = 9, startIndex = 30, value = 3.0]

Количество [lineNum =5, startIndex = 20, value = 4.0]

Количество [lineNum =5, startIndex = 20, value = 9.0]

Количество [lineNum =5, startIndex = 10, value = 1.0]

Количество [lineNum =3, startIndex = 40, value = 2.0]

Количество [lineNum =1, startIndex = 50, value = 6.0]

Все было правильно, за исключением порядка строк. Суммы были отсортированы по убыванию номера строки, в то время как я ожидал, что это будет в порядке возрастания.

Результаты были такими, как ожидалось, когда я изменил компаратор на следующий

 amounts.sort(
    Comparator.
    comparingInt( Amount::getLineNum ).reversed()
    .thenComparingInt( Amount::getStartIndex ).reversed()
    .thenComparingDouble( Amount::getValue ) );
  

Что странно, потому что comparingInt( Amount::getLineNum ).reversed() предполагалось сортировать суммы по убыванию номера строки .

Еще одна вещь, которую я заметил, это то, что сравнение с помощью startIndex работает так, как ожидалось. Но сравнение по номеру строки частью не является.

Кто-нибудь может это объяснить?

Ответ №1:

Легче понять, что происходит, если вы помещаете каждый вызов в строку:

 Comparator.comparingInt(Amount::getLineNum)
    .thenComparingInt(Amount::getStartIndex)
    .reversed()
    .thenComparingDouble(Amount::getValue)
  

Это reversed() возвращает компаратор, который отменяет результаты компаратора, к которому он был вызван… который является «компаратором, который сначала сравнивает номер строки, затем начальный индекс». Это не похоже на то, что он «заключен в квадратные скобки» только для области предыдущего thenComparingInt() вызова, как это выглядело при вашем предыдущем форматировании.

Вы могли бы сделать это как:

 Comparator.comparingInt(Amount::getLineNum)
    .thenComparing(Comparator.comparingInt(Amount::getStartIndex).reversed())
    .thenComparingDouble(Amount::getValue)
  

На этом этапе отменяется только начальное сравнение индексов.

Комментарии:

1. Вы имеете в виду, что reversed () в любом компараторе применяется также ко всем предыдущим компараторам?

2.@ArunGowda это применяется к результату thenComparingInt метода, который Comparator включает предыдущие

3. @ArunGowda: Вы вызываете это в компараторе . Это отменяет результат этого компаратора. Компаратор, созданный Comparator.comparingInt(Amount::getLineNum).thenComparingInt(Amount::getStartIndex) , является «компаратором, который упорядочивает по номеру строки, а затем запускает индекс». Итак, когда вы вызываете reverse этот компаратор, он возвращает «(компаратор, который упорядочивает по номеру строки, а затем запускает индекс) — но в обратном порядке»

4.может быть, проще для понимания: comp1 = Comparator.comparingInt(Amount::getLineNum); comp2 = comp1.thenComparingInt(Amount::getStartIndex); comp3 = comp2.reversed();

5. Я пытаюсь использовать это, но я в том случае, когда первый и второй компараторы поменялись местами, только третий нет. В этом случае, похоже, Comparator.comparing(Comparator.comparingInt(Amount::getLineNum).reversed()).thenComparing... это больше не работает. Я должен использовать reversed вне круглых скобок: Comparator.comparingInt(Amount::getLineNum).reversed().thenComparing... . Почему это возможно для thenComparing , но не для comparing ? Я понимаю, что это разные сигнатуры методов, мне просто интересно, правильно ли я это делаю.

Ответ №2:

Поместите вызов reversed() внутрь thenComparing:

    Comparator.comparingInt(Amount::getLineNum)

   .thenComparing(Comparator.comparingInt(Amount::getStartIndex).reversed())
   .thenComparingDouble( Amount::getValue );
  

Ответ №3:

Из документов:

reversed() : Возвращает компаратор, который устанавливает обратный порядок этого компаратора.

thenComparing() : Возвращает компаратор лексикографического порядка с другим компаратором. Если этот компаратор считает два элемента равными, т.е. compare(a, b) == 0, для определения порядка используется other.

Каждый шаг создает новый компаратор на основе предыдущего. Таким образом, reversed() метод создает обратный компаратор

 Comparator.comparingInt(Amount::getLineNum).thenComparingInt(Amount::getStartIndex)
  

Чтобы получить только второе обратное, вы должны обернуть его в собственный компаратор:

 .thenComparing(Comparator.comparingInt(Amount::getStartIndex).reversed())
  

В вашем втором решении результат правильный, потому что вы фактически дважды отменяете первое условие:

 Comparator.comparingInt(Amount::getLineNum).reversed() // reverses one time
    .thenComparingInt(Amount::getStartIndex).reversed() // reverses all before (also the first one)
  

Таким образом, полное решение будет выглядеть следующим образом:

 Comparator.comparingInt(Amount::getLineNum)
    .thenComparing(Comparator.comparingInt(Amount::getStartIndex).reversed())
    .thenComparingDouble(Amount::getValue)