计算数组列表的平均值?

72

我正在尝试使用以下代码计算用户输入的一组值的平均值,并在jTextArea中显示它,但它无法正常工作。比如,用户输入了7、4和5,程序显示平均值为1,而应该显示5.3。

  ArrayList <Integer> marks = new ArrayList();
  Collections.addAll(marks, (Integer.parseInt(markInput.getText())));

  private void analyzeButtonActionPerformed(java.awt.event.ActionEvent evt) {
      analyzeTextArea.setText("Class average:" + calculateAverage(marks));
  }

  private int calculateAverage(List <Integer> marks) {
      int sum = 0;
      for (int i=0; i< marks.size(); i++) {
            sum += i;
      }
      return sum / marks.size();
  }

代码哪里出了问题?


9
你不是在对分数求和,而是在对数组索引 i 求和。 - Tony Ennis
11个回答

105

使用Java 8就会稍微更容易一些点击此处查看示例

OptionalDouble average = marks
            .stream()
            .mapToDouble(a -> a)
            .average();

因此,您的平均值为average.getAsDouble()

return average.isPresent() ? average.getAsDouble() : 0; 

28
average.isPresent() ? average.getAsDouble() : defaultValue 可以进一步简化为 optional.orElse(defaultValue) - Oleg Estekhin
@OlegEstekhin - 我们应该使用mapToInt而不是mapToDouble吗?实际上需要映射吗? - MasterJoe

84

当你有增强型for循环时,为什么要使用一个带索引的笨重的for循环呢?

private double calculateAverage(List <Integer> marks) {
  Integer sum = 0;
  if(!marks.isEmpty()) {
    for (Integer mark : marks) {
        sum += mark;
    }
    return sum.doubleValue() / marks.size();
  }
  return sum;
}

更新: 正如其他一些人已经指出的那样,使用Java 8及更高版本的Streams会变得更加简单:

private double calculateAverage(List <Integer> marks) {
    return marks.stream()
                .mapToDouble(d -> d)
                .average()
                .orElse(0.0)
}

5
我会检查 marks.size() 是否为0,因为如果列表为空,这将导致除以零的错误。 - Axarydax
6
我喜欢Java,但在进行这种操作时,你会想念C#的list.Average()函数。 :p - John Humphreys
只是一个快速的提示,使用笨拙的循环的原因之一是它比所谓的文明循环要快得多。对于ArrayLists,for(int i = 0 .... )循环比使用迭代器或for(:)方法快约2倍,所以即使它更漂亮,它也慢得多!让它变得更快的一个提示是将长度缓存如下:for (int i = 0, len = list.size(); i <len ; i++)。len=list.size()只会在循环开始时执行一次,并且每次都会测试缓存的len值。 - Leo
1
实际上,在正确的测试中,增强型for循环和传统的for循环执行速度相同。有趣的是,“增强型for循环”和传统的for循环最终的执行速度与while(i-->0)循环相同,尽管每次循环都需要额外的评估/调用。这只是在se1.7上运行,使用填充了具有随机int成员变量的对象的arraylist,并将其计算为总和,以使vm进行实际工作。增强型循环的速度与使用迭代器手动迭代的速度相同。如果您正在使用arraylist,则没有必要使用增强型循环,因为基于索引的获取更快且不会导致垃圾回收问题。 - Lassi Kinnunen
为什么需要使用mapToDouble?可以使用Arrays.stream(marks).average().orElse(0.0);。https://www.baeldung.com/java-array-sum-average - Shanika Ediriweera
显示剩余2条评论

43

Java8开始,您可以按以下方式获取列表中值的平均值:

    List<Integer> intList = Arrays.asList(1,2,2,3,1,5);

    Double average = intList.stream().mapToInt(val -> val).average().orElse(0.0);

这种方法的优点在于没有任何可动部件。通过更改map方法的调用,它可以轻松地适应List中其他类型的对象。

例如,对于Doubles:

    List<Double> dblList = Arrays.asList(1.1,2.1,2.2,3.1,1.5,5.3);
    Double average = dblList.stream().mapToDouble(val -> val).average().orElse(0.0);

NB. 使用 mapToDouble 是因为它返回一个 DoubleStream,其中包含一个 average 方法,而使用 map 则不会。

或者使用 BigDecimals:

@Test
public void bigDecimalListAveragedCorrectly() {
    List<BigDecimal> bdList = Arrays.asList(valueOf(1.1),valueOf(2.1),valueOf(2.2),valueOf(3.1),valueOf(1.5),valueOf(5.3));
    Double average = bdList.stream().mapToDouble(BigDecimal::doubleValue).average().orElse(0.0);
    assertEquals(2.55, average, 0.000001);
}

使用 orElse(0.0) 可以解决从 average 返回的 Optional 对象不存在的问题。


哎呀 - 没有注意到上面的Java8答案,它和我给出的答案是一样的。 - robjwilkins
1
在第二个例子中,为什么需要使用mapToDouble函数,当dblList已经包含了Doubles? - simpleuser
1
@simpleuser - 因为 mapToDouble 方法返回 DoubleStream,该类型具有 average 方法。 - robjwilkins
我认为第三种方法(使用mapToDouble(BigDecimal::doubleValue).average())不起作用。你应该使用BigDecimal::valueOf - Hearen
@Hearen - 不确定为什么你认为它不起作用?我已经更新了这个例子,加入了一个简单的junit示例,你可以在IDE中运行它来验证代码是否有效。如果你还是不理解我的回答,也许你可以发一个新问题。谢谢。 - robjwilkins
显示剩余5条评论

18

使用双精度浮点数进行加法运算,否则你会执行整数除法而得不到小数部分:

private double calculateAverage(List <Integer> marks) {
    if (marks == null || marks.isEmpty()) {
        return 0;
    }

    double sum = 0;
    for (Integer mark : marks) {
        sum += mark;
    }

    return sum / marks.size();
}

或者使用Java 8的流 API:

    return marks.stream().mapToInt(i -> i).average().orElse(0);

在返回之前将其转换为double会更加干净,这样当marks是一个非常大的列表时,就不会出现任何浮点错误。 - n00begon
关于Java 8 API,需要导入哪些内容? - eactor
在上面的示例中,不需要任何额外的导入。 - Emmanuel Bourg

11
sum += i;
你正在添加索引,而应该在 ArrayList 中添加实际的项目:
sum += marks.get(i);

此外,为了确保返回值不被截断,强制一个操作数转换为double并将方法签名更改为double:

return (double)sum / marks.size();

3
由于他正在使用一个列表,因此您应该使用sum += marks.get(i); - jahroy

7
使用Guava,它得到了语法上的简化:
Stats.meanOf(numericList);

如果你在项目中能够使用番石榴(Guava),这比丑陋而冗长的.stream().mapToDouble(v -> v).average()垃圾好多了!谢谢你。 - higuaro

2

当数字不大时,一切似乎都很合适。但如果它变得很大,需要极度小心以确保正确性。

以双精度浮点数为例:

如果它不是很大,就像其他人提到的那样,你可以简单地尝试这个方法:

doubles.stream().mapToDouble(d -> d).average().orElse(0.0);

然而,如果它超出了你的控制并且非常大,你必须使用 BigDecimal 如下(使用 BigDecimal 的旧答案中的方法实际上是错误的)。

doubles.stream().map(BigDecimal::valueOf).reduce(BigDecimal.ZERO, BigDecimal::add)
       .divide(BigDecimal.valueOf(doubles.size())).doubleValue();

以下是我进行的测试,以证明我的观点:

    @Test
    public void testAvgDouble() {
        assertEquals(5.0, getAvgBasic(Stream.of(2.0, 4.0, 6.0, 8.0)), 1E-5);
        List<Double> doubleList = new ArrayList<>(Arrays.asList(Math.pow(10, 308), Math.pow(10, 308), Math.pow(10, 308), Math.pow(10, 308)));
        // Double.MAX_VALUE = 1.7976931348623157e+308
        BigDecimal doubleSum = BigDecimal.ZERO;
        for (Double d : doubleList) {
            doubleSum =  doubleSum.add(new BigDecimal(d.toString()));
        }
        out.println(doubleSum.divide(valueOf(doubleList.size())).doubleValue());
        out.println(getAvgUsingRealBigDecimal(doubleList.stream()));
        out.println(getAvgBasic(doubleList.stream()));
        out.println(getAvgUsingFakeBigDecimal(doubleList.stream()));
    }

    private double getAvgBasic(Stream<Double> doubleStream) {
        return doubleStream.mapToDouble(d -> d).average().orElse(0.0);
    }

    private double getAvgUsingFakeBigDecimal(Stream<Double> doubleStream) {
        return doubleStream.map(BigDecimal::valueOf)
                .collect(Collectors.averagingDouble(BigDecimal::doubleValue));
    }

    private double getAvgUsingRealBigDecimal(Stream<Double> doubleStream) {
        List<Double> doubles = doubleStream.collect(Collectors.toList());
        return doubles.stream().map(BigDecimal::valueOf).reduce(BigDecimal.ZERO, BigDecimal::add)
                .divide(valueOf(doubles.size()), BigDecimal.ROUND_DOWN).doubleValue();
    }

关于 IntegerLong,同样你可以使用 BigInteger

2
List.stream().mapToDouble(a->a).average()

尝试使用代码格式并为您的答案提供一些上下文。请参考其他答案作为示例。 - hidralisk

1

计算 List<Integer> 平均值的正确和快速方法:

private double calculateAverage(List<Integer> marks) {
    long sum = 0;
    for (Integer mark : marks) {
        sum += mark;
    }
    return marks.isEmpty()? 0: 1.0*sum/marks.size();
}

这个解决方案考虑了以下内容:
  • 处理溢出
  • 不像Java8 Stream那样分配内存
  • 不使用缓慢的BigDecimal
对于List,它可以正常工作,因为任何列表都包含少于2的31次方个整数,并且可以使用long作为累加器。
注:实际上,foreach会分配内存 - 在关键部分应该使用旧式的for()循环。

1
你可以使用标准的循环结构或迭代器/列表迭代器来实现相同的功能:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
double sum = 0;
Iterator<Integer> iter1 = list.iterator();
while (iter1.hasNext()) {
    sum += iter1.next();
}
double average = sum / list.size();
System.out.println("Average = " + average);

如果使用 Java 8,您可以使用 Stream 或 IntStream 操作来完成相同的操作:
OptionalDouble avg = list.stream().mapToInt(Integer::intValue).average();
System.out.println("Average = " + avg.getAsDouble());

参考资料:计算数组列表的平均值


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接