Java 8 Java8计算地图中对象列表的平均值

Java 8 Java8计算地图中对象列表的平均值,java-8,java-stream,Java 8,Java Stream,初始数据: public class Stats { int passesNumber; int tacklesNumber; public Stats(int passesNumber, int tacklesNumber) { this.passesNumber = passesNumber; this.tacklesNumber = tacklesNumber; } public int getPassesNumb

初始数据:

public class Stats {
    int passesNumber;
    int tacklesNumber;

    public Stats(int passesNumber, int tacklesNumber) {
        this.passesNumber = passesNumber;
        this.tacklesNumber = tacklesNumber;
    }

    public int getPassesNumber() {
        return passesNumber;
    }

    public void setPassesNumber(int passesNumber) {
        this.passesNumber = passesNumber;
    }

    public int getTacklesNumber() {
        return tacklesNumber;
    }

    public void setTacklesNumber(int tacklesNumber) {
        this.tacklesNumber = tacklesNumber;
    }
} 

Map<String, List<Stats>> statsByPosition = new HashMap<>();
statsByPosition.put("Defender", Arrays.asList(new Stats(10, 50), new Stats(15, 60), new Stats(12, 100)));
statsByPosition.put("Attacker", Arrays.asList(new Stats(80, 5), new Stats(90, 10)));

我不认为Java8中有任何新的东西能够真正帮助解决这个问题,至少不能有效地解决这个问题

如果仔细查看所有新的API,您会发现它们中的大多数都是为了提供更强大的原语来处理单个值及其序列,也就是说,处理
double
int
?扩展对象

例如,为了计算
double
上的序列平均值,JDK引入了一个新类-
DoubleSummaryStatistics
,它做了一件显而易见的事情-收集任意序列的
double
值的摘要。 实际上,我建议您自己也采用类似的方法:制作您自己的
statsummary
类,该类内容如下:

// assuming this is what your Stats class look like:
class Stats {
  public final double a ,b; //the two stats
  public Stats(double a, double b) {
    this.a = a; this.b = b;
  }
}

// summary will go along the lines of:
class StatsSummary implements Consumer<Stats> {
  DoubleSummaryStatistics a, b; // summary of stats collected so far
  StatsSummary() {
    a = new DoubleSummaryStatistics();
    b = new DoubleSummaryStatistics();
  }

  // this is how we collect it:
  @Override public void accept(Stats stat) {
    a.accept(stat.a); b.accept(stat.b);
  }
  public void combine(StatsSummary other) {
    a.combine(other.a); b.combine(other.b);
  }

  // now for actual methods that return stuff. I will implement only average and min
  // but rest of them are not hard
  public Stats average() {
    return new Stats(a.getAverage(), b.getAverage());
  }
  public Stats min() {
    return new Stats(a.getMin(), b.getMin());
  }
}

Java 8可能有一个更干净的解决方案,但它运行良好,并不太复杂:

Map<String, Stats> newMap = new HashMap<>();

statsByPosition.forEach((key, statsList) -> {
    newMap.put(key, new Stats(
         (int) statsList.stream().mapToInt(Stats::getPassesNumber).average().orElse(0),
         (int) statsList.stream().mapToInt(Stats::getTacklesNumber).average().orElse(0))
    );
});
Map newMap=newhashmap();
statsByPosition.forEach((键,statsList)->{
newMap.put(键,新属性(
(int)statsList.stream().mapToInt(Stats::GetPasseNumber).average().orElse(0),
(int)statsList.stream().mapToInt(Stats::getTacklesNumber.average().orElse(0))
);
});
函数式的
forEach
方法允许您迭代给定映射的每一对

您只需在地图中为平均值添加一个新条目。在这里,您可以使用给定地图中已有的
键。新值是一个新的
Stats
,其中直接计算构造函数的参数

只需获取旧映射的值,即
forEach
函数中的
statsList
,使用
mapToInt
将给定统计数据中的值映射到
Integer
值,并使用
average
函数

此函数返回一个
OptionalDouble
,它与
Optional
几乎相同。为了防止出现任何问题,可以使用它的
orElse()
方法并传递一个默认值(如
0
)。由于平均值是
double
,因此必须将值转换为
int


如前所述,可能会有一个更短的版本,使用
reduce

您还可以使用自定义收集器。让我们向
Stats
类添加以下方法:

 public Stats() {

 }

 public void accumulate(Stats stats) {
     passesNumber += stats.passesNumber;
     tacklesNumber += stats.tacklesNumber;
 }

 public Stats combine(Stats acc) {
     passesNumber += acc.passesNumber;
     tacklesNumber += acc.tacklesNumber;
     return this;
 }


 @Override
 public String toString() {
     return "Stats{" +
             "passesNumber=" + passesNumber +
             ", tacklesNumber=" + tacklesNumber +
             '}';
 }
现在我们可以使用
collect
方法中的
Stats

System.out.println(statsByPosition.entrySet().stream().collect(
        Collectors.toMap(
            entity -> entity.getKey(),
            entity -> {
                Stats entryStats = entity.getValue().stream().collect(
                        Collector.of(Stats::new, Stats::accumulate, Stats::combine)
                ); // get stats for each map key. 

                // get average
                entryStats.setPassesNumber(entryStats.getPassesNumber() / entity.getValue().size());
                // get average
                entryStats.setTacklesNumber(entryStats.getTacklesNumber() / entity.getValue().size());

            return entryStats;
        }
))); // {Attacker=Stats{passesNumber=85, tacklesNumber=7}, Defender=Stats{passesNumber=12, tacklesNumber=70}}

我不想发布这个,但它太大了,无法发表评论。如果java-9可用且StreamEx可用,您可以执行以下操作:

    public static Map<String, Stats> third(Map<String, List<Stats>> statsByPosition) {

    return statsByPosition.entrySet().stream()
            .collect(Collectors.groupingBy(e -> e.getKey(),
                    Collectors.flatMapping(e -> e.getValue().stream(),
                            MoreCollectors.pairing(
                                    Collectors.averagingDouble(Stats::getPassesNumber),
                                    Collectors.averagingDouble(Stats::getTacklesNumber),
                                    (a, b) -> new Stats(a, b)))));
} 
publicstaticmap第三(Map statsByPosition){
返回statsByPosition.entrySet().stream()
.collect(收集器.groupingBy(e->e.getKey()),
Collectors.flatMapping(e->e.getValue().stream(),
摩尔配对(
收集器.averagingDouble(Stats::GetPasseNumber),
收集器.averagingDouble(Stats::getTacklesNumber),
(a,b)->新统计数据(a,b‘‘‘‘‘));
} 

到目前为止您尝试了什么?我猜
Stats
有getter和setter作为它们的值吗?是的,有getter和setter,我忘了提到了thx,我已经编辑了我的问题如果你传递一个空的Stats集合,你的代码将crash@PascalSchneider后者会的,是的。我并不是真的确定它没有任何错误,这是一种替代方法。不过我会修好的。它可能会导致副作用-。请使用
Collectors.toMap
而不是从
forEach
loop@AntonBalaniuc,或cource is导致副作用-这是
forEach()
方法的全部目的,它们很少是纯的,纯forEach只对可变数据有用。另外,请解释在本例中使用
收集器.toMap
将给出什么(在本例中,它只是隐藏了实际发生的事情)。您是说在
流上使用
toMap
收集器吗?@M.Prokhorov,不建议使用
foreach
填充数据结构。它不是线程安全的,可能会产生不正确的结果。你可以在上面的链接中阅读更多内容。我们在这里可以使用的是
Collectors.toMap
statsByPosition.entrySet().stream().collector(Collectors.toMap(entry->entry.getKey(),entry->newstats((int)entry.getValue().stream().mapToInt(Stats::getPassesNumber).average().orElse(0),(int)entry.getValue().stream().mapToInt(Stats::getTacklesNumber.average().orElse(0))
@AntonBalaniuc,它不是线程安全的,但是除了
thread.currentThread()
,它也没有涉及线程,所以拥有一些非线程安全的东西是完全可以的。我仍然坚持我最初的判断,即利用溪流和收集器是一种过分的做法。我还认为,如果涉及到线程,那么我们应该根据我所看到的
statsummary
类的设计使用复合收集器,如下所示:
collect(collector.of(statsummary::new,statsummary::accept,statsummary::combine,statsummary::average))
System.out.println(statsByPosition.entrySet().stream().collect(
        Collectors.toMap(
            entity -> entity.getKey(),
            entity -> {
                Stats entryStats = entity.getValue().stream().collect(
                        Collector.of(Stats::new, Stats::accumulate, Stats::combine)
                ); // get stats for each map key. 

                // get average
                entryStats.setPassesNumber(entryStats.getPassesNumber() / entity.getValue().size());
                // get average
                entryStats.setTacklesNumber(entryStats.getTacklesNumber() / entity.getValue().size());

            return entryStats;
        }
))); // {Attacker=Stats{passesNumber=85, tacklesNumber=7}, Defender=Stats{passesNumber=12, tacklesNumber=70}}
    public static Map<String, Stats> third(Map<String, List<Stats>> statsByPosition) {

    return statsByPosition.entrySet().stream()
            .collect(Collectors.groupingBy(e -> e.getKey(),
                    Collectors.flatMapping(e -> e.getValue().stream(),
                            MoreCollectors.pairing(
                                    Collectors.averagingDouble(Stats::getPassesNumber),
                                    Collectors.averagingDouble(Stats::getTacklesNumber),
                                    (a, b) -> new Stats(a, b)))));
}