Java 8 将 List 转换为 Lookup Map

8

我有一个车站列表,每个车站都有一个无线电列表。我需要创建一个无线电到车站的查找映射表。我知道如何使用Java 8流的forEach方法来完成:

stationList.stream().forEach(station -> {
    Iterator<Long> it = station.getRadioList().iterator();
    while (it.hasNext()) {
        radioToStationMap.put(it.next(), station);
    }
});

但我认为应该有更简洁的方法,比如使用Collectors.mapping()。请问有人可以帮忙吗?

是否保证每个电台只对应一个无线电图? - slim
1
stationList.stream().forEach(station -> station.getRadioList().stream().forEach(rl -> radioToStationMap.put(rl, station))) 还不够好吗? - ZhenyaM
3
使用流操作和collect方法将数据写入可变列表要比使用forEach更好。 - RealSkeptic
可能是将对象的列表转换为映射 - 使用Java 8中的lambda表达式的重复问题。 - Mohamed Gad-Elrab
4
我相信最“简洁的方法”就是根本不使用lambda表达式或Streams。 - VGR
@EricWilson,哪一部分是“非常复杂”的?我一次阅读就理解了答案。我同意它可能需要更多的细节,但除此之外 - 它是一个好答案。 - Eugene
9个回答

10

这应该可以工作,而且你不需要第三方。

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));

4

根据问题,考虑将实体RadioStation定义为:

@lombok.Getter
class Radio {
    ...attributes with corresponding 'equals' and 'hashcode'
}

@lombok.Getter
class Station {
    List<Radio> radios;
    ... other attributes
}

使用类似于以下的实用工具,可以从输入为List<Station>的列表中创建查找映射表:

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));
}

与此行为略有不同的是,如果在多个Station中使用相同(相等)的Radio元素,希望将所有这样的站点分组,则可以使用groupingBy而不是toMap来实现,例如:

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())));
}

1

如果您愿意使用第三方库,可以使用Eclipse Collections中的groupByEach方法:

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

这也可以使用Java 8的流和Eclipse Collections中的Collectors2工具编写:

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

注意:我是 Eclipse Collections 的提交者。

0
原来答案有点不同,但我们可以使用Java9提供的flatMapping收集器来完成它。
这是你的站点类 -
class Station {
public List<String> getRadioList() {
    return radioList;
}

private List<String> radioList = new ArrayList<>();
}

而且你想要映射的车站列表 -

        List<Station> list = new ArrayList<>();

以下是代码,可以使用flatMapping收集器进行映射。
list.stream().collect(Collectors.flatMapping(station ->
                    station.getRadioList().stream()
                            .map(radio ->Map.entry( radio, station)),
            Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue), (radio, radio2) -> radio2)));
  1. 我们将把它们转换为Map.Entry
  2. 我们将使用flatmapping收集所有这些内容

如果您不想使用flatMapping,实际上您可以先使用FlatMap然后再进行收集,这样会更易读。

list.stream().flatMap(station -> station.getRadioList().stream().map(s -> Map.entry(s, station)))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (radio, radio2) -> radio2)));

0

就像Artur Biesiadowski的回答一样,我认为你必须创建一个配对列表,然后将它们分组,至少如果你想考虑电台不是唯一的情况。

在C#中,你可以使用实用的匿名类来实现这一点,但在Java中,你至少需要定义Pair类的接口。

interface Radio{ }
interface Station {
    List<Radio> getRadioList();
}
interface RadioStation{
    Station station();
    Radio radio();
}

List<Station> stations = List.of();

Map<Radio,List<Station>> result= stations
   .stream()
   .flatMap( s-> s.getRadioList().stream().map( r->new RadioStation() {
        @Override
        public Station station() {
            return s;
        }

        @Override
        public Radio radio() {
            return r;
        }
    }  )).collect(groupingBy(RadioStation::radio, mapping(RadioStation::stations, toUnmodifiableList())));

0

我们可以通过直接转换为SimpleEntry的Stream来避免收集到Map的中间步骤,例如:

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

当然,你可以不使用流来完成它,这样可能会使代码更易读。

Map<Radio, Station> LOOK_UP = new HashMap<>();
List<Station> stations = ...


stations.forEach(station -> {
    station.getRadios().forEach(radio -> {
         LOOK_UP.put(radio, station);
    });
});

这与普通循环并没有太大的区别:

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

这里显然存在一个问题,即LOOK_UP::put总是会替换某个键的值,隐藏了您曾经有重复的事实。例如:
[StationA = {RadioA, RadioB}]
[StationB = {RadioB}]

当你搜索 RadioB 时,你应该得到什么结果?
如果你能有这样的场景,显而易见的是改变 LOOK-UP 的定义并使用 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;
                          });
        });
    });

另一种可能性是在存在这些映射时抛出异常:
stations.forEach(station -> {
       station.getRadios().forEach(radio -> {
            LOOK_UP.merge(radio, station, (left, right) -> {
                 throw new RuntimeException("Duplicate Radio");
            });
       });
 });

这个最后一段代码片段的问题在于,你无法真正记录导致非唯一性的radioleftright都是Stations。如果你也想要记录它们,你需要使用一个不依赖于Map::merge内部的合并器,就像this answer中所示。
因此,你可以看到,所有这些都取决于你需要如何以及确切需要处理什么。

0

我认为使用Collectors更简洁的方式无法完成它,与混合解决方案相比。

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

或者

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

(您可以直接在集合上调用.forEach,无需通过.stream()

我能想到的最短的完全“功能性”解决方案是:

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

使用第三方库中提供的任何一种Pair类。与Xtend或Groovy等方言相比,Java 8在简单操作方面非常冗长。


如果 radio 中有重复项,你将会得到一个 java.lang.IllegalStateException: Duplicate key 异常。为了防止这种情况发生,您可以在您的解决方案中添加类似于 Collectors.toMap(Pair::getKey, Pair::getValue, (station, station2) -> station2) 的合并操作。 - Vlad Bochenin
1
如果有重复项,可能应该使用Multimap。 - Artur Biesiadowski

0

这样怎么样:

radioToStationMap = StreamEx.of(stationList)
        .flatMapToEntry(s -> StreamEx.of(s.getRadioList()).mapToEntry(r -> s).toMap())
        .toMap();

StreamEx提供


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接