4

我一直在尝试使用 Java 8 流,这是删除最小和最大分数的最佳方法。

private final Set<MatchScore> scores = new HashSet<>(10);

. . .

public double OPR() {
    return scores.stream()
            .mapToDouble(MatchScore::getScore)
            .filter((num) -> { //Exclude min and max score
                return num != scores.stream()
                                    .mapToDouble(MatchScore::getScore)
                                    .max().getAsDouble() 
                        && 
                       num != scores.stream()
                                    .mapToDouble(MatchScore::getScore)
                                    .min().getAsDouble();
            })
            .average().getAsDouble();
}
4

3 回答 3

8

一个更简单的方法是:

return scores.stream()
        .mapToDouble(MatchScore::getScore)
        .sorted()
        .skip(1)
        .limit(scores.size() - 2)
        .average().getAsDouble();

注意:这是有效的,因为集合中的元素是唯一的 - 对于列表,可能有多个元素等于最小或最大分数。


性能方面*,在一小组 10 个元素上,跳过/限制明显更快(平均列显示样本调用所花费的平均时间,以纳秒为单位):

Benchmark                      Mode   Samples         Mean   Mean error    Units
c.a.p.SO22923505.maxMin        avgt         5     6996.190      284.287    ns/op
c.a.p.SO22923505.skipLimit     avgt         5      479.935        4.547    ns/op

*使用 jmh - 这是测试的源代码

于 2014-04-07T21:35:43.657 回答
4

可以使用DoubleSummaryStatistics单次遍历数据来收集所需信息,然后减去最小值和最大值:

@GenerateMicroBenchmark
public double summaryStats() {
    DoubleSummaryStatistics stats =
        scores.stream()
              .collect(Collectors.summarizingDouble(Double::doubleValue));

    if (stats.getCount() == 0L) {
        return 0.0; // or something
    } else {
        return (stats.getSum() - stats.getMin() - stats.getMax()) / (stats.getCount() - 2);
    }
}

将此代码添加到 assylias 的基准代码中会给我以下结果。虽然我的机器整体速度较慢,但​​使用DoubleSummaryStatistics单次通过的相对性能更快。

Benchmark                         Mode   Samples         Mean   Mean error    Units
c.a.p.SO22923505.maxMin           avgt         5     9629.166     1051.585    ns/op
c.a.p.SO22923505.skipLimit        avgt         5      682.221       80.504    ns/op
c.a.p.SO22923505.summaryStats     avgt         5      412.740       85.372    ns/op
于 2014-04-08T02:01:41.040 回答
2

我认为这将完成这项工作,而无需多次通过流或对其进行排序:

private static class ScoreData {
    public double min, max, sum;
    public int count;
    public ScoreData() {
        min = Double.POSITIVE_INFINITY;
        max = Double.NEGATIVE_INFINITY;
        sum = 0;
        count = 0;
    }
    public void add(double d) {
        if (d < min)
            min = d;
        if (d > max)
            max = d;
        sum += d;
        count++;
    }
    public void combine(ScoreData m) {
        if (m.min < min)
            min = m.min;
        if (m.max > max)
            max = m.max;
        sum += m.sum;
        count += m.count;
    }
}

private static ScoreData getScoreData(DoubleStream ds) {
    return ds.collect(ScoreData::new, ScoreData::add, ScoreData::combine);
}

这适用于任何DoubleStream. 现在你可以得到不包括极值的平均值

ScoreData sd = getScoreData(scores.stream().mapToDouble(MatchScore::getScore));
double answer = (sd.sum - sd.min - sd.max) / (sd.count - 2);

假设sd.count > 2.

编辑:看起来我只是重新发明了轮子!Stuart 使用 JDK 中已经存在的类有一个更好的解决方案。

于 2014-04-08T00:51:22.070 回答