Widziałem wiele pytań na ten temat i próbowałem rozwiązać problem, ale po godzinie googlowania i wielu prób i błędów nadal nie mogę tego naprawić. Mam nadzieję, że niektórzy z was złapią problem.
Oto, co otrzymuję:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:835)
at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:453)
at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:392)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:191)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at java.util.Collections.sort(Collections.java:155)
...
A to jest moja komparator:
@Override
public int compareTo(Object o) {
if(this == o){
return 0;
}
CollectionItem item = (CollectionItem) o;
Card card1 = CardCache.getInstance().getCard(cardId);
Card card2 = CardCache.getInstance().getCard(item.getCardId());
if (card1.getSet() < card2.getSet()) {
return -1;
} else {
if (card1.getSet() == card2.getSet()) {
if (card1.getRarity() < card2.getRarity()) {
return 1;
} else {
if (card1.getId() == card2.getId()) {
if (cardType > item.getCardType()) {
return 1;
} else {
if (cardType == item.getCardType()) {
return 0;
}
return -1;
}
}
return -1;
}
}
return 1;
}
}
Dowolny pomysł?
Card
klasy:return card1.compareTo(card2)
i zaimplementuj tam tę logikę.Odpowiedzi:
Komunikat o wyjątku jest właściwie dość opisowy. Umowa wymienia jest przechodniości : jeśli
A > B
aB > C
następnie dla każdegoA
,B
iC
:A > C
. Sprawdziłem to na papierze i ołówku i wydaje się, że twój kod ma kilka dziur:if (card1.getRarity() < card2.getRarity()) { return 1;
nie wrócisz,
-1
jeślicard1.getRarity() > card2.getRarity()
.if (card1.getId() == card2.getId()) { //... } return -1;
Wrócisz
-1
jeśli identyfikatory nie są równe. Powinieneś wrócić-1
lub w1
zależności od tego, który identyfikator był większy.Popatrz na to. Oprócz tego, że jest znacznie bardziej czytelny, myślę, że powinien faktycznie działać:
if (card1.getSet() > card2.getSet()) { return 1; } if (card1.getSet() < card2.getSet()) { return -1; }; if (card1.getRarity() < card2.getRarity()) { return 1; } if (card1.getRarity() > card2.getRarity()) { return -1; } if (card1.getId() > card2.getId()) { return 1; } if (card1.getId() < card2.getId()) { return -1; } return cardType - item.getCardType(); //watch out for overflow!
źródło
if (card1.getSet() != card2.getSet()) return card1.getSet() > card2.getSet() ? 1 : -1;
dla większej szybkości, jeśli komuś to zależy.Możesz użyć następującej klasy, aby wskazać błędy przechodniości w komparatorach:
/** * @author Gili Tzabari */ public final class Comparators { /** * Verify that a comparator is transitive. * * @param <T> the type being compared * @param comparator the comparator to test * @param elements the elements to test against * @throws AssertionError if the comparator is not transitive */ public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements) { for (T first: elements) { for (T second: elements) { int result1 = comparator.compare(first, second); int result2 = comparator.compare(second, first); if (result1 != -result2) { // Uncomment the following line to step through the failed case //comparator.compare(first, second); throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 + " but swapping the parameters returns " + result2); } } } for (T first: elements) { for (T second: elements) { int firstGreaterThanSecond = comparator.compare(first, second); if (firstGreaterThanSecond <= 0) continue; for (T third: elements) { int secondGreaterThanThird = comparator.compare(second, third); if (secondGreaterThanThird <= 0) continue; int firstGreaterThanThird = comparator.compare(first, third); if (firstGreaterThanThird <= 0) { // Uncomment the following line to step through the failed case //comparator.compare(first, third); throw new AssertionError("compare(" + first + ", " + second + ") > 0, " + "compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " + firstGreaterThanThird); } } } } } /** * Prevent construction. */ private Comparators() { } }
Po prostu wywołaj
Comparators.verifyTransitivity(myComparator, myCollection)
przed kodem, który się nie powiedzie.źródło
Ma to również coś wspólnego z wersją JDK. Jeśli działa dobrze w JDK6, może będzie miał problem w JDK 7 opisany przez Ciebie, ponieważ metoda implementacji w jdk 7 została zmieniona.
Spójrz na to:
Opis: algorytm sortowania używany przez
java.util.Arrays.sort
i (pośrednio)java.util.Collections.sort
został zastąpiony. Nowa implementacja sortowania może zgłosić,IllegalArgumentException
jeśli wykryje,Comparable
że naruszaComparable
kontrakt. Poprzednia implementacja po cichu ignorowała taką sytuację. Jeśli wymagane jest poprzednie zachowanie, można użyć nowej właściwości systemowejjava.util.Arrays.useLegacyMergeSort
, aby przywrócić poprzednie zachowanie scalania.Nie znam dokładnego powodu. Jeśli jednak dodasz kod przed użyciem sort. Będzie dobrze.
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
źródło
(a,b) -> { return (new int[]{-1,1})[(int)((Math.random()*2)%2)]}
Wiem, żeCollections.shuffle
istnieje, ale nie lubię być nadmiernie chroniony.Rozważ następujący przypadek:
Najpierw
o1.compareTo(o2)
nazywa się.card1.getSet() == card2.getSet()
tak się składa, że tak jestcard1.getRarity() < card2.getRarity()
, więc zwracasz 1.Następnie
o2.compareTo(o1)
nazywa się. To znowucard1.getSet() == card2.getSet()
prawda. Następnie przechodzisz do następnegoelse
, acard1.getId() == card2.getId()
tak się składa, że tak jestcardType > item.getCardType()
. Zwracasz 1 ponownie.Od tego
o1 > o2
, io2 > o1
. Zerwałeś kontrakt.źródło
if (card1.getRarity() < card2.getRarity()) { return 1;
Jeśli jednak
card2.getRarity()
jest mniejsze niż,card1.getRarity()
możesz nie zwrócić -1 .Podobnie tęsknisz za innymi sprawami. Zrobiłbym to, możesz się zmienić w zależności od zamiarów:
public int compareTo(Object o) { if(this == o){ return 0; } CollectionItem item = (CollectionItem) o; Card card1 = CardCache.getInstance().getCard(cardId); Card card2 = CardCache.getInstance().getCard(item.getCardId()); int comp=card1.getSet() - card2.getSet(); if (comp!=0){ return comp; } comp=card1.getRarity() - card2.getRarity(); if (comp!=0){ return comp; } comp=card1.getSet() - card2.getSet(); if (comp!=0){ return comp; } comp=card1.getId() - card2.getId(); if (comp!=0){ return comp; } comp=card1.getCardType() - card2.getCardType(); return comp; } }
źródło
Może to być również błąd OpenJDK ... (nie w tym przypadku, ale jest to ten sam błąd)
Jeśli ktoś taki jak ja natknie się na tę odpowiedź dotyczącą
to może być również błąd w wersji Java. Mam porównanie do pracy od kilku lat w niektórych aplikacjach. Ale nagle przestał działać i zgłasza błąd po wykonaniu wszystkich porównań (porównuję 6 atrybutów przed zwróceniem „0”).
Teraz właśnie znalazłem ten raport o błędzie OpenJDK:
źródło
Miałem ten sam objaw. U mnie okazało się, że inny wątek modyfikował porównywane obiekty, podczas gdy sortowanie odbywało się w strumieniu. Aby rozwiązać ten problem, zamapowałem obiekty na niezmienne obiekty tymczasowe, zebrałem strumień do tymczasowej kolekcji i przeprowadziłem sortowanie.
źródło
Musiałem posortować według kilku kryteriów (data i, jeśli ta sama data; inne rzeczy ...). To, co działało na Eclipse ze starszą wersją Javy, już nie działało na Androidzie: metoda porównawcza narusza kontrakt ...
Po przeczytaniu na StackOverflow napisałem osobną funkcję, którą wywołałem z compare (), jeśli daty są takie same. Ta funkcja oblicza priorytet zgodnie z kryteriami i zwraca wartość -1, 0 lub 1 w celu porównania (). Wydaje się, że teraz działa.
źródło
Otrzymałem ten sam błąd z klasą taką jak poniżej
StockPickBean
. Zadzwoniono z tego kodu:List<StockPickBean> beansListcatMap.getValue(); beansList.sort(StockPickBean.Comparators.VALUE); public class StockPickBean implements Comparable<StockPickBean> { private double value; public double getValue() { return value; } public void setValue(double value) { this.value = value; } @Override public int compareTo(StockPickBean view) { return Comparators.VALUE.compare(this,view); //return Comparators.SYMBOL.compare(this,view); } public static class Comparators { public static Comparator<StockPickBean> VALUE = (val1, val2) -> (int) (val1.value - val2.value); } }
Po otrzymaniu tego samego błędu:
Zmieniłem tę linię:
public static Comparator<StockPickBean> VALUE = (val1, val2) -> (int) (val1.value - val2.value);
do:
public static Comparator<StockPickBean> VALUE = (StockPickBean spb1, StockPickBean spb2) -> Double.compare(spb2.value,spb1.value);
To naprawia błąd.
źródło
Napotkałem podobny problem, gdy próbowałem posortować
n x 2 2D array
nazwanącontests
tablicę dwuwymiarową prostych liczb całkowitych. To działało przez większość czasu, ale generowało błąd w czasie wykonywania dla jednego wejścia: -Arrays.sort(contests, (row1, row2) -> { if (row1[0] < row2[0]) { return 1; } else return -1; });
Błąd:-
Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.base/java.util.TimSort.mergeHi(TimSort.java:903) at java.base/java.util.TimSort.mergeAt(TimSort.java:520) at java.base/java.util.TimSort.mergeForceCollapse(TimSort.java:461) at java.base/java.util.TimSort.sort(TimSort.java:254) at java.base/java.util.Arrays.sort(Arrays.java:1441) at com.hackerrank.Solution.luckBalance(Solution.java:15) at com.hackerrank.Solution.main(Solution.java:49)
Patrząc na powyższe odpowiedzi, próbowałem dodać warunek dla
equals
i nie wiem dlaczego, ale zadziałało. Mamy nadzieję, że musimy wyraźnie określić, co powinno zostać zwrócone we wszystkich przypadkach (większe niż, równe i mniejsze niż):Arrays.sort(contests, (row1, row2) -> { if (row1[0] < row2[0]) { return 1; } if(row1[0] == row2[0]) return 0; return -1; });
źródło