首先:这不是问题Partial Ordered Comparator的重复,而是建立在它之上。
我的目标是在原地对对象列表(例如[2,“a”,1])进行排序,这样在排序后没有两个整数出现故障。
为此,我使用了this answer中的实现,并使用了以下部分排序,得到了IllegalArgumentException
:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:868)
at java.util.TimSort.mergeAt(TimSort.java:485)
at java.util.TimSort.mergeCollapse(TimSort.java:410)
at java.util.TimSort.sort(TimSort.java:214)
at java.util.TimSort.sort(TimSort.java:173)
at java.util.Arrays.sort(Arrays.java:659)
at java.util.Collections.sort(Collections.java:217)
at MySortUtils.sortPartially(ArimsCollectionUtils.java:150)
这是因为建议的比较器存在缺陷。示范:
对所有R
个实例使用部分排序Object
,a.before(b)
iff a
和b
都是整数,a < b
根据整数的自然顺序:
public boolean before(Object a, Object b) {
// only integers are ordered
if (a instanceof Integer && b instanceof Integer) {
int intA = ((Integer) a).intValue();
int intB = ((Integer) b).intValue();
return intA < intB;
} else {
return false;
}
}
原因是以下实施
Comparator<Object> fullCmp = new Comparator<Object>() {
// Implementation shamelessly plucked from
// https://stackoverflow.com/a/16702332/484293
@Override
public int compare(Object o1, Object o2) {
if(o1.equals(o2)) {
return 0;
}
if(partialComparator.before(o1, o2)) {
return -1;
}
if(partialComparator.before(o2, o1)) {
return +1;
}
return getIndex(o1) - getIndex(o2);
}
private Map<Object ,Integer> indexMap = new HashMap<>();
private int getIndex(Object i) {
Integer result = indexMap.get(i);
if (result == null) {
indexMap.put(i, result = indexMap.size());
}
return result;
}
};
这可以在生成的排序中产生一个循环,因为
// since 2 and "a" are incomparable,
// 2 gets stored with index 0
// "a" with index 1
assert fullCmp.compare(2, "a") == -1
// since "a" and 1 are incomparable,
// "a" keeps its index 1
// 2 gets index 2
assert fullCmp.compare("a", 1) == -1
// since 1 and 2 are comparable:
assert fullCmp.compare(1, 2) == -1
都是正确的,即2&lt; “a”,“a”&lt; 1和“1&lt; 2,显然不是有效的总排序。
这给我留下了最后一个问题:如何修复此错误?
答案 0 :(得分:3)
我无法建议任何部分订购的完整解决方案。但是对于您的特定任务(比较忽略其他任何内容的整数),您只需要确定整数是在其他任何内容之前还是之后。假设整数首先出现的这个比较器应该可以正常工作(使用Java-8语法):
Comparator<Object> comparator = (a, b) -> {
if(a instanceof Integer) {
if(b instanceof Integer) {
return ((Integer) a).compareTo((Integer) b);
}
return -1;
}
if(b instanceof Integer)
return 1;
return 0;
};
示例:
List<Object> list = Arrays.asList("a", "bb", 1, 3, "c", 0, "ad", -5, "e", 2);
list.sort(comparator);
System.out.println(list); // [-5, 0, 1, 2, 3, a, bb, c, ad, e]
答案 1 :(得分:1)
您正在比较器中使用getIndex()
。通常,这很好,但在排序算法中交换值时不可行。
因此,选择一个仅依赖于值而不是依赖于它们在数组中位置的比较器函数。
您可以使非整数在所有整数之前或之后排序。要么使它们都相等(在比较器中返回0
),要么使用其他准则来区分它们。
答案 2 :(得分:0)
如果您想要的只是按照自己的自然顺序排序整数(而不是其他完全有序的类型),并且如果您不关心其他元素如何按照整数排序,但您确实希望结果为是一个正确的总排序(即传递和反对称),那么你开始使用和拒绝的答案的微小变化将起到作用:
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
class IntegerPartialOrderComperator implements Comparator<Object> {
@Override
public int compare(Object o1, Object o2) {
return getIndex(o1) - getIndex(o2);
}
private int getIndex(Object i) {
Integer result = indexMap.get(i);
if (result == null) {
if (i instanceof Integer) {
result = (Integer) i*2;
} else {
result = indexMap.size()*2+1;
}
indexMap.put(i, result);
}
return result;
}
private Map<Object,Integer> indexMap = new HashMap<>();
public static void main(String[] args) {
Comparator<Object> cmp = new IntegerPartialOrderComperator();
// since 2 and "a" are incomparable,
// 2 gets stored with index 4 and "a" with index 3
assert cmp.compare(2, "a") > 0;
// since "a" and 1 are incomparable,
// "a" keeps its index 3 while 1 gets index 2
assert cmp.compare("a", 1) > 0;
// since 1 and 2 are comparable:
assert cmp.compare(1, 2) < 0;
}
}
这使用所有值的运行时生成的索引作为比较的基础,其中偶数用作Integer
s的索引,奇数用作可能出现的任何其他值的索引。
如果您的数字变大(> 2^30-1
)或变小(< -2^30
),那么加倍将会溢出,因此您将不得不求助于BigInteger
索引的值类型地图。
请注意,对于Integer
旁边的许多类型,同样的技巧 不会起作用,因为您需要首先通过索引编号来表征您想要遵守的总订单。如果不可能的话,我认为解决方案将变得更加棘手:计算新元素的索引可能会花费最差时间线性的先前比较元素的数量,这只会破坏Comparator
的排序(有效)。
答案 3 :(得分:-1)
您可以将元素分组为可以相互比较的元素。你有问题可以比较(a,b)和canCompare(b,c)但是!canCompare(a,c)。但是我们假设情况并非如此
由于您没有使用传统的排序算法,因此这不具备可比性。但是,如果必须这样做,您可以先确定所需的订单并比较所需订单的索引。
一个简单的解决方法是提供任意排序策略,这样您就可以进行总排序。你遇到的问题是,如果你排序1, "a", 2
你期望发生什么?您可以将其保留为未定义,无论您是1, 2, "a"
还是"a", 1, 2
,还是说您已经按顺序排列了所有可比较的内容。如果后者没问题,冒泡排序将完成这项工作。
您无法使用TimSort进行部分订购。它假设您比较a
和b
,您可以说它是大于,等于还是小于。没有其他选择。
但是,其他排序算法没有此要求。插入排序就是其中之一。必须遵循a < b
和b < c
然后a < c
的唯一要求,否则您无法订购这些条目。
顺便说一句你不能让-1
意味着无比,因为-1
通常意味着大于。{/ p>
你能做的是
static final int INCOMPARABLE = Integer.MIN_VALUE;
// since 2 and "a" are incomparable,
// 2 gets stored with index 0
// "a" with index 1
assert fullCmp.compare(2, "a") == INCOMPARABLE;
// since "a" and 1 are incomparable,
// "a" keeps its index 1
// 2 gets index 2
assert fullCmp.compare("a", 1) == INCOMPARABLE;
// since 1 and 2 are comparable:
assert fullCmp.compare(1, 2) == -1;
assert fullCmp.compare(2, 1) == 1;