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