总有序比较器的部分有序比较器

时间:2015-08-12 16:16:27

标签: java algorithm sorting partial-ordering

首先:这不是问题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个实例使用部分排序Objecta.before(b) iff ab都是整数,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,显然不是有效的总排序。

这给我留下了最后一个问题:如何修复此错误?

4 个答案:

答案 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进行部分订购。它假设您比较ab,您可以说它是大于,等于还是小于。没有其他选择。

但是,其他排序算法没有此要求。插入排序就是其中之一。必须遵循a < bb < 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;