【问题标题】:Does reduction on an ordered stream reduce in order?有序流的减少是否按顺序减少?
【发布时间】:2018-09-06 07:06:41
【问题描述】:

我有一个List,由 A、B、C 组成。

C reduce A reduce B != A reduce B reduce C(但是,A reduce (B reduce C) 是可以的)。

换句话说,我的归约操作是关联的,但不是可交换的。

java 是否对有序的顺序流(例如列表中的默认流)强制执行减少总是根据遇到的顺序发生?也就是说,java会重新排序归约(使得B归约A而不是A归约B)?

(希望这足够清楚)。

编辑添加一个小演示,也许有助于澄清

Stream.of(" cats ", " eat ", " bats ")
  .reduce("", (a, b) -> a + b); // cats eat bats

有了以上内容,输出可能是“bats cat eat”还是“eat bats cats”?这在规范中的某个地方得到保证吗?

【问题讨论】:

  • @shmosel 好的,但这是列表的缩减,而不是特定列表元素的缩减!!
  • @Andremoniy 我认为它的意思是op.apply(op.apply(a, b), c),其中opBinaryOperator
  • @DD 你设法让事情变得更加混乱。
  • @shmosel reduce("", (a, b) -> a + " " + b) 将违反有关身份值的合同,因为f(id, x) == x 不成立。因此,在将其与并行流一起使用时,您确实会得到不需要的虚假空间。使用String::concat 很好,这不应该让人感到意外,因为.reduce("", String::concat) 可以作为有效(但低效)减少right in the documentation 的示例。

标签: java java-8 java-stream


【解决方案1】:

根据规范,它尊重元素的顺序。

证明很简单。 specification 声称归约函数必须是 关联

但是,关联性如果不保留顺序,它本身就没有任何意义。根据关联属性的mathematical definition

在包含在一行中出现两次或多次的表达式中 相同的关联运算符,操作的顺序 执行无关紧要只要操作数的顺序是 没有改变

换句话说,关联属性并不意味着:

(a + b) + c = (a + c) + b

它只允许对应用操作的顺序进行任意排列。

【讨论】:

  • 规范声称归约函数是关联的。 你的意思是它要求它是关联的。
  • @shmosel,嗯,是的。可以编写一个非关联函数,例如使用 pow() 函数。所以我编辑了我的答案
  • @shmosel 但是,它自己的函数无法管理它应用的操作数的顺序。所以这是它自己的reduce实现的一个交易
  • 这就是我的评论Can this not be because BinaryOperator is associative?的意思
  • @Andremoniy 1+,因为您实际上得到了不同的答案!
【解决方案2】:

你一共问了两个问题。

  1. java 是否对有序的顺序流(例如列表中的默认流)强制执行减少将始终根据遇到的顺序发生?

假设“将永远发生”是指函数求值的顺序,答案是,这个不能保证。

  1. Stream.of(" cats ", " eat ", " bats ")
      .reduce("", (a, b) -> a + b); // cats eat bats
    
    有了以上内容,输出可能是“bats cat eat”还是“eat bats cats”?这在规范中的某个地方有保证吗?

不管归约函数的求值顺序(处理顺序),保证结果为" cats eat bats ",正确反映遭遇顺序(另见this answer)。为确保未指定的处理顺序仍然产生关于遭遇顺序的正确结果,归约函数必须是关联的as specified

请注意,the documentation 甚至将.reduce("", String::concat) 显示为有效但低效的归约函数的示例。同样,(a,b) -> bhas been acknowledged 作为获取流最后一个元素的有效方式。

关键点在“Associativity” section of the documentation中给出:

关联性

如果满足以下条件,则运算符或函数 op关联

(a op b) op c == a op (b op c)

如果我们将其扩展到四个术语,则可以看出这对并行评估的重要性:

a op b op c op d == (a op b) op (c op d)

所以我们可以同时评估(a op b)(c op d),然后在结果上调用op

关联运算的示例包括数字加法、最小值和最大值以及字符串连接。

【讨论】:

    【解决方案3】:

    当您使用 Stream.of() 时,文档会说:

    返回一个顺序的有序流,其元素是指定的值。

    所以此时,你知道你有一个有序的顺序流,stream ops 的 javadoc 也说:

    对于顺序流,遇到顺序的存在与否不会影响性能,只会影响确定性。 如果流是有序的,在相同的源上重复执行相同的流管道将产生相同的结果;如果没有排序,重复执行可能会产生不同的结果。

    仅关于reduce 操作,当顺序流存在顺序时,结果应该相同,即使对于并行排序流,操作也将保持最终顺序(至少在 java8 的当前实现中)和 java9,未来可能会出现一些优化,但使用 reduce 的有序流的顺序可能永远不会改变)。

    您必须小心知道何时订购流。比如mapfilter这样的操作会保留流的顺序,所以如果你有一个有序流,你可以使用这个方法,流会继续有序。

    注意:ordered 与 sorted 完全不同

    如果流是有序的,则大多数操作都被限制为按照遇到的顺序对元素进行操作;如果流的来源是一个包含[1, 2, 3]的List,那么执行map(x -> x*2)的结果一定是[2, 4, 6]

    编辑(根据评论):

    但不限于顺序执行。

    这就是为什么需要关联性的原因,例如,如果您有一个从这样的数组生成的流 { a, b, c, d}, 然后是 a + @987654334 @ 可以先解决,然后c + d 最后一起解决 (a + b) + (c + d),这就是为什么操作必须是关联的。这样,如果操作确实是关联的(必须如此),最终的顺序将被保留。

    【讨论】:

    • 让我担心的是reduce docs中的这个简介“这相当于......但不限于按顺序执行。” docs.oracle.com/javase/8/docs/api/java/util/stream/…
    • 嗯,这引用了并行流,它使用相同的接口。例如,如果您有字符串abcd 在并行流中a + b 可以首先被解析,然后c + d 最后一起a + b + c + d,那是由操作必须是关联的。
    • 重要的是要强调相同的有序流只有在它们是连续的时才能保证产生相同的结果。甚至那个断言也是questionable
    • 这确实为 OP 的特定场景提供了答案,但我很确定 reduce() 也保证保留并行流的顺序。
    • @shmosel 我没有发现使用reduce时顺序是有保证的,但在当前的实现中这是事实(添加了一个说明),我想至少可以这么说,这无论如何都不太可能。
    【解决方案4】:

    我担心的是reduce docs中的这个简介“这相当于......但不限于按顺序执行。”

    Execution order is not the same as encounter order.流管道允许执行"cats" + ("eat" + "bats")("cats" + "eat") + "bats"

    【讨论】:

      猜你喜欢
      • 2013-11-12
      • 2016-02-18
      • 1970-01-01
      • 1970-01-01
      • 2019-09-21
      • 2016-12-14
      • 2015-01-17
      • 2019-02-09
      • 1970-01-01
      相关资源
      最近更新 更多