带有 3 个参数的 Java.reduce 函数究竟是如何工作的?

Posted

技术标签:

【中文标题】带有 3 个参数的 Java.reduce 函数究竟是如何工作的?【英文标题】:How exactly does the Java.reduce function with 3 parameters work? 【发布时间】:2020-06-17 21:48:00 【问题描述】:

我目前正在学习java.reduce(),最近在阅读一些材料和浏览视频时遇到了一些事情。我知道有 3 种方法可以使用它,但是,我在使 3 参数之一工作时遇到问题。我希望有人能更好地解释它发生了什么,以及为什么我没有得到我例外的结果(一组 MagicNumber 对象的平均值)。这是我到目前为止的一个例子:

一个对象,我们称之为 MagicNumber.java

public class MagicNumber
    Random randomValueGenerator = new Random();
    int number;
    public MagicNumber()
        this.number = randomValueGenerator.nextInt();
    

包含幻数的随机流

public static Stream<MagicNumber> getNStream()
    List<MagicNumber> magic = new ArrayList<MagicNumber>();
    for(int i = 0; i < 100; i++)
        magic.add(new MagicNumber());
    
    return magic.stream();

返回平均幻数的函数

// This only returns the sum of the magic numbers and not the average
public static double average()
    Stream<MagicNumbers> magicNumberStream = getNStream();
    return magicNumberStream.reduce(
        0.0, // initial value
        (a,b) -> a + (double)b.number,
        (a,b) -> (a + b)/100
    )

正如评论所暗示的,上面只返回所有幻数的总和。虽然我期待一些东西。所以,我不相信我完全理解 reduce 函数中第三个参数的用途。此外,当我使用 2 个参数编写如下内容时: 返回平均幻数的函数

// This throws an error due to the return type
public static double average()
    Stream<MagicNumbers> magicNumberStream = getNStream();
    return magicNumberStream.reduce(
        0.0, // initial value
        (a,b) -> (double)(a.number + b.number)/100
    )

我收到错误 Bad return type in lambda expression: double cannot be converted to MagicNumber。有人可以解释(A)为什么这两个average 函数没有按预期工作,以及(B)如何修改它们以获得我正在寻找的结果?非常感谢您的帮助!

【问题讨论】:

你从哪里得到这个例子的? 我编的... 【参考方案1】:

您误解了reduce() 的第三个参数。该参数是组合函数,它将两个中间结果组合成一个结果。

现在,您无法计算平均值的原因是,为了计算平均值,您必须将 Stream 减少为 2 个值 - 一个是元素的数量,另一个是它们的总和。一旦你得到这两个值,你就可以将它们分开。

为了让reduce() 产生两个值,您可以使用一些包含两个值的类,例如SimpleEntry

public static double average()
    Stream<MagicNumber> magicNumberStream = getNStream();
    Map.Entry<Integer,Double> sums = 
        magicNumberStream.reduce(
            new SimpleEntry<Integer,Double>(0,0.0), // initial value (number of elements, sum)
            (a,b) -> new SimpleEntry<>(a.getKey()+1,a.getValue()+b.number),
            (a,b) -> new SimpleEntry<>(a.getKey()+b.getKey(),a.getValue()+b.getValue()));
    return sums.getValue()/sums.getKey();

【讨论】:

Eran,我注意到在我的回答中,即使我删除了 parallel(),我仍然得到正确的平均值。你能告诉我为什么会这样吗?如果您愿意,我可以将其作为单独的问题发布。谢谢。这里 - ***.com/a/60554907/6648326 @MasterJoe2 如果您遵循 Stream 方法的约定,则流应该产生相同的输出,无论它是并行的还是顺序的。因此,删除 parallel() 应该没有什么不同。 Eran,我问了一个相关的问题。随时回答 - ***.com/questions/60555563/… 谢谢。【参考方案2】:

免责声明 - 我是 lambdas 和一般流的初学者。

我认为Eran's answer 更好。但是,我也会添加我的答案。如果需要,这里是与此问题相关的 Streams 上的 short tutorial。

我使用了一个选定整数的列表,而不是你的“幻数”。这是相同的,因为幻数在您的示例中只是整数。所以我们实际上只是在计算整数的平均值。此外,使用我们可以决定其数量的列表将很容易测试和学习。找出 lambda 部分后,您可以将 List&lt;Integer&gt; 替换为 List&lt;MagicNumber&gt; 并相应地更新代码。

虽然不需要,但我将使用并行流来回答您的问题。代码的解释在cmets中。

import java.util.Arrays;
import java.util.List;

public class Temp 
    public static void main(String [] args)
        //For example, consider a list of a *series* of numbers in increasing order.
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        int n = numbers.get(numbers.size()-1);//n = 6 is the number of numbers in list.

        double expectedSum = (n * (n + 1))/2;//By mathematical formula for increasing series.

        Double sum = numbers
                //Take a stream of numbers - it could be integers, "magic numbers" or any other number.
                .stream()
                //Split the stream into mini streams and calculate sum of each of mini stream.
                .parallel()
                //Reduce all the numbers in a stream to their sum.
                .reduce(
                        //start with a partial sum of 0.
                0.0,
                //For a stream, calculate the sum of all the numbers.
                (partialSum, nextNumber) -> partialSum + (double) nextNumber,
                //Add the sums of each mini stream.
                (onePartialSum, anotherPartialSum) -> (onePartialSum + anotherPartialSum)
        );

        System.out.println("Sum : expected value = " + expectedSum + ", actual value = " + sum);

        double expectedAverage = expectedSum/numbers.size();
        double average = sum/numbers.size();

        System.out.println("Average : expected value = " + expectedAverage + ", actual value = " + average);

    

输出:

Sum : expected value = 21.0, actual value = 21.0
Average : expected value = 3.5, actual value = 3.5

【讨论】:

实际上,我认为您的问题本身也可以回答我的问题。我对 reduce 方法中的最后一个参数感到困惑。【参考方案3】:

以下是我想基于以下问题讨论的一些要点:具有 3 个参数的 reduce 方法如何工作? 1.如果我们不使用并行流方法,那么简单地减少方法使用标识和累加器, 下面是同样的演示,

    int length=se.reduce(0,//identity
    //below is the Accumulator that is Bifunctional parameter
    (i,s)->
        System.out.println("Initial Value= "+i+" with Stream Object="+s+" Addition is ="+(i+s.length()));//extra work to understand the data        
        return i+s.length();,
    //below is the BinaryOperator method which never gets executed in single pipe Stream**strong text**,    
    (a,b)->
            System.out.println("First Value that passed to combiner = "+a+" Second value ="+b);//never gets called in single stream
        return a+b;);
    System.out.println(length); 

输出

Initial Value= 0 with Stream Object=I Addition would be =1
Initial Value= 1 with Stream Object=am Addition would be =3
Initial Value= 3 with Stream Object=The Addition would be =6
Initial Value= 6 with Stream Object=King Addition would be =10
Initial Value= 10 with Stream Object=Of Addition would be =12
Initial Value= 12 with Stream Object=Java Addition would be =16
16

在我们应用并行流的第二种情况下,Combiner 将被执行,

public static int z=0;//need to put outside of public static void main(String[] args);

List<String> ll=List.of("I","am","very","Brave");
    ll.parallelStream().reduce(0,(i,s)->
        System.out.println("Initial Value= "+i+" with Stream Object="+s+" With Stream Number= "+z++);
        return i+s.length();,(a,b)->
            System.out.println("First Value that passed to combiner = "+a+" Second value ="+b);
        return a+b;);
System.out.println(Length);

运行后,我们得到以下输出,

Initial Value= 0 with Stream Object=very With Stream Number= 0
Initial Value= 0 with Stream Object=am With Stream Number= 1
Initial Value= 0 with Stream Object=I With Stream Number= 2
Initial Value= 0 with Stream Object=Brave With Stream Number= 3
First Value that passed to combiner = 1 Second value =2
First Value that passed to combiner = 4 Second value =5
First Value that passed to combiner = 3 Second value =9
12

这清楚地表明身份每次只是传递0值,因此每个流都不同,需要组合器来组合结果, 谢谢!

【讨论】:

在提供答案时避免使用诸如“我想根据问题讨论的一些观点”之类的短语。讨论不应成为答案的一部分;改用 cmets ot chat。

以上是关于带有 3 个参数的 Java.reduce 函数究竟是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

opencv库中houghcircle函数中的dp参数究竟是如何工作的?

js中函数传递参数,究竟是值传递还是引用传递?

带有可选参数的 PHP 函数

HttpGetAttribute不包含带有1个参数的构造函数

如何使用带有第二个参数设置 0xFACE 的 ioctl 来调用特定函数?

五分钟学Java:可变参数究竟是怎么一回事?