Java 8 convert List to Lookup Map

前端 未结 9 2056
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-12 03:29

I have a list of Station, in each Station there is a list of radios. I need to create a lookup Map of radio to Station. I know how to use Java 8 stream forEach to do it:

相关标签:
9条回答
  • 2021-01-12 04:01

    Based on the question, considering the entities Radio and Station to be defined as:

    @lombok.Getter
    class Radio {
        ...attributes with corresponding 'equals' and 'hashcode'
    }
    
    @lombok.Getter
    class Station {
        List<Radio> radios;
        ... other attributes
    }
    

    One can create a lookup map from a List<Station> as an input using a utility such as:

    private Map<Radio, Station> createRadioToStationMap(final List<Station> stations) {
        return stations.stream()
                // create entries with each radio and station
                .flatMap(station -> station.getRadios().stream()
                        .map(radio -> new AbstractMap.SimpleEntry<>(radio, station)))
                // collect these entries to a Map assuming unique keys
                .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey,
                        AbstractMap.SimpleEntry::getValue));
    }
    

    Slightly different from this behaviour, if for same(equal) Radio element across multiple Stations, one wants to group all such stations, it can be achieved using groupingBy instead of toMap such as :

    public Map<Radio, List<Station>> createRadioToStationGrouping(final List<Station> stations) {
        return stations.stream()
                .flatMap(station -> station.getRadios().stream()
                        .map(radio -> new AbstractMap.SimpleEntry<>(radio, station)))
                // grouping the stations of which each radio is a part of
                .collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getKey,
                        Collectors.mapping(AbstractMap.SimpleEntry::getValue, Collectors.toList())));
    }
    
    0 讨论(0)
  • 2021-01-12 04:02

    If you are open to using a third-party library, there is the method groupByEach from Eclipse Collections:

    Multimap<Radio, Station> multimap = 
        Iterate.groupByEach(stationList, Station::getRadioList);
    

    This can also be written using Java 8 Streams with the Collectors2 utility from Eclipse Collections:

    Multimap<Radio, Station> multimap =
            stationList.stream().collect(
                    Collectors2.groupByEach(
                            Station::getRadioList,
                            Multimaps.mutable.list::empty));
    

    Note: I am a committer for Eclipse Collections.

    0 讨论(0)
  • 2021-01-12 04:08

    We can save the intermediate step of collectiong to a Map by transforming directly to a Stream of SimpleEntry, for example:

    Map<Long, Station> result = stationList.stream()
                    .flatMap(station -> station.getRadioList().stream().map(radio -> new SimpleEntry<>(radio, station)))
                    .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
    
    0 讨论(0)
  • 2021-01-12 04:16

    You can do it without Streams, of course, probably making it a bit more readable.

    Map<Radio, Station> LOOK_UP = new HashMap<>();
    List<Station> stations = ...
    
    
    stations.forEach(station -> {
        station.getRadios().forEach(radio -> {
             LOOK_UP.put(radio, station);
        });
    });
    

    This is not very different than a plain loop with:

    for (Station station : stations) {
         for (Radio radio : station.getRadios()) {
              LOOK_UP.put(radio, station);
         }
    }
    

    The obvious problem here is that LOOK_UP::put will always replace the value for a certain key, hiding the fact that you ever had duplicates. For example:

    [StationA = {RadioA, RadioB}]
    [StationB = {RadioB}]
    

    When you search for RadioB - what should you get as a result?

    If you could have such a scenario, the obvious thing is to change the LOOK-UP definition and use Map::merge:

        Map<Radio, List<Station>> LOOK_UP = new HashMap<>();
        List<Station> stations = new ArrayList<>();
    
        stations.forEach(station -> {
            station.getRadios().forEach(radio -> {
                LOOK_UP.merge(radio,
                              Collections.singletonList(station),
                              (left, right) -> {
                                  List<Station> merged = new ArrayList<>(left);
                                  merged.addAll(right);
                                  return merged;
                              });
            });
        });
    

    Another possibility is to throw an Exception when there are these kid of mappings:

    stations.forEach(station -> {
           station.getRadios().forEach(radio -> {
                LOOK_UP.merge(radio, station, (left, right) -> {
                     throw new RuntimeException("Duplicate Radio");
                });
           });
     });
    

    The problem with this last snippet, is that you can't really log the radio that is to be blamed for non-uniqueness. left and right are Stationss. If you want that too, you will need to use a merger that does not rely on Map::merge internally, like in this answer.

    So you can see, that it all depends on how and what exactly you need to handle.

    0 讨论(0)
  • 2021-01-12 04:19

    I don't think that you can do it in more concise way using Collectors, as compared to mixed solution like

        stationList.stream().forEach(station -> {
            for ( Long radio : station.getRadioList() ) {
                radioToStationMap.put(radio, station);
            }
        });
    

    or

        stationList.forEach(station -> {
            station.getRadioList().forEach(radio -> {
                radioToStationMap.put(radio, station);
            });
        });
    

    (you can call .forEach directly on collections, don't need to go through .stream())

    Shortest fully 'functional' solution I was able to come up with would be something like

     stationList.stream().flatMap(
         station -> station.getRadioList().stream().map(radio -> new Pair<>(radio, station)))
     .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
    

    using any of the Pair classes available in third party libraries. Java 8 is very verbose for simple operations, compared to dialects like Xtend or Groovy.

    0 讨论(0)
  • 2021-01-12 04:20

    This should work, and you don't need third parties.

    stationList.stream()
        .map(s -> s.getRadioList().stream().collect(Collectors.toMap(b -> b, b -> s)))
        .flatMap(map -> map.entrySet().stream())
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    
    0 讨论(0)
提交回复
热议问题