自包含阵列深等于

时间:2012-03-05 07:02:43

标签: java arrays algorithm

我需要对两个可能包含它们的Object []数组进行结构比较:

Object[] o1 = new Object[] { "A", null };
o1[1] = o1;

Object[] o2 = new Object[] { "A", null };
o2[1] = o2;

Arrays.deepEquals(o1, o2); // undefined behavior

不幸的是,deepEquals在这种情况下不起作用。上面的例子应该是真的。

是否有可以可靠计算的算法?

我的想法大致如下:

List<Object> xs = new ArrayList<>();
List<Object> ys = new ArrayList<>();

boolean equal(Object[] o1, Object[] o2, List<Object> xs, List<Object> ys) {
   xs.add(o1);
   ys.add(o2);
   boolean result = true;
   for (int i = 0; i < o1.length; i++) {
       if (o1[i] instanceof Object[]) {
           int idx1 = xs.lastIndexOf(o1[i]);
           if (idx1 >= 0) { idx1 = xs.size() - idx1 - 1; }
           if (o2[i] instanceof Object[]) {
               int idx2 = xs.lastIndexOf(o2[i]);
               if (idx2 >= 0) { idx2 = ys.size() - idx2 - 1; }
               if (idx1 == idx2) {
                   if (idx1 >= 0) {
                       continue;
                   }
                   if (!equal(o1[i], o2[i], xs, ys)) {
                       result = false;
                       break;
                   }
               }
           }
       }
   }
   xs.removeLast();
   ys.removeLast();
   return result;
}

4 个答案:

答案 0 :(得分:2)

正如我在上面的评论中提到的,你的代码有一些编译错误,你已经遗漏了很多,这使得很难100%确定完全它是怎么回事代码完成后工作。但是在完成代码之后,修复了一个明确的拼写错误(你写了idx2 = xs.lastIndexOf(o2[i]),但我确定你的意思是idx2 = ys.lastIndexOf(o2[i]))而且我认为的一件事是错字(我不要你想让if (!equal(o1[i], o2[i], xs, ys))嵌套在if (idx1 == idx2)里面),删除一些无操作代码,然后重构一下(改为一种我觉得更清晰的风格) ; YMMV),我明白了:

boolean equal(final Object[] o1, final Object[] o2)
{
    return _equal(o1, o2, new ArrayList<Object>(), new ArrayList<Object>());
}

private static boolean _equal(final Object[] o1, final Object[] o2,
                                 final List<Object> xs, final List<Object> ys)
{
    if(o1.length != o2.length)
        return false;

    xs.add(o1);
    ys.add(o2);
    try
    {
        for(int i = 0; i < o1.length; i++)
        {
            if(o1[i] == null && o2[i] == null)
                continue;
            if(o1[i] == null || o2[i] == null)
                return false;
            if(o1[i].equals(o2[i]))
                continue;
            if(! (o1[i] instanceof Object[]) || ! (o2[i] instanceof Object[]))
                return false;

            final int idx1 = xs.lastIndexOf(o1[i]);

            if(idx1 >= 0 && idx1 == ys.lastIndexOf(o2[i]))
                continue;

            if(! _equal((Object[])o1[i], (Object[])o2[i], xs, ys))
                return false;
        }

        return true;
    }
    finally
    {
        xs.remove(xs.size() - 1);
        ys.remove(ys.size() - 1);
    }
}

主要是的作品。逻辑是,每当它得到两个Object[]时,它会检查它是否正在将它们中的每一个在堆栈中的更高位置进行比较,如果是,它会检查是否最顶层的堆栈帧正在比较一个它们也是最顶层的堆栈框架,正在比较另一个。 (你想要的逻辑,对吗?)

我能看到的唯一严重错误是在这种情况下:

// a one-element array that directly contains itself:
final Object[] a = { null }; a[0] = a;
// a one-element array that contains itself via another one-element array:
final Object[][] b = { { null } }; b[0][0] = b;

// should return true (right?); instead, overflows the stack:
equal(a, b, new ArrayList<Object>(), new ArrayList<Object>());

您会在上面看到xs的最后一个元素始终为a,但ys的最后一个元素将在b和{{1}之间交替}}。在每次递归调用中,b[0]始终是xs.lastIndexOf(a)的最佳索引,而xsys.lastIndexOf(b)(无论哪一个)将始终为 less < / em>而不是ys.lastIndexOf(b[0])的最大索引。

问题是,逻辑不应该是,“ys的最顶层比较与o1[i]的最顶层比较在同一堆栈帧中”;相反,它应该是“存在一些堆栈框架 - 任何堆栈框架 - 将o2[i]o1[i]进行比较”。但是为了提高效率,我们实际上可以使用逻辑“有或者曾经是,正在/正在比较o2[i]o1[i]”的堆栈框架;我们可以使用o2[i]个数组对,而不是两个Set个数组。为此,我写了这个:

List

应该很清楚,上面不能导致无限递归,因为如果程序有一定数量的数组,那么它有一定数量的数组,一次只能有一个堆栈帧比较一对给定的数组(因为,一对开始被比较,它被添加到private static boolean equal(final Object[] a1, final Object[] a2) { return _equal(a1, a2, new HashSet<ArrayPair>()); } private static boolean _equal (final Object[] a1, final Object[] a2, final Set<ArrayPair> pairs) { if(a1 == a2) return true; if(a1.length != a2.length) return false; if(! pairs.add(new ArrayPair(a1, a2))) { // If we're here, then pairs already contained {a1,a2}. This means // either that we've previously compared a1 and a2 and found them to // be equal (in which case we obviously want to return true), or // that we're currently comparing them somewhere higher in the // stack and haven't *yet* found them to be unequal (in which case // we still want to return true: if it turns out that they're // unequal because of some later difference we haven't reached yet, // that's fine, because the comparison higher in the stack will // still find that). return true; } for(int i = 0; i < a1.length; ++i) { if(a1[i] == a2[i]) continue; if(a1[i] == null || a2[i] == null) return false; if(a1[i].equals(a2[i])) continue; if(! (a1[i] instanceof Object[]) || ! (a2[i] instanceof Object[])) return false; if(! _equal((Object[]) a1[i], (Object[]) a2[i], pairs)) return false; } return true; } private static final class ArrayPair { private final Object[] a1; private final Object[] a2; public ArrayPair(final Object[] a1, final Object[] a2) { if(a1 == null || a2 == null) throw new NullPointerException(); this.a1 = a1; this.a2 = a2; } @Override public boolean equals(final Object that) { if(that instanceof ArrayPair) if(a1 == ((ArrayPair)that).a1) return a2 == ((ArrayPair)that).a2; else if(a1 == ((ArrayPair)that).a2) return a2 == ((ArrayPair)that).a1; else return false; else return false; } @Override public int hashCode() { return a1.hashCode() + a2.hashCode(); } } ,并且任何将来尝试比较该对将立即返回pairs),这意味着总堆栈深度在任何给定时间都是有限的。 (当然,如果数组的数量很大,那么上面仍然会溢出堆栈;递归是有界的,但最大堆栈大小也是如此。实际上,我建议true - 循环被分成两个for - 循环,一个接一个:第一次,跳过所有的数组元素,第二次,跳过所有不是的元素。这可以避免许多昂贵的比较的情况。)

还应该清楚的是,上面的内容永远不会返回for,因为它只会在找到实际差异时返回false

最后,我认为应该清楚,当它应该返回true时,上面的内容永远不会返回false,因为对于每对对象,一个完整的循环是总是在所有元素上做出来的。这一部分更难以证明,但实质上,我们已经定义了结构相等性,即如果我们能够找到它们之间的某些差异,那么两个数组在结构上只是不相等;并且上面的代码最终检查它遇到的每个数组的每个元素,所以如果有可查找的差异,它会找到它。

注意:

  • 我不担心基元数组,truefalse等等。亚当的回答提出了你希望这些元素也可以元素化的可能性;如果需要,它很容易添加(因为它不需要递归:基元数组不能包含数组),但上面的代码只使用int[],这意味着引用相等。
  • 上面的代码假设double[]实现了对称关系,正如其契约所指定的那样。然而,实际上,合同并不总能实现;例如,Object.equals(Object)Object.equals(Object),而new java.util.Date(0L).equals(new java.sql.Timestamp(0L))true。如果订单对您的目的很重要 - 如果您希望new java.sql.Timestamp(0L).equals(new java.util.Date(0L))falseequal(new Object[]{java.util.Date(0L)}, new Object[]{java.sql.Timestamp(0L)})true,则您需要更改equal(new Object[]{java.sql.Timestamp(0L)}, new Object[]{java.util.Date(0L)}),也许false,关心哪个数组是哪个。

答案 1 :(得分:1)

您可以将所有访问过的对象添加到临时Map<Object, Object>结构中,以确保您不再访问/检查它们。该值始终是一个新对象,将用于替换结果列表中已访问过的实例。

每当你看到一个物体时,

  1. 检查地图是否包含实例
  2. 如果没有,将它放到地图上,地图值是一个新的对象
  3. 如果是,请使用列表中的地图值(唯一的新对象)(xsys
  4. 在您的示例中,结果列表应如下所示(伪语言):

    xs == {o1, "A", obj2}     // obj2 == map.get(o2);
    ys == {o2, "A", obj1}     // obj1 == map.get(o1);
    

    这可以防止无限循环。

答案 2 :(得分:0)

我编写了这个辅助函数来展平每个数组,并用每个级别的字符串(或任何基元)替换它自己的引用。地图将使用键值存储来跟踪现有的数组对象(初始化时为新)。假设原始数组中只有数组和类似对象,您可以比较两个展平列表(具有相同的起始级别),而不必担心无限循环。这种比较应该是线性的。

private List<Object> flatten(Object[] array, Map<Object, String> map, int level) {
    List<Object> list = new ArrayList<>();
    for (Object o : array) {
        if (o instanceof Object[]) {
            if (map.get(o) != null) {
                list.add(map.get(o));
            } else {
                map.put(array, "level"+level);
                List<Object> flattened = flatten((Object[]) o, map, level+1);
                for (Object obj : flattened)
                    list.add(obj);
            }
        } else {
            list.add(o);
        }
    }
    return list;
}

希望这会有所帮助。

答案 3 :(得分:0)

我认为我已经设法了,至少适用于我迄今为止尝试的所有测试用例。请有人确认我的逻辑是否正常。需要使用鲜为人知的IdentityHashMap来通过引用跟踪以前访问过的节点。

不知道如何处理其他原语的嵌套数组,如int []等。我为int []添加了case,但是有float,double,byte,short,long,boolean可以添加。

public static boolean deepEquals(Object [] o1, Object [] o2) {
    return deepEquals(o1, o2, new IdentityHashMap<Object, Integer>());
}

public static boolean deepEquals(Object o1, Object o2, IdentityHashMap<Object, Integer> visited) {
    if (! visited.containsKey(o1)) {
        visited.put(o1, 0);
    } else {
        visited.put(o1, visited.get(o1) + 1);
    }
    if (! visited.containsKey(o2)) {
        visited.put(o2, 0);
    } else {
        visited.put(o2, visited.get(o2) + 1);
    }
    boolean ret = false;
    if (o1 == o2) {
        ret = true; 
    } else if (o1 instanceof Object[] && o2 instanceof Object[]){
        Object [] a1 = (Object[]) o1;
        Object [] a2 = (Object[]) o2;
        if (a1.length != a2.length ) {
            ret = false; // different length, can't be equal
        } else if (visited.get(o1) > 0 || visited.get(o2) > 0) {
            ret = true; // been here before, stop searching
        } else {
            ret = true;
            for (int i = 0; i < a1.length; i++) {
                if (! deepEquals(a1[i], a2[i], visited)) {
                    ret = false;
                    break;
                }
            }
        }
    } else if (o1 instanceof int[] && o2 instanceof int[]){
        ret = Arrays.equals((int[])o1, (int[])o2);
    } else if (o1 == null && o2 == null){
        ret = true; // null = null?
    } else {
        ret = o1.equals(o2); // just use equals
    }
    return ret;
}