【问题标题】:Efficient way to compare two similar maps in java在java中比较两个相似地图的有效方法
【发布时间】:2021-04-27 17:53:01
【问题描述】:

假设我有两张地图:

HashMap<TestClass, Integer> map1, map2;

TestClass 被定义为:

class TestClass {
    int i;

    public TestClass(int i){
        this.i = i;
    }
}

下面的代码来填充地图:

map1.put(new TestClass(1), 1);
map1.put(new TestClass(2), 2);

map2.put(new TestClass(1), 1);
map2.put(new TestClass(2), 2);

如果两个映射都包含“相同”的键和值,我想编写一个返回 true 的方法。 是的,在上面的示例中,我可以创建一个局部变量来存储第一个构造函数调用的结果并将其传递给第二个映射,但是 我不能在我的实际应用程序中这样做因为我想比较类似的对象,它们本身不相等,至少就 java 的 equals 方法的实现而言。

我之前的实现(简化):

if(map1.size() == map2.size()){
    for(String key1 : map1.keySet()){
        if(!map1.get(key1).equals(map2.get(key1))
            return false;
    }
    return true;
} else
    return false;

这适用于Strings,因为即使在不同位置实例化两次,它们也彼此相等。

由于两次执行 TestClass 的构造函数会返回两个不同的对象,map2.get(key1) 将返回 null(或抛出异常,不完全确定),因为 key1 不在 map2 中。

为了比较两张地图,我编写了以下代码(简化):

if(map1.size() == map2.size()){
    for(TestClass key1 : map1.keySet()){
        boolean foundEqualing = false;
        for (TestClass key2 : map2.keySet()) {
            // Search equaling 
            if (key1.i == key2.i) {
                // Check if corresponding values equal as well
                if (map1.get(key1).equals(map2.get(key2))
                    // Store that a equaling key value pair was found
                    foundEqualing = true;
                // Break if keys equaled each other
                break;
            }
        }
        // Return false if no equaling key was found or if keys equal each other but the corresponding values don't
        if (!foundEqualing)
            return false;
    }
    return true;
} else
    return false;

我对这段代码的问题是它循环遍历两个地图,这对我来说似乎效率很低。我不熟悉正确的符号,但如果我没记错的话,如果地图的大小翻倍,那么操作的时间会增加四倍。

有没有一种方法可以比编写 for 循环更有效地循环或过滤这些映射?

我的真实世界代码使用反射,因此不要过分关注提供的示例。地图的类型可能来自每一种类型(我唯一知道的是它们必须实现某个接口,否则它们会被忽略)。

编辑:

我目前正在考虑使用流过滤器收集语法,但我从未使用过。无论如何,这更有效还是只是在内部循环地图?

【问题讨论】:

  • @Abra containsKey 将不起作用,因为两个映射中的键不相同(至少对于 java)。
  • @Abra 我只使用 equals 方法来比较值,而不是键。

标签: java dictionary iterator equals


【解决方案1】:

如果您可以在您的TestClass 中实现equals 方法,这可以更容易地完成。您根本不必使用循环。您也可以将equals 与地图一起使用。

Map.equals() 的工作方式是使用 Object.equals() 方法比较键和值。这意味着它只有在 key 和 value 对象都正确实现 equals() 时才有效。

import java.util.HashMap;
import java.util.Objects;

class Main {

    public static void main(String[] args) {
        HashMap<TestClass, Integer> map1, map2;
        map1 = new HashMap<>();
        map2 = new HashMap<>();
        map1.put(new TestClass(1), 1);
        map1.put(new TestClass(2), 2);

        map2.put(new TestClass(1), 1);
        map2.put(new TestClass(2), 2);

        System.out.println(map1.equals(map2));
    }

}

class TestClass {
    int i;

    public TestClass(int i){
        this.i = i;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestClass testClass = (TestClass) o;
        return i == testClass.i;
    }

    @Override
    public int hashCode() {
        return Objects.hash(i);
    }
}

编辑:如cmets中所说,由于使用了反射,无法使用上述实现。通过进行通常的空间和时间交易,仍然可以在时间复杂度方面提高性能。

由于无论如何您都必须编写逻辑来比较两个对象的相等性,因此我创建了一个带有附加值字段的类并在那里实现了equals 逻辑。我在这个类中使用 HashSet,基本上将时间复杂度从 O(m*n) 降低到 O(max(m,n))。(假设大小为 m 和 n)。

import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;

class Main {

    public static void main(String[] args) {
        HashMap<TestClass, Integer> map1, map2;
        map1 = new HashMap<>();
        map2 = new HashMap<>();
        map1.put(new TestClass(1), 1);
        map1.put(new TestClass(2), 2);

        map2.put(new TestClass(1), 1);
        map2.put(new TestClass(2), 2);
        boolean check = checkEqual(map1, map2);
        System.out.println(check);

        //---------------------------------------

        map1 = new HashMap<>();
        map2 = new HashMap<>();
        map1.put(new TestClass(1), 1);
        map1.put(new TestClass(2), 2);

        map2.put(new TestClass(1), 1);
        map2.put(new TestClass(2), 3);
        check = checkEqual(map1, map2);
        System.out.println(check);

    }

    private static boolean checkEqual(HashMap<TestClass, Integer> map1, HashMap<TestClass, Integer> map2) {
        HashSet<TestAndValue> set = new HashSet<>();
        map1.forEach((k,v) -> set.add(new TestAndValue(k,v)));
        for(TestClass t: map2.keySet()) {
            if(!set.contains(new TestAndValue(t, map2.get(t))))
                return false;
        }
        return true;
    }


}
class TestAndValue {
    TestClass t;
    int val;

    public TestAndValue(TestClass t, int val) {
        this.t = t;
        this.val = val;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestAndValue that = (TestAndValue) o;
        return val == that.val && t.i == that.t.i;
    }

    @Override
    public int hashCode() {
        return Objects.hash(t.i, val);
    }
}

class TestClass {
    int i;

    public TestClass(int i){
        this.i = i;
    }
}

输出是:

true
false

虽然实现很混乱,但我希望它能给你足够的想法来在线性时间内实现它。

【讨论】:

  • 与上一个(或多或少同时)答案相同:我相信我不能这样做。正如我所说,我正在使用反射,并且不确定最终类的实际外观(这取决于实现我的库的人,我不希望他们强制实现这样的方法,或者我应该吗?)。我唯一知道的是该类实现了某个接口,但我认为我不能覆盖接口中的equalshashCode 方法,可以吗?另外:我不确定我是否会想要覆盖默认的 equals 方法,因为我不确定它是否在其他任何地方以其默认形式使用。
  • @Jojomatik 更新了答案以包含另一个实现,虽然有点混乱,但对于较大的输入来说更快。如果大小增加到两倍,时间不会增加四倍。希望这会有所帮助。
猜你喜欢
  • 2013-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-21
  • 1970-01-01
  • 1970-01-01
  • 2014-05-15
  • 1970-01-01
相关资源
最近更新 更多