简体   繁体   English

比较java中两个相似地图的有效方法

[英]Efficient way to compare two similar maps in java

Imagine I have two maps:假设我有两张地图:

HashMap<TestClass, Integer> map1, map2;

with TestClass being defined as: TestClass 被定义为:

class TestClass {
    int i;

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

And the following code to fill the maps:并使用以下代码填充地图:

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

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

I want to write a method that returns true if both maps contain the "same" keys and values.如果两个映射包含“相同”的键和值,我想编写一个返回true的方法。 Yes, in above example I could just create a local variable to store the result of the first constructor call and pass it to the second map, but I can't do this in my real application as I want to compare similar objects that do not equal each other per se at least as far as java's implementation of the equals method goes.是的,在上面的示例中,我可以创建一个局部变量来存储第一个构造函数调用的结果并将其传递给第二个 map,但我不能在我的实际应用程序中这样做,因为我想比较类似的对象至少就 java 对equals方法的实现而言,它们本身是相等的。

My previous implementation (simplified):我以前的实现(简化):

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;

This works fine for String s, as they equal each other even if instantiated twice in different positions.这适用于String s,因为即使在不同位置实例化两次,它们也彼此相等。

As executing the constructor of TestClass twice returns two different objects though, map2.get(key1) will return null (or throw an exception, not entirely sure) as key1 is not in map2 .虽然两次执行 TestClass 的构造函数会返回两个不同的对象,但map2.get(key1)将返回 null(或抛出异常,不完全确定),因为key1不在map2中。

To compare both maps I have written the following code (simplified):为了比较这两个地图,我编写了以下代码(已简化):

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;

The issue I have with this code is that it loops through both maps, which seems really inefficient to me.我对这段代码的问题是它循环遍历了两个地图,这对我来说似乎效率很低。 I'm not familiar with the correct notations, but the time the operation takes quadruples if the size of the map doubles, if I'm not mistaken.我不熟悉正确的符号,但如果 map 的大小加倍,如果我没记错的话,操作时间会翻两番。

Is there a way to more efficiently loop through or filter these maps in another way than writing for loops?有没有一种方法可以比编写循环更有效地循环或过滤这些地图?

My real world code uses reflection, therefore do not focus too hard on the provided example.我的真实世界代码使用了反射,因此不要过分关注所提供的示例。 The types of the map could be from each and every type (the only thing I know is that they have to implement a certain interface, otherwise they are just ignored). map 的类型可能来自每种类型(我唯一知道的是它们必须实现某个接口,否则它们将被忽略)。

Edit:编辑:

I'm currently thinking about using the stream filter collect syntax, but I've never used that.我目前正在考虑使用 stream 过滤器收集语法,但我从未使用过它。 Is that more efficient anyway or does it just loop over the map internally as well?无论如何,这是否更有效率,还是它也只是在内部循环 map?

Imagine I have two maps:想象一下我有两张地图:

HashMap<TestClass, Integer> map1, map2;

with TestClass being defined as: TestClass 被定义为:

class TestClass {
    int i;

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

And the following code to fill the maps:以下代码填充地图:

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

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

I want to write a method that returns true if both maps contain the "same" keys and values.如果两个映射都包含“相同”的键和值,我想编写一个返回true的方法。 Yes, in above example I could just create a local variable to store the result of the first constructor call and pass it to the second map, but I can't do this in my real application as I want to compare similar objects that do not equal each other per se at least as far as java's implementation of the equals method goes.是的,在上面的示例中,我可以创建一个局部变量来存储第一个构造函数调用的结果并将其传递给第二个 map,但我不能在我的实际应用程序中这样做,因为我想比较类似的对象至少就 java 的equals方法的实现而言,它们本身是相等的。

My previous implementation (simplified):我之前的实现(简化):

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;

This works fine for String s, as they equal each other even if instantiated twice in different positions.这适用于String s,因为即使在不同位置实例化两次,它们也彼此相等。

As executing the constructor of TestClass twice returns two different objects though, map2.get(key1) will return null (or throw an exception, not entirely sure) as key1 is not in map2 .由于执行 TestClass 的构造函数两次返回两个不同的对象,但map2.get(key1)将返回 null (或抛出异常,不完全确定),因为key1不在map2中。

To compare both maps I have written the following code (simplified):为了比较两张地图,我编写了以下代码(简化):

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;

The issue I have with this code is that it loops through both maps, which seems really inefficient to me.我对这段代码的问题是它循环遍历两个地图,这对我来说似乎效率很低。 I'm not familiar with the correct notations, but the time the operation takes quadruples if the size of the map doubles, if I'm not mistaken.我不熟悉正确的符号,但如果我没记错的话,如果 map 的大小翻倍,那么操作时间会翻四倍。

Is there a way to more efficiently loop through or filter these maps in another way than writing for loops?有没有一种方法可以比编写 for 循环更有效地循环或过滤这些映射?

My real world code uses reflection, therefore do not focus too hard on the provided example.我的真实世界代码使用反射,因此不要过于关注提供的示例。 The types of the map could be from each and every type (the only thing I know is that they have to implement a certain interface, otherwise they are just ignored). map 的类型可能来自每一种类型(我唯一知道的是它们必须实现某个接口,否则它们会被忽略)。

Edit:编辑:

I'm currently thinking about using the stream filter collect syntax, but I've never used that.我目前正在考虑使用 stream 过滤器收集语法,但我从未使用过。 Is that more efficient anyway or does it just loop over the map internally as well?无论如何,这是否更有效,还是它只是在内部循环 map?

This could be done in a lot easier way if you could implement the equals method in your TestClass.如果您可以在您的 TestClass 中实现equals方法,这可以以更简单的方式完成。 You wouldn't have to use loops at all.您根本不必使用循环。 You can use equals with map as well.您也可以将equals与 map 一起使用。

The way that Map.equals() works is by comparing keys and values using the Object.equals() method. Map.equals() 的工作方式是使用 Object.equals() 方法比较键和值。 This means it only works when both key and value objects implement equals() properly.这意味着它仅在键和值对象都正确实现 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);
    }
}

Edit: As mentioned in the comments, the above implementation cannot be used due to the use of reflection.编辑:如评论中所述,由于使用反射,无法使用上述实现。 The performance can still be improved in terms of time complexity by doing the usual trade of space and time.通过进行通常的空间和时间交易,仍然可以在时间复杂度方面提高性能。

Since you have to anyway write the logic to compare, the equality of two objects, I created a class with the added value field and implemented the equals logic there.由于您无论如何都必须编写逻辑来比较两个对象的相等性,因此我创建了一个带有附加值字段的 class 并在那里实现了equals逻辑。 I am using a HashSet for this class essentially reducing the time complexity from O(m*n) to O(max(m,n)) .(Assuming sizes to be m and n).我正在为此 class 使用 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;
    }
}

The Output is: Output 是:

true
false

Although the implementation is messy, I hope it can give you enough idea to implement this in linear time.虽然实现起来比较乱,但希望能给大家足够的思路,在线性时间内实现这个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM