【问题标题】:Java 8 stream - merge collections of objects sharing the same Name and who follow each otherJava 8 流 - 合并具有相同名称且彼此跟随的对象集合
【发布时间】:2019-12-22 21:25:20
【问题描述】:

假设一个类Person:

public class Person {
    private String name;
    private int amount;
}

并假设有一个 Person 集合:

persons = [{"Joe", 5}, {"Joe", 8}, {"Joe", 10}, {"Jack", 3}, {"Jack",6}, {"Joe" 4}, 
           {"Joe", 7}, {"Jack", 12}, {"Jack", 15}, {"Luke", 10}, {"Luke", 12}]

我想要的是获得具有合并元素的人员列表,这些人员具有相同的名称并且彼此跟随并总和(使用 java 8 Stream);像这样的列表:

Perons = [{"Joe", 23}, {"Jack", 9}, {"Joe", 11}, {"Jack", 27}, {"Luke", 22}]

【问题讨论】:

  • 您现在可以考虑接受答案或发表评论以获取详细信息;)以奖励那些为您花费时间的人;)

标签: java collections java-8 java-stream


【解决方案1】:

您应该为此创建自己的自定义Collector

class AdjacentNames {

    List<Person> result = new ArrayList<>();
    Person last = null;

    void accumulate(Person person) {
        if (last != null && last.getName().equals(person.getName())) {
            last.setAmount(last.getAmount() + person.getAmount())
        } else {
            last = new Person(person.getName(), person.getAmount());  // Clone
            result.add(last);
        }
    }

    AdjacentNames merge(AdjacentNames other) {
        List<Person> other_list = other.finisher();

        if (other_list.size() > 0) {
            accumulate(other_list.remove(0));
            result.addAll(other_list);
            last = result.get(result.size()-1);
        }

        return this;
    }

    List<Person> finisher() {
        return result;
    }

    public static Collector<Person, AdjacentNames, List<Person>> collector() {
        return Collector.of(AdjacentNames::new,
                            AdjacentNames::accumulate,
                            AdjacentNames::merge,
                            AdjacentNames::finisher);
    }
}

并像这样使用:

List<Person> result = persons.stream().collect(AdjacentNames.collector());

【讨论】:

  • 我一直希望人们有权进行自己的定制Collector
【解决方案2】:

您正在寻找的解决方案使用流方法有点复杂,我建议使用基本的for 循环并使用Map,每个序列都有重复的逻辑

List<Person> persons = List.of(new Person("Jeo", 5), new Person("Jeo", 5), new Person("Jack", 8),
            new Person("Luke", 5), new Person("Jeo", 5), new Person("Jeo", 5));

    List<Person> result = new ArrayList<>();
    Map<String, Integer> check = new HashMap<String, Integer>();
    for (Person per : persons) {

        if (check.containsKey(per.getName())) {
            check.compute(per.getName(), (k, v) -> v + per.getAmount());

        } else if (check.size() == 0) {
            check.put(per.getName(), per.getAmount());

        }else {
            result.add(check.entrySet().stream().map(e->new Person(e.getKey(), e.getValue())).findFirst().get());
            check.clear();
            check.put(per.getName(), per.getAmount());
        }
    }
    result.add(check.entrySet().stream().map(e->new Person(e.getKey(), e.getValue())).findFirst().get());

我还建议使用stream 方法,通过使用Collectors.groupingBysummingInt 根据名称获取每个Person 总和,然后通过维护顺序将它们保存到LinkedHashMap,然后转换每个回到Person 对象。

person.stream()
       .collect(Collectors.groupingBy(Person::getName, LinkedHashMap::new,Collectors.summingInt(Person::getAmount)))
       .entrySet()
       .stream()
       .map(entry->new Person(entry.getKey(),entry.getValue()))
       .collect(Collectors.toList());

【讨论】:

  • 很好,但它没有回答问题的“互相关注”部分。 (Collectore 中有一个错字)。
【解决方案3】:

如果您不想按姓名对 all 人进行分组,并将那些一起放在原始列表中的人,我认为流解决方案不是一个合适的解决方案,与for 循环似乎更好更容易:

List<Person> output = new ArrayList<>();
Person toAdd = null;
for (Person current : persons) {
    if (toAdd == null) {
        toAdd = current;
    } else if (toAdd.getName().equals(current.getName())) {
        toAdd = new Person(toAdd.getName(), toAdd.getAmount() + current.getAmount());
    } else {
        output.add(toAdd);
        toAdd = current;
    }
}
output.add(toAdd);

【讨论】:

  • 通过循环获得它,我正在寻找流。无论如何,谢谢。
【解决方案4】:

你可以这样做:

List<Person> result = persons.stream()
        .reduce(new ArrayList<>(), (list, currentPerson) -> {
            // get the last person of the list being computed
            Person lastListPerson = list.isEmpty() ? null : list.get(list.size() - 1);
            if (lastListPerson != null && currentPerson.name.equals(lastListPerson.name)) {
                // if the previous persone had the same name, just add the amount
                lastListPerson.amount += currentPerson.amount;
            } else {
                // if the previous person had a different name, clone the person and add it in the result list
                list.add(new Person(currentPerson));
            }
            return list;
        }, (a, b) -> a);

【讨论】:

  • 注意:这将改变输入数据中每组具有相同姓名的连续人员中的第一个Person。您可能应该克隆此人的数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-20
  • 2019-08-02
相关资源
最近更新 更多