简体   繁体   中英

Does reduction on an ordered stream reduce in order?

I have a List of A, B, C.

C reduce A reduce B != A reduce B reduce C (however, A reduce (B reduce C) is OK).

In other words, my reduction operation is associative but not commutative.

Does java enforce on an ordered sequential stream (such as the default one from a list) that reduction will always happen according to the encounter order? That is to say, will java reorder reductions (such that B reduce A instead of A reduce B)?

(Hopefully this is clear enough).

edit to add a little demo and maybe helping to clarify

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

With the above, could the output ever be "bats cats eat" or "eat bats cats"? Is that guaranteed somewhere in the spec?

According to the specification it respects the order of the elements.

A proof is very simple. The specification claims that a reduction function has to be associative .

However, associativity it self doesn't make any sense if the order is not preserved. According to the mathematical definition of the associative property:

Within an expression containing two or more occurrences in a row of the same associative operator, the order in which the operations are performed does not matter as long as the sequence of the operands is not changed .

In other words, associative property doesn't imply that:

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

It only allows an arbitrary permutation of the order in which operations are applied.

You have asked two questions in one.

  1. Does java enforce on an ordered sequential stream (such as the default one from a list) that reduction will always happen according to the encounter order?

Assuming “will always happen” is referring to the order of the function evaluation, the answer is no , this is not guaranteed.

  1.  Stream.of(" cats ", " eat ", " bats ") .reduce("", (a, b) -> a + b); // cats eat bats
    With the above, could the output ever be "bats cats eat" or "eat bats cats"? Is that guaranteed somewhere in the spec?

Regardless of the evaluation order of the reduction function (the processing order ), the result is guaranteed to be " cats eat bats " , correctly reflecting the encounter order (see also this answer ). To ensure that the unspecified processing order still yields the correct result regarding the encounter order, the reduction function must be associative , as specified

Note that the documentation even shows .reduce("", String::concat) as an example of a valid, but inefficient reduction function. Similarly, (a,b) -> b has been acknowledged as a valid way to get the last element of a stream.

The key point is given in the “Associativity” section of the documentation :

Associativity

An operator or function op is associative if the following holds:

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

The importance of this to parallel evaluation can be seen if we expand this to four terms:

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

So we can evaluate (a op b) in parallel with (c op d) , and then invoke op on the results.

Examples of associative operations include numeric addition, min, and max, and string concatenation.

When you use Stream.of() the doc says:

Returns a sequential ordered stream whose elements are the specified values.

So at this point, you know you have a ordered sequential stream, and the javadoc of stream ops also says:

For sequential streams, the presence or absence of an encounter order does not affect performance, only determinism. If a stream is ordered , repeated execution of identical stream pipelines on an identical source will produce an identical result ; if it is not ordered, repeated execution might produce different results.

Regarding only to the reduce operation, the result should be identical when the order exists for sequential streams, and even for parallel ordered streams the operation will keep the final order ( at least in the current implementations of java8 and java9, in the future some optimizations can occur, but the order of ordered streams using reduce will probably never change ).

You have to be carefull of knowing when the stream is ordered. For instance, operations like map or filter preserves the order of the stream, so if you have an ordered stream, you can use this methods and the stream will continue to be ordered.

note: ordered is totally different than sorted .

If a stream is ordered, most operations are constrained to operate on the elements in their encounter order; if the source of a stream is a List containing [1, 2, 3], then the result of executing map(x -> x*2) must be [2, 4, 6]

Edit (according to the comment):

But is not constrained to execute sequentially.

This is why the associativity is necessary, for instance, if you have a stream generated from an array like this { a , b , c , d }, then a + b could be resolved first, then c + d and finally all together ( a + b ) + ( c + d ), that's why the operation must be associative. This way if the operation is indeed associative (like it has to be) the final order will be preserved.

What concerns me is this blurb in the reduce docs "This is equivalent to ... but is not constrained to execute sequentially."

Execution order is not the same as encounter order. The stream pipeline is allowed to perform "cats" + ("eat" + "bats") or ("cats" + "eat") + "bats"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM