当前位置: 首页 > 知识库问答 >
问题:

TimSort什么时候抱怨比较器损坏?

秦炜
2023-03-14

Java 7更改了排序算法,从而抛出

java.lang.IllegalArgumentException:“比较方法违反了其总合同!”

在某些情况下,当使用的比较器有故障时。是否可以确定比较器中的哪种错误导致了这种情况?在我的实验中,如果x!=x,如果x也没有关系

(如果有一个通用的规则,在比较器中寻找bug可能会更容易。但是当然最好是修复所有的bug。:-) )

特别是,以下两个比较器没有让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

共有2个答案

黎浩然
2023-03-14

从文档中:

IllegalArgumentException - (可选)如果发现数组元素的自然顺序违反了可比较合约

我在提到的合同中没有找到太多内容,但IMHO它应该表示一个总体顺序(即compareTo方法定义的关系必须是传递的、反对称的和总体的)。如果不满足该要求,排序可能会抛出IllegalArgumentException。(我说可能是因为未能满足这一要求可能会被忽视。)

编辑:添加了使关系成为总订单的属性的链接。

林绪
2023-03-14

看了< code>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时,才会抛出异常。第一个观察结果如下:在通常的合并过程中,由于循环直接中止一次lenthis1,因此永远不会抛出异常。因此,异常只能作为疾驰的结果抛出。

这已经给出了异常行为不可靠的强烈暗示:只要您的数据集很小(小到生成的运行可能永远不会运行,因为< code>MIN_GALLOP是< code>7),或者生成的运行总是巧合地生成一个永远不会运行的合并,您就永远不会收到异常。因此,在没有进一步回顾< code>gallopRight方法的情况下,我们可以得出这样的结论:您不能依赖异常:无论您的比较器有多么错误,它都可能永远不会被抛出。

 类似资料:
  • 所以我正在学习Comparator和Comparable,我有以下问题。我有一门课: 另一个类Name实现了可比较的,在构造函数中有两个String。我不完全理解的是比较器的功能,我读过Java留档,我知道它用于对元素进行不同的排序,而不改变我的例子中的名称类它也可以在某些情况下允许空值,但是这个我的类构造函数中的声明工作正常,我根本不需要在PhoneBook类中实现比较器接口: 并实现了我希望它

  • 问题内容: 我在mysql db表中有一个布尔字段。 要获得所有未过时的测试用例的计数,可以像这样简单地完成: 效果很好,但是flake8报告以下警告: E712:与False的比较应为“如果cond为False:”或“如果非cond:” 好吧,我认为这是有道理的。因此,将我的代码更改为此: 要么 但是它们都不起作用。结果始终为0。我认为filter子句不支持运算符“ is”或“ is not”。

  • 问题内容: 具有函数f(x,y,z),我需要求解约束f(x,y,z)= 0,然后对其进行绘制。我试图为每对(y,z)查找f(x,y,z)= 0的值x: Python(2.7.5)说“ TypeError:fsolve:’func’参数’func’的输入和输出形状不匹配。” 但是,如果我自己进行测试,它会具有相同的形状: 返回True。 为什么fsolve()抱怨? 问题答案: 期望参数和的返回值为

  • [Error]org.testng.testngException:无法将@Test annotated方法[testLoginPage]与[interface java.util.Map]插入。有关本机依赖项注入的更多信息,请参阅http://testng.org/doc/documentation-main.html#native-dependent ency-injection at org

  • 问题内容: 这是我在Linux上编译的一些代码: 很好 很好 失败并显示以下错误: 不喜欢Linux中C99的定义与C99有何不同? 问题答案: 这是预处理和GNU C vs C99的一系列后果。 首先,: 包括 稍后,它在一个块内定义。 所以: 什么啊 -这是BSD和System V共有的东西 在这一点上定义了吗?-我们需要检查一下 所以现在: 默认情况下,当您使用GCC时定义(因为这就是C99

  • 问题内容: 通常,您需要编写如下代码: 这似乎有点冗长,而且我还听说使用强制解包运算符可能是不安全的,最好避免使用。有没有更好的方法来解决这个问题? 问题答案: 几乎总是没有必要检查可选项是否没有。几乎唯一需要这样做的时间是,如果它的-ness是 唯一 要了解的内容–您不在乎值的含义,而不必在意。 在大多数其他情况下,还有一些Swift速记可以更安全,简洁地为您完成任务。 如果不是,则使用该值 代