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

Ali*_*rov 5 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 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)));
Run Code Online (Sandbox Code Playgroud)

我需要按位置计算统计数据的平均值。因此,结果应该是具有相同键的映射,但是值应聚合到单个Stats对象(列表应减少为单个Stats对象)

{
  "Defender" => Stats((10 + 15 + 12) / 3, (50 + 60 + 100) / 3),
  "Attacker" => Stats((80 + 90) / 2, (5 + 10) / 2)
} 
Run Code Online (Sandbox Code Playgroud)

M. *_*rov 3

我认为 Java8 中没有任何新内容可以真正帮助解决这个问题,至少效率不高。

如果您仔细查看所有新 API,您会发现它们中的大多数旨在提供更强大的原语来处理单个值及其序列 - 即 、 、doubleint序列? extends Object

例如,为了计算 上的序列的平均值double,JDK 引入了一个新类 -DoubleSummaryStatistics它做了一件显而易见的事情 - 收集任意值序列的摘要double。我实际上建议你自己采用类似的方法:创建你自己的StatsSummary类,看起来像这样:

// 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());
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,上述实现实际上允许您在使用Streams 等时表达正确的意图:通过构建严格的 API 并使用 JDK 中可用的类作为构建块,总体上会减少错误。

但是,如果您只想在某个地方计算一次平均值并且不需要其他任何东西,则对此类进行编码有点矫枉过正,这里有一个快速而肮脏的解决方案:

Map<String, Stats> computeAverage(Map<String, List<Stats>> statsByPosition) {
  Map<String, Stats> averaged = new HashMap<>();
  statsByPosition.forEach((position, statsList) -> {
    averaged.put(position, averageStats(statsList));
  });
  return averaged;
}

Stats averageStats(Collection<Stats> stats) {
  double a, b;
  int len = stats.size();
  for(Stats stat : stats) {
    a += stat.a;
    b += stat.b;
  }
  return len == 0d? new Stats(0,0) : new Stats(a/len, b/len);
}
Run Code Online (Sandbox Code Playgroud)