使用Stream从HashSet中排除极值

时间:2014-04-07 21:30:51

标签: java performance java-8 java-stream

我一直在尝试使用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();
}

3 个答案:

答案 0 :(得分: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 - 这里是the source code for the tests

答案 1 :(得分: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&#39;基准代码给了我以下结果。虽然我的机器整体速度较慢,但​​在单次通过中使用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

答案 2 :(得分: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中已经存在的类。