How to use Stream.reduce() appropriately

Asked

Viewed 972 times

7

My problem: I have an Arraylist of the type of Routes (first value is the name of the city, second value is the destination city and the third value at distance), I want to create a new map organizing these routes in their due cities origin.

example:

I have a list of routes like this = [AB6, AE4, BA6, BC2, BD4, CB3, CD1, CE7, BD8, EB5, ED7]

and the final map I want would be {A=[AB6, AE4], B=[BA6 , BC2, BD4, BD8], C=[CB3, CD1, CE7]}

My list of routes:

List<Route> routes = new ArrayList<Route>();
            routes.add(new Route("A","B",6));
            routes.add(new Route("A","E",4));
            routes.add(new Route("B","A",6));
            routes.add(new Route("B","C",2));
            routes.add(new Route("B","D",4));
            routes.add(new Route("C","B",3));
            routes.add(new Route("C","D",1));
            routes.add(new Route("C","E",7));
            routes.add(new Route("B","D",8));
            routes.add(new Route("E","B",5));
            routes.add(new Route("E","D",7));


            Stream data = Stream.of(routes);

from that list I have tried to make a reduce, where I pass an empty map and according to the function I passed I fill this map, but I can’t walk...

data.reduce(new HashMap<String,List<Route>> (), (map, current) -> {
    if (map.containsValue(current.getSource()/* seria o primeiro valor da rota */)) {
                map.get(current.getSource()).add(current);
    } else {
                List<Route> x = new ArrayList<>();
                x.add(current);
                map.put(current.getSource(),x);
    }
return map;
});

That’s what I thought I could do, but I couldn’t... My question is: Am I missing using Reduce? or is it Reduce I’m just getting lost in logic?

  • I think you already have one Collector do that...

  • has kick of any? to with difficulty of which to use

2 answers

6


I believe what you need is groupingBy. Taking advantage of your list routes and passing as the grouping criterion a class property Route, in the case, getOrigem():

Map<String, List<Route>> rotasAgrupadas = routes.stream().collect(Collectors.groupingBy(Route::getOrigem);
  • It worked, Thank you !!!! was taking me to sleep already rs

  • 2

    If it helped you, mark the answer as correct, to help other colleagues in the future.

  • 3

    Collectors.groupingBy(Route::getOrigem) is a little simpler.

  • @Victorstafusa thanks for the tip. Edited in response.

5

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 Routes 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 Maps 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 streams 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 streams.

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.

  • Thank you Jefferson, the other answer answered me, but I really liked yours, and the cool thing is that it is known to the next. Congratulations.

  • 1

    @Luisfelipe my idea was to stay for reading reference. I really believe that the most correct one is the Statelessdev, I would have answered so. But I was slow, so I found myself asking myself, "What if someone stops asking this question with this need for the title?" and I decided to write this from here.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.