Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Stream with map and multiples sets

I am trying to write these lines using java8 streams:

    for (Town town : getAllTowns(routes)) {

        if (originTown.equals(town))
            continue;

        for (Route route : routes) {
            if (route.hasOrigin(originTown) && route.hasDestine(town)) {
                distances.put(town, route.getDistance());
                break;
            }
            distances.put(town, maxDistance);
        }
    }
    return distances; //Map<Town,Integer>

The result that I got so far is:

Map<Town, Integer> distances = getAllTowns(routes).stream()
.filter(town -> !originTown.equals(town))
.forEach(town -> routes.stream()
        .filter(route -> route.hasOrigin(originTown) && route.hasDestine(town)
        ...)
return distances;

How can I collect after the inner filter and build the Map< Town,Integer> where the integer is the route.getDistance()? I tried to use:

.collect(Collectors.toMap(route -> route.getDestineTown(), route -> route.getDistance()))

But it is inside the forEach call, then I can't return it to my variable distances because it generates the map only for the inner call. I did not understand it. Any input would be really helpful. Thanks.

like image 689
fforbeck Avatar asked Nov 29 '25 15:11

fforbeck


2 Answers

You can use findFirst() to build a list that contains, for each town, the first route that has that town as the destination, and then call toMap() on it. The default values for missing cities can be handled separately.

Collection<Town> towns = getAllTowns(routes);
Map<Town, Integer> distances = towns.stream()
    .filter(town -> !originTown.equals(town))
    .map(town -> routes.stream()
        .filter(route -> route.hasOrigin(originTown) && route.hasDestine(town))
        .findFirst())
    .filter(Optional::isPresent)
    .collect(toMap(route -> route.get().getDestine(), route -> route.get().getDistance()));
towns.stream()
    .filter(town -> !distances.containsKey(town))
    .forEach(town -> distances.put(town, maxDistance));

(Note that town is no longer available in collect(), but you can take advantage of the fact that each route got added only if its destination town was town.)

Also note that toMap() doesn't accept duplicate keys. If there can be multiple routes to any town (which I assume there might be), you should use groupingBy() instead.

like image 158
Aasmund Eldhuset Avatar answered Dec 01 '25 05:12

Aasmund Eldhuset


I think you have two options to solve this. Either you create your resulting Map beforehand and use nested foreachs:

Map<Town, Integer> distances = new HashMap<>();
    getAllTowns(routes).stream().filter(town -> !originTown.equals(town))
            .forEach(town -> routes.stream().forEach(route -> distances.put(town,
                    route.hasOrigin(originTown) && route.hasDestine(town) ? route.getDistance() : maxDistance)));

The other option is to collect your stream to a Map by creating an intermediate Object which is essentially a Pair of Town and Integer:

Map<Town, Integer> distances = getAllTowns(routes).stream().filter(town -> !originTown.equals(town))
            .flatMap(town -> routes.stream()
                    .map(route -> new AbstractMap.SimpleEntry<Town, Integer>(town,
                            route.hasOrigin(originTown) && route.hasDestine(town) ? route.getDistance()
                                    : maxDistance)))
            .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); 
like image 43
garnulf Avatar answered Dec 01 '25 05:12

garnulf