找出比较器可能不起作用的情况

时间:2015-11-30 13:58:47

标签: java sorting comparator

我想对List of(整数列表)进行排序,以便将包含数字3的列表放在列表顶部,使剩余元素的现有顺序保持不变。

    final ArrayList<ArrayList<Integer>> arrayLists = Lists.newArrayList(
        Lists.newArrayList(1, 2),
        Lists.newArrayList(1, 2, 3),
        Lists.newArrayList(1, 2),
        Lists.newArrayList(1, 3),
        Lists.newArrayList(1, 4),
        Lists.newArrayList(1, 2, 3)
    );
    System.out.println(arrayLists);

[[1, 2], [1, 2, 3], [1, 2], [1, 3], [1, 4], [1, 2, 3]]

初步尝试使用以下比较器

Comparator<List<Integer>> c = new Comparator<List<Integer>>() {
    @Override
    public int compare(final List<Integer> o1, final List<Integer> o2) {
        System.out.println("Compare " + o1 + "<=>" + o2);
        if (o1.contains(3))
            return -1;
        return 0;
    }
};

Collections.sort(arrayLists, c);
System.out.println(arrayLists);

返回

Compare [1, 2, 3]<=>[1, 2]
Compare [1, 2]<=>[1, 2, 3]
Compare [1, 2]<=>[1, 2]
Compare [1, 3]<=>[1, 2]
Compare [1, 3]<=>[1, 2, 3]
Compare [1, 4]<=>[1, 2]
Compare [1, 4]<=>[1, 2]
Compare [1, 2, 3]<=>[1, 2]
Compare [1, 2, 3]<=>[1, 2, 3]
Compare [1, 2, 3]<=>[1, 3]

[[1, 2, 3], [1, 3], [1, 2, 3], [1, 2], [1, 2], [1, 4]]

这是预期的(所有包含3的列表都在顶部)

然而,深入了解javadoc for Comparator表明

  

实现者必须确保所有x和y的sgn(compare(x,y))== -sgn(compare(y,x))。

     

实现者还必须确保关系是传递的:((compare(x,y)&gt; 0)&amp;&amp;(compare(y,z)&gt; 0))暗示比较(x,z)&gt ; 0

     

最后,实现者必须确保compare(x,y)== 0意味着所有z的sgn(compare(x,z))== sgn(compare(y,z))

上述比较器没有完全实现,下面的测试很容易断言。

    final ArrayList<Integer> x = Lists.newArrayList(1, 2, 3);
    final ArrayList<Integer> y = Lists.newArrayList(1, 2);
    System.out.println(c.compare(x,y));
    System.out.println(c.compare(y,x));


Compare [1, 2, 3]<=>[1, 2] => -1
Compare [1, 2]<=>[1, 2, 3] => 0 which is not -(-1)

有没有办法真正证明上面的比较器不适用于某些特定的示例List(它没有将包含3的列表放在顶部)?

4 个答案:

答案 0 :(得分:3)

  

有没有办法证明上面的比较器在某些情况下不起作用?

是。最明显的原因是它可以返回-1但不会返回正数。这显然违反了第一条规则。

不违反规则的比较器是

Comparator<List<Integer>> c = new Comparator<List<Integer>>() {
    @Override
    public int compare(final List<Integer> o1, final List<Integer> o2) {
        return Integer.compare(o1.contains(3) ? 0 : 1, o2.contains(3) ? 0 : 1);
    }
};

在Java 8中,您可以将其简化为

Comparator<List<Integer>> c = Comparator.comparingInt(o -> o.contains(3) ? 0 : 1);

我建议使用新方法Comparator.comparingX。除了减少冗长之外,它还使编写正确的compare方法变得更加容易。

这是有效的,Collections.sort的文档保证

  

这种保证是稳定的:相同的元素不会因排序而重新排序。

另一种方法是迭代原始列表并形成两个单独的列表,一个包含包含3的列表,另一个包含不包含3的列表,然后在addAll处使用sort结束。这比使用O(n)O(n log n)而不是module Gexcore class Myability include CanCan::Ability def initialize(user) can :delete_user, User do |victim| .. end end end end )的方法具有更好的时间复杂度。

答案 1 :(得分:2)

问题是你没有回复1。您需要在比较中考虑所有个案。所以你应该拥有的是

Comparator<List<Integer>> c = new Comparator<List<Integer>>() {
    @Override
    public int compare(final List<Integer> o1, final List<Integer> o2) {
        System.out.println("Compare " + o1 + "<=>" + o2);
        if (o1.contains(3) && !o2.contains(3)) {
            return -1;
        } else if (!o1.contains(3) && o2.contains(3)) {
            return 1;
        } else {
            return 0;
        }
    }
};

<强>更新

要证明您的比较在某些情况下不起作用,请x为包含3的列表,y为列表,其中包含3个。您的比较我们将sgn(compare(x, y))视为否定,将sgn(compare(y, x))视为零,使Comparator合约的第一个条件无效。

此外,由于您的比较并未返回正值,因此您无法获得任何传递性。这无视合同的其他两个条件。

通常,您希望尽可能考虑所有情况。这将确保稳定的代码。

答案 2 :(得分:1)

根据文档中提到的限制,有一些改进比较器或使比较器有效的建议。

但实际问题是

  

有没有办法真正证明上面的比较器不适用于某些特定的示例List(它没有将包含3的列表放在顶部)?

答案是:是的,有。

这可能不是您实际的意图背后的问题。但是一个非常实用的检查可能看起来像这样:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ComparatorTest
{
    public static void main(String[] args)
    {
        List<List<Integer>> arrayLists = new ArrayList<List<Integer>>(
            Arrays.asList(
            Arrays.asList(1, 2),
            Arrays.asList(1, 2, 3),
            Arrays.asList(1, 2),
            Arrays.asList(1, 3),
            Arrays.asList(1, 4),
            Arrays.asList(1, 2, 3)
        ));
        System.out.println(arrayLists);
        Comparator<List<Integer>> c = new Comparator<List<Integer>>() {
            @Override
            public int compare(final List<Integer> o1, final List<Integer> o2) {
                //System.out.println("Compare " + o1 + "<=>" + o2);
                if (o1.contains(3))
                    return -1;
                return 0;
            }
        };

        Collections.sort(arrayLists, c);
        System.out.println(arrayLists);        

        validate(arrayLists, c);;
    }

    private static <T> void validate(
        List<T> list, Comparator<? super T> comparator)
    {
        for (int i=0; i<list.size(); i++)
        {
            for (int j=0; j<list.size(); j++)
            {
                T x = list.get(i);
                T y = list.get(j);
                int xy = comparator.compare(x, y);
                int yx = comparator.compare(y, x);

                if (Math.signum(xy) != -Math.signum(yx))
                {
                   System.out.println(
                       "The implementor must ensure that " +
                       "sgn(compare(x, y)) == -sgn(compare(y, x)) " +
                       "for all x and y.");
                   System.out.println("This is not the case for x="+x+", y="+y);
                }
                for (int k=0; k<list.size(); k++)
                {
                    T z = list.get(k);
                    int yz = comparator.compare(y, z);
                    int xz = comparator.compare(x, z);
                    if (xy > 0 && yz > 0)
                    {
                        if (xz <= 0)
                        {
                            System.out.println(
                                "The implementor must ensure that " +
                                "the relation is transitive: " +
                                "((compare(x, y)>0) && (compare(y, z)>0)) " +
                                "implies compare(x, z)>0.");
                            System.out.println(
                                "This is not the case for " +
                                "x="+x+", y="+y+", z="+z);
                        }
                    }

                    if (xy == 0)
                    {
                        if (Math.signum(xz) != -Math.signum(yz))
                        {
                            System.out.println(
                                "Ihe implementor must ensure that " +
                                "compare(x, y)==0 implies that " +
                                "sgn(compare(x, z))==sgn(compare(y, z)) " +
                                "for all z");
                            System.out.println(
                                "This is not the case for " +
                                "x="+x+", y="+y+", z="+z);
                        }
                    }
                }
            }
        }
    }
}

该程序的输出是

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y.
This is not the case for x=[1, 2, 3], y=[1, 2, 3]
The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y.
This is not the case for x=[1, 2, 3], y=[1, 3]
...
Ihe implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z
This is not the case for x=[1, 2], y=[1, 2, 3], z=[1, 2, 3]
Ihe implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z
This is not the case for x=[1, 2], y=[1, 2, 3], z=[1, 3]
...

列出给定比较器和给定列表的所有合同违规行为。

答案 3 :(得分:0)

如果您想保留订单,请尝试以下初始数据:

 final ArrayList<ArrayList<Integer>> arrayLists = Lists.newArrayList(
    Lists.newArrayList(3, 1, 2),        
    Lists.newArrayList(1, 2),
    Lists.newArrayList(1, 2, 3),
    Lists.newArrayList(1, 2),
    Lists.newArrayList(1, 3),
    Lists.newArrayList(1, 4),
    Lists.newArrayList(1, 2, 3)
);

您可以看到订单未保留,因为最终结果是:

[[1, 2, 3], [1, 3], [1, 2, 3], [3, 1, 2], [1, 2], [1, 4]]

您的比较器始终将包含3值的列表放在最上面,但不能确保保留订单。为此,您必须按照文档中的说明实现对称比较。