To accepted answer already answers what is important to know about the question. However, I still felt a loose gap: and how would be using reduce
?
Foreplay
To begin with, we need to understand what are the possible types of reduce
; the guys are next (in front I put a alias so that I can refer throughout the text):
op-reduce
This reduction is useful for when there is no previously known neutral/Identite value. Generally, it is identical to the behavior of id-merge, except in a special case: in the case of the empty set. For the general case, I prefer to explain within the body of the other reduction.
When the input set is empty, the op-reduce returns Optional.empty()
, otherwise return a Optional
with the appropriate value. You can treat the result in the traditional way of Optional
:
Stream<X> fluxo = ...;
Optional<X> reducao = fluxo.reduce((x1, x2) -> operacaoCombinaX(x1, x2));
reducao.ifPresentOrElse(x -> System.out.println("A redução é " + x), () -> System.out.println("Fluxo vazio =P");
id-merge
Here, the reduction ensures that there is at least the neutral element. An element id
is considered neutral if, for the operation op
, op(id, x) ==> x
, whatever x
.
This reduction will always return a value, even if it is the identity. It can be understood in several ways:
T respostaReducao = identity;
for (T valorNovo: valoresDaStream) {
respostaReducao = accumulator(respostaReducao, valorNovo);
}
But this operation does not need to be linear. If you have 6 elements and want to reduce them all, you could also do in parallel:
x1---x2 x3---x4 id---x5 id---x6
| | | |
x7-------x8 x5-------x6
| |
x9----------------xa
|
xb
In this operations tree, each vertical bar |
is the application of the accumulate
for the past arguments.
Operations with the identity id
are tautological, but I put them by illustration, for all the operations to have the same level of depth.
map-merge
This mapping will not return the same type as the last type. It can be thought of as a kind of mapping followed by reduction. The following codes are more or less equivalent:
Stream<X> fluxo = ...;
U id = ...;
BiFunction<U, T, U> map2u = ...;
BinaryOperator<U> merge = ...;
// usando diretamente a redução map-merge
U reducaoMapMerge = fluxo.reduce(id, map2u, merge);
// fazendo o mapeamento para U e usando id-merge
U reducaoIdMerge = fluxo.map(map2u).reduce(id, merge);
// fazendo o mapeamento para U e usando op-reduce, pegando identidade caso vazio
U reducaoOpReduce = fluxo.map(map2u).reduce(merge).orElse(id);
What kind of appropriate reduction to the issue?
The first thing to observe is what you have and what you want to return.
- what you got? a flow of
Route
- what is intended to return? one multimope of the key to all
Route
s of that key
Since the type of return obtained is different from the type of input, then I go to the strategy of map-reduce. Now, I need to establish who is the neutral element and what are the functions:
- who is id?
new HashMap<String, List<Route>>()
in case, we could use the diamond operator to treat this more elegantly new HashMap<>()
, but I wanted to make it clear which types are used
who is accumulate? the method Map.merge
seems appropriate, just need first transform the new Route
spent in a List<Route>
before; or check if the key exists and enter at the end of the list:
// usando o Map.merge
(hm, route) -> {
List<Route> lista = new ArrayList<>();
lista.add(route);
hm.merge(route.getSource(), lista, (l1, l2) -> { l1.addAll(l2), return l1; });
return hm;
};
// inspecionando se já existe a lista e, caso contrário, criando-a
(hm, route) -> {
List<Route> lista = hm.getOrDefault(route.getSource(), new ArrayList<>());
lista.add(route);
hm.putIfAbsent(route.getSource(), lista);
return hm;
};
who is combine? this method should take two Map
s and match them; I can iterate on the second keys Map
and call something to mix the two lists:
(hm1, hm2) -> {
hm2.forEach( (k, listaRoutes) -> {
List<Route> listaOriginal = hm1.get(k);
if (listaOriginal == null) {
hm1.put(k, listsRoutes);
} else {
listaOriginal.addAll(listaRoutes);
}
});
return hm1;
};
What the code looks like?
It looks something like this, in this case:
Stream<Route> fluxo = ...;
Map<String, List<Route>> mapeamento =
fluxo.reduce(new HashMap<>(),
/* accumulate */
(hm, route) -> {
List<Route> lista = hm.getOrDefault(route.getSource(), new ArrayList<>());
lista.add(route);
hm.putIfAbsent(route.getSource(), lista);
return hm;
},
/* combine */
(hm1, hm2) -> {
hm2.forEach( (k, listaRoutes) -> {
List<Route> listaOriginal = hm1.get(k);
if (listaOriginal == null) {
hm1.put(k, listsRoutes);
} else {
listaOriginal.addAll(listaRoutes);
}
});
return hm1;
}
);
Jabah: totalcross-functional-toolbox
Totalcross alone does not provide the necessary support for stream
s of Java 8. Since Totalcross provides virtually no support for Java 8 (with exceptions through retrolambda
and some functional interfaces (but not all correctly)), and had the need to use stream
in my project, so I implemented something similar to the use of stream
s.
With that came the project totalcross-functional-toolbox
, created here at the company and made available under the MIT license, which among other things with the most functional footprint also worried about implementing what we need to use Stream
and of Optional
.
So I had to implement the Collectors.groupingBy(Function<T, K> keyExtractor)
. Basically, an implementation that works is to do the Collectors.groupingBy
call the Collectors.toMap
transforming each individual element into a changeable list of an element (CollectionUtils::getSingletonList
) and a method to concatenate two lists (CollectionUtils::addAll
).
More precisely? When did I implement Collectors.groupingBy
, I did a very similar version of reduce
that was made here.
I think you already have one
Collector
do that...– Jefferson Quesado
has kick of any? to with difficulty of which to use
– Luis Felipe