Ich habe viele Fragen dazu gesehen und versucht, das Problem zu lösen, aber nach einer Stunde Googeln und viel Ausprobieren kann ich es immer noch nicht beheben. Ich hoffe, einige von Ihnen haben das Problem erkannt.
Das bekomme ich:
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)
...
Und das ist mein 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;
}
}
Irgendeine Idee?
Card
Klasse :return card1.compareTo(card2)
und diese Logik dort implementieren sollten.Antworten:
Die Ausnahmemeldung ist eigentlich ziemlich beschreibend. Der Vertrag, den es erwähnt, ist Transitivität : wenn
A > B
undB > C
dann für irgendeinenA
,B
undC
:A > C
. Ich habe es mit Papier und Bleistift überprüft und Ihr Code scheint einige Löcher zu haben:if (card1.getRarity() < card2.getRarity()) { return 1;
Sie kehren nicht zurück,
-1
wenncard1.getRarity() > card2.getRarity()
.if (card1.getId() == card2.getId()) { //... } return -1;
Sie kehren zurück,
-1
wenn die IDs nicht gleich sind. Sie sollten zurückkehren-1
oder1
je nachdem, welche ID größer war.Schau dir das an. Abgesehen davon, dass es viel besser lesbar ist, denke ich, dass es tatsächlich funktionieren sollte:
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!
quelle
if (card1.getSet() != card2.getSet()) return card1.getSet() > card2.getSet() ? 1 : -1;
für mehr Geschwindigkeit zu verwenden, wenn es jemanden interessiert.Sie können die folgende Klasse verwenden, um Transitivitätsfehler in Ihren Komparatoren zu lokalisieren:
/** * @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() { } }
Rufen Sie einfach vor dem fehlgeschlagenen
Comparators.verifyTransitivity(myComparator, myCollection)
Code auf.quelle
Es hat auch etwas mit der Version von JDK zu tun. Wenn es in JDK6 gut funktioniert, liegt möglicherweise das von Ihnen beschriebene Problem in JDK 7 vor, da die Implementierungsmethode in JDK 7 geändert wurde.
Schau dir das an:
Beschreibung: Der von
java.util.Arrays.sort
und (indirekt) von verwendete Sortieralgorithmusjava.util.Collections.sort
wurde ersetzt. Die neue Sortierimplementierung kann eineIllegalArgumentException
auslösenComparable
, wenn sie eine erkennt , die denComparable
Vertrag verletzt . Die vorherige Implementierung ignorierte eine solche Situation stillschweigend. Wenn das vorherige Verhalten gewünscht wird, können Sie die neue Systemeigenschaft verwendenjava.util.Arrays.useLegacyMergeSort
, um das vorherige Mergesort-Verhalten wiederherzustellen.Ich kenne den genauen Grund nicht. Wenn Sie jedoch den Code hinzufügen, bevor Sie sort verwenden. Es wird in Ordnung sein.
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
quelle
(a,b) -> { return (new int[]{-1,1})[(int)((Math.random()*2)%2)]}
Ich weißCollections.shuffle
, dass es existiert, aber ich mag es nicht, übermäßig geschützt zu sein.Betrachten Sie den folgenden Fall:
Zunächst
o1.compareTo(o2)
heißt.card1.getSet() == card2.getSet()
zufällig wahr und so istcard1.getRarity() < card2.getRarity()
, also geben Sie 1 zurück.Dann
o2.compareTo(o1)
heißt es. Wiedercard1.getSet() == card2.getSet()
ist wahr. Dann springen Sie zum Folgendenelse
, dann istcard1.getId() == card2.getId()
es wahr, und so ist es auchcardType > item.getCardType()
. Sie geben wieder 1 zurück.Daraus
o1 > o2
undo2 > o1
. Sie haben den Vertrag gebrochen.quelle
if (card1.getRarity() < card2.getRarity()) { return 1;
Wenn
card2.getRarity()
jedoch kleiner als ist, gebencard1.getRarity()
Sie möglicherweise nicht -1 zurück .Sie vermissen auch andere Fälle. Ich würde das tun, Sie können sich je nach Ihrer Absicht ändern:
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; } }
quelle
Es könnte auch ein OpenJDK-Fehler sein ... (nicht in diesem Fall, aber es ist der gleiche Fehler)
Wenn jemand wie ich über diese Antwort bezüglich der stolpert
dann könnte es auch ein Fehler in der Java-Version sein. Ich habe einen Vergleich, der seit einigen Jahren in einigen Anwendungen ausgeführt wird. Aber plötzlich hat es aufgehört zu funktionieren und wirft den Fehler aus, nachdem alle Vergleiche durchgeführt wurden (ich vergleiche 6 Attribute, bevor ich "0" zurückgebe).
Jetzt habe ich gerade diesen Bugreport von OpenJDK gefunden:
quelle
Ich hatte das gleiche Symptom. Für mich stellte sich heraus, dass ein anderer Thread die verglichenen Objekte modifizierte, während die Sortierung in einem Stream stattfand. Um das Problem zu beheben, habe ich die Objekte unveränderlichen temporären Objekten zugeordnet, den Stream einer temporären Sammlung zugeordnet und danach sortiert.
quelle
Ich musste nach mehreren Kriterien sortieren (Datum und, wenn dasselbe Datum; andere Dinge ...). Was mit einer älteren Java-Version an Eclipse funktionierte, funktionierte unter Android nicht mehr: Vergleichsmethode verstößt gegen Vertrag ...
Nach dem Lesen von StackOverflow habe ich eine separate Funktion geschrieben, die ich von compare () aufgerufen habe, wenn die Daten identisch sind. Diese Funktion berechnet die Priorität gemäß den Kriterien und gibt -1, 0 oder 1 zurück, um sie zu vergleichen (). Es scheint jetzt zu funktionieren.
quelle
Ich habe den gleichen Fehler mit einer Klasse wie der folgenden erhalten
StockPickBean
. Von diesem Code aufgerufen: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); } }
Nach dem gleichen Fehler:
Ich habe diese Zeile geändert:
public static Comparator<StockPickBean> VALUE = (val1, val2) -> (int) (val1.value - val2.value);
zu:
public static Comparator<StockPickBean> VALUE = (StockPickBean spb1, StockPickBean spb2) -> Double.compare(spb2.value,spb1.value);
Das behebt den Fehler.
quelle
Ich bin auf ein ähnliches Problem gestoßen, bei dem ich versucht habe, einen
n x 2 2D array
Namen zu sortieren, beicontests
dem es sich um ein 2D-Array einfacher Ganzzahlen handelt. Dies funktionierte die meiste Zeit, warf jedoch einen Laufzeitfehler für eine Eingabe aus: -Arrays.sort(contests, (row1, row2) -> { if (row1[0] < row2[0]) { return 1; } else return -1; });
Error:-
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)
Mit Blick auf die obigen Antworten habe ich versucht, eine Bedingung für hinzuzufügen,
equals
und ich weiß nicht warum, aber es hat funktioniert. Hoffentlich müssen wir explizit angeben, was für alle Fälle zurückgegeben werden soll (größer als, gleich und kleiner als):Arrays.sort(contests, (row1, row2) -> { if (row1[0] < row2[0]) { return 1; } if(row1[0] == row2[0]) return 0; return -1; });
quelle