Java 8 Lambdas flatmapping, groupingBy and mapping to get a Map of T and List<K>

335 Views Asked by At

Here's what I have so far:

Map<Care, List<Correlative>> mapOf = quickSearchList
                .stream()
                .map(QuickSearch::getFacility)
                .collect(Collectors.flatMapping(facility -> facility.getFacilityCares().stream(),
                    Collectors.groupingBy(FacilityCare::getCare,
                        Collectors.mapping(c -> {
                            final Facility facility = new Facility();
                            facility.setId(c.getFacilityId());
                            return Correlative.createFromFacility(facility);
                        }, Collectors.toList()))));

I have a list of Quick Searches to begin with. Each item in the quick search has a single facility as in:

public class QuickSearch {
  Facility facility;
}

In every Facility, there's a List of FacilityCare as in:

public class Facility {
  List<FacilityCare> facilityCares;
}

And finally, FacilityCare has Care property as in:

public class FacilityCare {
   Care care;
}

Now, the idea is to convert a List of QuickSearch to a Map of <Care, List<Correlative>>. The code within the mapping() function is bogus, in the example above. FacilityCare only has facilityID and not Facility entity. I want the facility object that went as param in flatMapping to be my param again in mapping() function as in:

Collectors.mapping(c -> Correlative.createFromFacility(facility)) 

where "facility" is the same object as the one in flatMapping.

Is there any way to achieve this? Please let me know if things need to be explained further.

Edit: Here's a solution doesn't fully utilize Collectors.

final Map<Care, List<Correlative>> mapToHydrate = new HashMap<>();

 quickSearchList
  .stream()
  .map(QuickSearch::getFacility)
  .forEach(facility -> {
    facility.getFacilityCares()
      .stream()
      .map(FacilityCare::getCare)
      .distinct()
      .forEach(care -> {
        mapToHydrate.computeIfAbsent(care, care -> new ArrayList<>());
        mapToHydrate.computeIfPresent(care, (c, list) -> {
          list.add(Correlative.createFromFacility(facility));
          return list;
        });
      });
    });
      
3

There are 3 best solutions below

2
On BEST ANSWER

Sometimes, streams are not the best solution. This seems to be the case, because you are losing each facility instance when going down the pipeline.

Instead, you could do it as follows:

Map<Care, List<Correlative>> mapToHydrate = new LinkedHashMap<>();
quickSearchList.forEach(q -> {
    Facility facility = q.getFacility();
    facility.getFacilityCares().forEach(fCare -> 
        mapToHydrate.computeIfAbsent(fCare.getCare(), k -> new ArrayList<>())
                    .add(Correlative.createFromFacility(facility)));
});

This uses the return value of Map.computeIfAbsent (which is either the newly created list of correlatives or the already present one).

It is not clear from your question why you need distinct cares before adding them to the map.


EDIT: Starting from Java 16, you might want to use Stream.mapMulti:

Map<Care, List<Correlative>> mapToHydrate = quickSearchList.stream()
    .map(QuickSearch::getFacility)
    .mapMulti((facility, consumer) -> facility.getFacilityCares()
        .forEach(fCare -> consumer.accept(Map.entry(fCare.getCare(), facility))))
    .collect(Collectors.groupingBy(
        e -> e.getKey(), 
        Collectors.mapping(
            e -> Correlative.createFromFacility(e.getValue()),
            Collectors.toList())));
0
On

Inspired by @fps answer, I was able to come up with a solution that will work for the time being (pre-Java16).

Map<Care, List<Correlative>> mapOf = quickSearchList
            .stream()
            .map(QuickSearch::getFacility)
            .map(expandIterable())
            .collect(
                Collectors.flatMapping(map -> map.entrySet().stream(),
                    Collectors.groupingBy(Map.Entry::getKey,
                        Collectors.mapping(entry -> Correlative.createFromFacility(entry.getValue()),
                            Collectors.toList()
                        )
                    )
                ));
    }

    public Function<Facility, Map<Care, Facility>> expandIterable() {
        return facility -> facility.getFacilityCares()
            .stream()
            .map(FacilityCare::getCare)
            .distinct()
            .collect(Collectors.toMap(c -> c, c -> facility));
    }

Basically, I added a method call that returns a Function that takes in Facility as argument and returns a Map of Care as key with Facility as value. That map is used in the collection of the previous stream.

4
On

This is what I came up with based on the information provided. The Facility and Care are stored in a temp array to be processed later in the desired map.

Map<Care, List<Correlative>> mapOf = quickSearchList.stream()
        .map(QuickSearch::getFacility)
        .flatMap(facility -> facility
                .getFacilityCares().stream()
        .map(facCare->new Object[]{facility, facCare.getCare()}))
        .collect(Collectors.groupingBy(obj->(Care)obj[1], Collectors
                .mapping(obj -> Correlative.createFromFacility(
                        (Facility)obj[0]),
                        Collectors.toList())));

I prepared some simple test data and this seems to work assuming I understand the ultimate goal. For each type of care offered, it puts all the facilities that offer that care in an associated list of facilities.