Dev
Dev

Reputation: 345

HashMap manipulation using streams Java 8

Please let me know if there is a possibility of changing the below code in terms of Java 8 using parallel streams?

I am looking for an option to run the "outer for loop" in parallel and finally all the values of stationMap gets collected together?

Map<Integer, Set<Integer>> stationMap = new HashMap<>();
Map<Integer, Set<Integer>> routes = function();
for (Map.Entry<Integer, Set<Integer>> entry : routes.entrySet()) 
{
     Set<Integer> stations = entry.getValue();

      for (Integer station : stations) {
        Set<Integer> temporaryStations = new HashSet<>(stations);
        Set<Integer> stationSet = stationMap.get(station);
        if (stationSet == null) {
          stationSet = new HashSet<>();
          temporaryStations.remove(station);
          stationSet.addAll(temporaryStations);
          stationMap.put(station, stationSet);
        } else {
          temporaryStations.remove(station);
          stationSet.addAll(temporaryStations);
        }
      }
    }

More shorter version:

routes.forEach((k, stations) -> {
      stations.forEach((station) -> {
        Set<Integer> stationSet = stationMap.get(station);
        if (stationSet == null) {
          stationSet = new HashSet<>();
          stationSet.addAll(stations);
          stationMap.put(station, stationSet);
        } else {
          stationSet.addAll(stations);
        }
      });
    });

Upvotes: 2

Views: 2109

Answers (1)

Holger
Holger

Reputation: 298599

Even the long pre-Java 8 version can be simplified as there is no need to iterate over the entry set, when you are only processing the values and there is no need for code duplication within the two conditional branches:

Map<Integer, Set<Integer>> routes = function();
Map<Integer, Set<Integer>> stationMap = new HashMap<>();
for(Set<Integer> stations: routes.values()) {
    for(Integer station: stations) {
        Set<Integer> temporaryStations = new HashSet<>(stations);
        temporaryStations.remove(station);
        Set<Integer> stationSet = stationMap.get(station);
        if (stationSet == null) {
            stationMap.put(station, temporaryStations);
        } else {
            stationSet.addAll(temporaryStations);
        }
    }
}

using Java 8 features, you may get the improved variant:

routes.values().forEach(stations ->
    stations.forEach(station -> {
        Set<Integer> temporaryStations = new HashSet<>(stations);
        temporaryStations.remove(station);
        Set<Integer> old = stationMap.putIfAbsent(station, temporaryStations);
        if(old!=null) old.addAll(stations);
    })
);

though it might be simpler to first merge all values and remove the keys afterwards in one step:

routes.values().forEach(stations ->
    stations.forEach(station -> 
        stationMap.computeIfAbsent(station, key -> new HashSet<>()).addAll(stations)
    )
);
stationMap.forEach((k,set) -> set.remove(k));

It’s possible to formulate an equivalent (parallel) Stream operation:

Map<Integer, Set<Integer>> stationMap=routes.values().parallelStream()
    .flatMap(stations -> stations.stream().map(station -> {
        Set<Integer> temporaryStations = new HashSet<>(stations);
        temporaryStations.remove(station);
        return new AbstractMap.SimpleImmutableEntry<>(station, temporaryStations);
    })
).collect(Collectors.toMap(
    Map.Entry::getKey, Map.Entry::getValue, (a,b) -> {a.addAll(b); return a; }));

but this may also be simpler when removing the keys from the value set in a post processing step:

Map<Integer, Set<Integer>> stationMap=routes.values().parallelStream()
    .flatMap(stations -> stations.stream().map(station -> 
        new AbstractMap.SimpleImmutableEntry<>(station, new HashSet<>(stations))
    )
).collect(Collectors.toMap(
    Map.Entry::getKey, Map.Entry::getValue, (a,b) -> {a.addAll(b); return a; }));
stationMap.entrySet().parallelStream().forEach(e -> e.getValue().remove(e.getKey()));

or you use a custom collector instead of flatMap:

Map<Integer, Set<Integer>> stationMap=routes.values().parallelStream()
    .collect(HashMap::new,
            (map,stations) -> stations.forEach(station -> 
                map.computeIfAbsent(station, key -> new HashSet<>()).addAll(stations)
            ),
            (m1,m2) -> m2.forEach((k,v)->m1.merge(k, v, (a,b)->{a.addAll(b); return a;})));
stationMap.entrySet().parallelStream().forEach(e -> e.getValue().remove(e.getKey()));

this might be more efficient as it doesn’t need the temporary Map.Entry instances.

Upvotes: 3

Related Questions