【问题标题】:How do I compare all objects in a collection against all objects in the same collection?如何将集合中的所有对象与同一集合中的所有对象进行比较?
【发布时间】:2022-01-02 04:24:09
【问题描述】:

如何将集合中的所有对象与同一集合中的所有其他对象进行比较?我目前将 for 循环与另一个嵌套的 for 循环一起使用。但是,这很慢,我想优化我的方法。

请参阅下面的示例。具体来说,如何优化personsGroupedByFirstName 方法?该方法检索Person 的列表并将其转换为地图,其中每个Person 与具有相同firstName 的其他人分组。

谢谢。

import java.util.*;

class Scratch {
    public static void main(String[] args) {
        new Scratch().start();
    }

    public void start() {
        List<Person> persons = getPersons();
        Map<String, List<Person>> stacks = personsGroupedByFirstName(persons);

        for (Map.Entry<String, List<Person>> entrySet : stacks.entrySet()) {
            String key = entrySet.getKey();
            List<Person> value = entrySet.getValue();

            System.out.println("First name: " + key);
            System.out.println("People: " + value);
        }
    }

    private List<Person> getPersons() {
        return List.of(
                new Person("Andrew", "Red"), new Person("Bob", "Red"),
                new Person("Craig", "Red"), new Person("Daniel", "Red"),
                new Person("Andrew", "Yellow"), new Person("Bob", "Yellow"),
                new Person("Craig", "Yellow"), new Person("Daniel", "Yellow"),
                new Person("Andrew", "Pink"), new Person("Bob", "Pink"),
                new Person("Craig", "Pink"), new Person("Daniel", "Pink"),
                new Person("Andrew", "Green"), new Person("Bob", "Green"),
                new Person("Craig", "Green"), new Person("Daniel", "Green"),
                new Person("Andrew", "Orange"), new Person("Bob", "Orange"),
                new Person("Craig", "Orange"), new Person("Daniel", "Orange"),
                new Person("Andrew", "Purple"), new Person("Bob", "Purple"),
                new Person("Craig", "Purple"), new Person("Daniel", "Purple"),
                new Person("Andrew", "Blue"), new Person("Bob", "Blue"),
                new Person("Craig", "Blue"), new Person("Daniel", "Blue")
        );
    }

    private Map<String, List<Person>> personsGroupedByFirstName(List<Person> persons) {
        Map<String, List<Person>> stacks = new HashMap<>();

        int loop = 0;
        for (Person outer : persons) {
            if (!outer.isReachable()) {
                continue;
            }

            String outerFirstName = outer.getFirstName();
            List<Person> stack = new ArrayList<>();
            for (Person inner : persons) {
                loop = loop + 1;
                if (!inner.isReachable()) {
                    continue;
                }

                if (isSimilar(outerFirstName, inner.getFirstName())) {
                    inner.setReachable(false);
                    stack.add(inner);
                }
            }

            List<Person> tmp = stacks.get(outerFirstName);
            if (tmp == null) {
                tmp = new ArrayList<>();
            }

            tmp.addAll(stack);
            stacks.put(outerFirstName, tmp);
        }

        System.out.println("Total Loops: " + loop);
        return stacks;
    }

    /*
     * Trivial condition. My actual use case computes the Levenshtein Distance.
     * */
    private boolean isSimilar(String outerFirstName, String outerSecondName) {
        return outerFirstName.equals(outerSecondName);
    }
}

class Person {
    private final String firstName;
    private final String secondName;
    private boolean reachable = true;

    public Person(String firstName, String secondName) {
        this.firstName = firstName;
        this.secondName = secondName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    public boolean isReachable() {
        return reachable;
    }

    public void setReachable(boolean reachable) {
        this.reachable = reachable;
    }

    @Override
    public String toString() {
        return "Person{" +
                "firstName='" + firstName + '\'' +
                ", secondName='" + secondName + '\'' +
                '}';
    }
}

【问题讨论】:

  • 您可以进行的一项优化:您的内部循环不需要遍历列表中的所有元素。只是在外循环中的那个之后。如果您的外部循环根据 2、3、4... 等检查元素 1,那么当您的外部循环检查元素 2 时,您已经将其与上一次迭代中的元素 1 进行了比较。所以没有必要再做一次。当然,与自己相比是毫无意义的。因此,没有必要从头开始内部循环,只有元素 3 中的所有内容才是您关心的内容
  • isReachable() 属性是什么?查询修改了它正在处理的对象,这看起来很奇怪。

标签: java performance collections comparison


【解决方案1】:

根本不需要执行两个嵌套循环。您是按属性分组,而不是“将每个对象相互比较”。您用来存储结果的映射已经通过键记住了所有遇到的对象。

你可以简单地使用

private Map<String, List<Person>> personsGroupedByFirstName(List<Person> persons) {
    Map<String, List<Person>> result = new HashMap<>();

    for(Person p: persons) {
        List<Person> list = result.get(p.getFirstName());
        if(list == null) {
            list = new ArrayList<>();
            result.put(p.getFirstName(), list);
        }
        list.add(p);
    }
    return result;
}

我认为这个isReachable() 测试是为了优化您的两个嵌套循环,而不是实际任务的一部分。

当您熟悉 lambda 表达式后,您可以进一步简化此循环,在地图上使用单个操作,而不是 getput

private Map<String, List<Person>> personsGroupedByFirstName(List<Person> persons) {
    Map<String, List<Person>> result = new HashMap<>();

    for(Person p: persons) {
        List<Person> list
            = result.computeIfAbsent(p.getFirstName(), key -> new ArrayList<>());
        list.add(p);
    }
    return result;
}

使用 Stream API 的等效操作是

private Map<String, List<Person>> personsGroupedByFirstName(List<Person> persons) {
    return persons.stream()
        .collect(Collectors.groupingBy(Person::getFirstName));
}

groupingBy(Person::getFirstName)groupingBy(Person::getFirstName, toList()) 的简写。

【讨论】:

  • 感谢您的反馈/评论。也许我的问题并不清楚。问题是“isSimilar”方法。在我的实际用例中,我正在比较新闻标题并确定它们是否在报道类似的主题。为此,我使用了 levenshtein 距离。因此,我不能简单地使用对象的属性,因为它们会有所不同,但可能相似。
  • 好吧,这段代码将您的示例中的 28 个人减少到四个唯一名称,因此可以对这四个不同的名称应用任何更昂贵的分析,这样会便宜得多。确实,您的问题并不清楚。 Levenshtein 距离是数字,而不是布尔值。按距离分组通常不起作用。 [a, b] 可能是“相似的”(即低于阈值的距离),[b, c] 也可能相似,而 [a, c] 则不是。代码应该创建哪些组?
猜你喜欢
  • 1970-01-01
  • 2018-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-20
  • 1970-01-01
  • 1970-01-01
  • 2014-09-14
相关资源
最近更新 更多