简体   繁体   English

使用lambda表达式来总结成员变量?

[英]Using lambda expressions for summing up member variables?

I have a class like followings. 我有一个类似于以下的课程。

public class Votes{
    String name;
    int likes;
    int dislikes;

    //constructors, getters and setters    
}

I am having a list like followings. 我有一个如下列表。

List<Votes> votesList;

Assume I am populating above list with some elements. 假设我在列表中填充了一些元素。 I want to declare a method which performs grouping and summing operation in that list. 我想声明一个在该列表中执行分组和求和操作的方法。

As an example, assume I am giving the following elements in a list as the input for that method. 例如,假设我在列表中提供以下元素作为该方法的input

votesList.add(new Votes("A", 10, 5));
votesList.add(new Votes("B", 15, 10));
votesList.add(new Votes("A", 20, 15));
votesList.add(new Votes("B", 10, 25));
votesList.add(new Votes("C", 10, 20));
votesList.add(new Votes("C", 0, 15));

That method should output a List<Votes> with the following elements. 该方法应输出带有以下元素的List<Votes>

("A", 30, 20),
("B", 25, 35),
("C", 10, 35)

Is there an easy way to do that using streams, lambda expressions in Java8? 在Java8中使用stream,lambda表达式有一种简单的方法吗? I know how it can be done using collectors if I only have one int memeber. 我知道如果我只有一个int memeber,如何使用collectors

Can someone please explain me how can I address this situation? 有人可以解释一下我该如何解决这种情况?

One approach is to use groupingBy , combined with reducing : 一种方法是使用groupingBy ,结合reducing

Collection<Optional<Votes>> res =
     votesList.stream().collect(groupingBy(v -> v.name, reducing((v1, v2) -> new Votes(v1.name, v1.likes + v2.likes, v1.dislikes + v2.dislikes)))).values();

This will give you a Collection<Optional<Votes>> , you can get rid of it by combining the reducing collector and the finisher function Optional::get using the collectingAndThen collector but that will starts to look quite hard to read. 这会给你一个Collection<Optional<Votes>> ,你可以通过将摆脱它reducing收集和整理功能Optional::get使用collectingAndThen收集,但将开始显得相当难以阅读。

So another alternative could be to use toMap , and merge two votes whenever they have the same name: 所以另一种选择可能是使用toMap ,并在它们具有相同名称时合并两个投票:

Collection<Votes> res =
    votesList.stream().collect(toMap(v -> v.name,
                                     v -> v,
                                     (v1, v2) -> new Votes(v1.name, v1.likes + v2.likes, v1.dislikes + v2.dislikes))).values();

From there you can use the ArrayList copy-constructor (either by using collectingAndThen with m -> new ArrayList<>(m.values()) , or by putting the previous expression in the parameter list of this copy constructor). 从那里,你可以使用ArrayList拷贝构造函数(或者通过使用collectingAndThenm -> new ArrayList<>(m.values())或通过将在这个拷贝构造函数的参数列表中的前一个表达式)。

Finally, I came across this as the easiest way. 最后,我发现这是最简单的方法。 :)) :))

Map<String, List<Votes>> grouped = voteCountList.stream().collect(Collectors.groupingBy(r->r.getName()));

List<Votes> collectedList = new ArrayList<>();

grouped.forEach((groupName, votes) -> collectedList.add(new Votes(groupName,
                votes.stream().collect(Collectors.summingInt(r->r.getLikes())),
                votes.stream().collect(Collectors.summingInt(r->r.getDislikes())))));

This should get you started, it's not exactly what you want since it does not create the final list. 这应该让你开始,它不是你想要的,因为它不会创建最终列表。

Map<String, Votes> collected = votesList.stream().collect(Collectors.groupingBy(Votes::getName,
        collectingAndThen(reducing(
                (originalVotes, newVotes) -> new Votes(originalVotes.getName(), originalVotes.getLikes() + newVotes.getLikes(), originalVotes.getDislikes() + newVotes.getDislikes()))
                , Optional::get)));

collected.forEach((key, value) -> {
    System.out.println(key + "," + value.getLikes() + "," + value.getDislikes());
});

Here's a solution using a custom Collector. 这是使用自定义收集器的解决方案。

Test code: 测试代码:

final List<Votes> votesList = new ArrayList<>();
votesList.add(new Votes("A", 10, 5));
votesList.add(new Votes("B", 15, 10));
votesList.add(new Votes("A", 20, 15));
votesList.add(new Votes("B", 10, 25));
votesList.add(new Votes("C", 10, 20));
votesList.add(new Votes("C", 0, 15));

final List<Votes> totals = votesList.stream().collect(new VoteCollector());
totals.forEach(votes -> {
    System.out.println(votes.getName() + "  " + votes.getLikes() + "  " + votes.getDislikes());
});

VoteCollector class: VoteCollector类:

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class VoteCollector implements Collector<Votes, Map<String, Votes>, List<Votes>> {

    @Override
    public Supplier<Map<String, Votes>> supplier() {
        return HashMap::new;
    }

    @Override
    public BiConsumer<Map<String, Votes>, Votes> accumulator() {
        return (map, votes) -> {
            final Votes mapVotes = map.get(votes.getName());
            if (mapVotes == null) {
                map.put(votes.getName(), new Votes(votes.getName(), votes.getLikes(), votes.getDislikes()));
            } else {
                mapVotes.setLikes(mapVotes.getLikes() + votes.getLikes());
                mapVotes.setDislikes(mapVotes.getDislikes() + votes.getDislikes());
            }
        };
    }

    @Override
    public BinaryOperator<Map<String, Votes>> combiner() {
        return (map1, map2) -> {
            for (final Entry<String, Votes> map2Entry : map2.entrySet()) {
                final Votes map1Votes = map1.get(map2Entry.getKey());
                if (map1Votes == null) {
                    map1.put(map2Entry.getKey(), map2Entry.getValue());
                }
                else {
                    map1Votes.setLikes(map1Votes.getLikes() + map2Entry.getValue().getLikes());
                    map1Votes.setDislikes(map1Votes.getDislikes() + map2Entry.getValue().getDislikes());
                }
            }

            return map1;
        };
    }

    @Override
    public Function<Map<String, Votes>, List<Votes>> finisher() {
        return map -> map.values().stream().collect(Collectors.toList());
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
    }   
}

The output is 输出是

A  30  20
B  25  35
C  10  35

Here is one of many good sources for learning to write Collectors: 以下是学习编写收藏家的众多好来源之一:

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

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