[英]When does TimSort complain about broken comparator?
Java 7 更改了排序算法 ,從而引發了
java.lang.IllegalArgumentException:“比較方法違反了其常規協定!”
在某些情況下,使用的比較器有故障。 是否可以判斷比較器中的哪種錯誤導致了此錯誤? 在我的實驗中,x!= x無關緊要,x <y和y <z但z <x也不重要,但是x = y和y = z但x <z對於某些值無關緊要x,y,z 通常是這樣嗎?
(如果有一個一般規則,在比較器中查找錯誤可能會更容易。但是當然,修復所有錯誤會更好。:-))
特別地,以下兩個比較器沒有使TimSort抱怨:
final Random rnd = new Random(52);
Comparator<Integer> brokenButNoProblem1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 < o2) {
return Compare.LESSER;
} else if (o1 > o2) {
return Compare.GREATER;
}
return rnd.nextBoolean() ? Compare.LESSER : Compare.GREATER;
}
};
Comparator<Integer> brokenButNoProblem2 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 == o2) {
return Compare.EQUAL;
}
return rnd.nextBoolean() ? Compare.LESSER : Compare.GREATER;
}
};
但是以下比較器確實使它拋出:
Comparator<Integer> brokenAndThrowsUp = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (Math.abs(o1 - o2) < 10) {
return Compare.EQUAL; // WRONG and does matter
}
return Ordering.natural().compare(o1, o2);
}
};
更新:在一些實際數據中,我們失敗了,沒有x,y,z,其中x = y和y = z但x <z。 因此,似乎我的猜測是錯誤的,而且似乎並非僅是這種特定的失敗。 還有更好的主意嗎?
看完ComparableTimSort
的代碼后,我不太確定。 讓我們來分析一下。 這是拋出該錯誤的唯一方法(有一個類似的方法僅對交換的角色執行相同的操作,因此分析其中一個就足夠了)。
private void mergeLo(int base1, int len1, int base2, int len2) {
assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
// Copy first run into temp array
Object[] a = this.a; // For performance
Object[] tmp = ensureCapacity(len1);
int cursor1 = tmpBase; // Indexes into tmp array
int cursor2 = base2; // Indexes int a
int dest = base1; // Indexes int a
System.arraycopy(a, base1, tmp, cursor1, len1);
// Move first element of second run and deal with degenerate cases
a[dest++] = a[cursor2++];
if (--len2 == 0) {
System.arraycopy(tmp, cursor1, a, dest, len1);
return;
}
if (len1 == 1) {
System.arraycopy(a, cursor2, a, dest, len2);
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
return;
}
int minGallop = this.minGallop; // Use local variable for performance
outer:
while (true) {
int count1 = 0; // Number of times in a row that first run won
int count2 = 0; // Number of times in a row that second run won
/*
* Do the straightforward thing until (if ever) one run starts
* winning consistently.
*/
// ------------------ USUAL MERGE
do {
assert len1 > 1 && len2 > 0;
if (((Comparable) a[cursor2]).compareTo(tmp[cursor1]) < 0) {
a[dest++] = a[cursor2++];
count2++;
count1 = 0;
if (--len2 == 0)
break outer;
} else {
a[dest++] = tmp[cursor1++];
count1++;
count2 = 0;
if (--len1 == 1)
break outer;
}
} while ((count1 | count2) < minGallop);
// ------------------ GALLOP
/*
* One run is winning so consistently that galloping may be a
* huge win. So try that, and continue galloping until (if ever)
* neither run appears to be winning consistently anymore.
*/
do {
assert len1 > 1 && len2 > 0;
count1 = gallopRight((Comparable) a[cursor2], tmp, cursor1, len1, 0);
if (count1 != 0) {
System.arraycopy(tmp, cursor1, a, dest, count1);
dest += count1;
cursor1 += count1;
len1 -= count1;
// -->>>>>>>> HERE IS WHERE GALLOPPING TOO FAR WILL TRIGGER THE EXCEPTION
if (len1 <= 1) // len1 == 1 || len1 == 0
break outer;
}
a[dest++] = a[cursor2++];
if (--len2 == 0)
break outer;
count2 = gallopLeft((Comparable) tmp[cursor1], a, cursor2, len2, 0);
if (count2 != 0) {
System.arraycopy(a, cursor2, a, dest, count2);
dest += count2;
cursor2 += count2;
len2 -= count2;
if (len2 == 0)
break outer;
}
a[dest++] = tmp[cursor1++];
if (--len1 == 1)
break outer;
minGallop--;
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
if (minGallop < 0)
minGallop = 0;
minGallop += 2; // Penalize for leaving gallop mode
} // End of "outer" loop
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
if (len1 == 1) {
assert len2 > 0;
System.arraycopy(a, cursor2, a, dest, len2);
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
} else if (len1 == 0) {
throw new IllegalArgumentException(
"Comparison method violates its general contract!");
} else {
assert len2 == 0;
assert len1 > 1;
System.arraycopy(tmp, cursor1, a, dest, len1);
}
}
該方法執行兩個排序運行的合並。 它進行通常的合並,但是一旦遇到一方總是“獲勝”(即總是小於另一方),便開始“疾馳”。 奔騰試圖通過向前看更多的元素而不是一次比較一個元素來使事情變得更快。 由於應該對運行進行排序 , 因此可以很好地進行展望。
您會看到僅當len1
為0
時才引發異常。 第一個觀察是:在正常的合並,該異常不能被拋出,因為環路可放棄曾經直接len
這種1
。 因此,只能由於疾馳而拋出異常 。
這已經很明顯地表明異常行為是不可靠的:只要您的數據集很小(數據集太小,以至於生成的運行可能永遠不會疾馳,因為MIN_GALLOP
為7
),或者生成的運行始終會巧合地產生一個不會疾馳的合並,您將永遠不會收到例外。 因此,在不進一步檢查gallopRight
方法的情況下,我們可以得出以下結論:您不能依賴該異常: 無論您的比較器有多錯誤 ,都永遠不會拋出該異常。
從文檔中 :
IllegalArgumentException-(可選)如果發現數組元素的自然順序違反了Comparable協定
我在提到的合同上沒有找到太多內容,但是恕我直言,它應該代表一個總訂單 (即, compareTo
方法定義的關系必須是transitive , antisymmetric和total )。 如果不滿足該要求,則sort
可能會拋出IllegalArgumentException
。 (我說這可能是因為未能滿足此要求可能會被忽略。)
編輯:添加鏈接到使一個關系總訂單的屬性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.