如何基于分隔符将List<String>转换为Map<String,List<String>>?

20

我有一个字符串列表,例如:

List<String> locations = Arrays.asList("US:5423","US:6321","CA:1326","AU:5631");

我想将其转换为 Map<String, List<String>>,就像这样:

AU = [5631]
CA = [1326]
US = [5423, 6321]

我尝试了这段代码,并且它能够正常工作,但是在这种情况下,我必须创建一个新的类GeoLocation.java

List<String> locations=Arrays.asList("US:5423", "US:6321", "CA:1326", "AU:5631");
Map<String, List<String>> locationMap = locations
        .stream()
        .map(s -> new GeoLocation(s.split(":")[0], s.split(":")[1]))
        .collect(
                Collectors.groupingBy(GeoLocation::getCountry,
                Collectors.mapping(GeoLocation::getLocation, Collectors.toList()))
        );

locationMap.forEach((key, value) -> System.out.println(key + " = " + value));

GeoLocation.java

private class GeoLocation {
    private String country;
    private String location;

    public GeoLocation(String country, String location) {
        this.country = country;
        this.location = location;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

但我想知道,是否有办法将 List<String> 转换为 Map<String, List<String>> 而不引入新类。


Java 再次受制于元组的缺乏 :( - Alexander
5个回答

27

你可以这样做:

Map<String, List<String>> locationMap = locations.stream()
        .map(s -> s.split(":"))
        .collect(Collectors.groupingBy(a -> a[0],
                Collectors.mapping(a -> a[1], Collectors.toList())));

A much better approach would be,

private static final Pattern DELIMITER = Pattern.compile(":");

Map<String, List<String>> locationMap = locations.stream()
    .map(s -> DELIMITER.splitAsStream(s).toArray(String[]::new))
        .collect(Collectors.groupingBy(a -> a[0], 
            Collectors.mapping(a -> a[1], Collectors.toList())));

更新

根据以下评论,这可以进一步简化为:

Map<String, List<String>> locationMap = locations.stream().map(DELIMITER::split)
    .collect(Collectors.groupingBy(a -> a[0], 
        Collectors.mapping(a -> a[1], Collectors.toList())));

3
第二种方法为什么更好我不明白。你能否详细说明一下? - Michael A. Schaffrath
1
我不确定这个正确。如果你查看String.split的源代码,你会发现针对1个字符的字符串进行了大量优化。 - Michael A. Schaffrath
后者表现更优秀,因为它使用了预编译的模式。 - Ravindra Ranwala
5
为什么不直接使用 DELIMITER.split(s)DELIMITER.splitAsStream(s).toArray(String[]::new) 的作用与 DELIMITER.split(s) 相同,但前者在处理大型字符串时更加高效。 - Lino
8
在尝试了1000万个位置后,发现了一个奇怪的结果,预编译模式需要大约0.830秒,而字符串分割需要大约0.58秒,从你提到的方法中,更好的方法不一定是最好的方法。因此,我使用s->s.split(":") - Vinit Solanki

3

试试这个

Map<String, List<String>> locationMap = locations.stream()
            .map(s ->  new AbstractMap.SimpleEntry<String,String>(s.split(":")[0], s.split(":")[1]))
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                     Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

1
可以在那里优化使用 split 两次。 - Naman
@Naman,没错!只是复制了OP的答案。...map(s->s.split(":")) .map(s -> new AbstractMap.SimpleEntry<String,String>(s[0],s[1]))... 尽管@Ravindra Ranwala更好。 - Hadi J

3
你可以将代码直接放在分组部分,将第一个组作为键,第二个组作为值,而不是先进行映射。
Map<String, List<String>> locationMap = locations
            .stream()
            .map(s -> s.split(":"))
            .collect( Collectors.groupingBy( s -> s[0], Collectors.mapping( s-> s[1], Collectors.toList() ) ) );

1
我发布后才注意到这个问题。 - n1t4chi

2

关于POJO,与流相比看起来并不复杂。

最初的回答

public static Map<String, Set<String>> groupByCountry(List<String> locations) {
    Map<String, Set<String>> map = new HashMap<>();

    locations.forEach(location -> {
        String[] parts = location.split(":");
        map.compute(parts[0], (country, codes) -> {
            codes = codes == null ? new HashSet<>() : codes;
            codes.add(parts[1]);
            return codes;
        });
    });

    return map;
}

1
似乎您的位置地图需要基于键进行排序,您可以尝试以下操作。
List<String> locations = Arrays.asList("US:5423", "US:6321", "CA:1326", "AU:5631");

    Map<String, List<String>> locationMap = locations.stream().map(str -> str.split(":"))
            .collect(() -> new TreeMap<String, List<String>>(), (map, parts) -> {
                if (map.get(parts[0]) == null) {
                    List<String> list = new ArrayList<>();
                    list.add(parts[1]);
                    map.put(parts[0], list);
                } else {
                    map.get(parts[0]).add(parts[1]);
                }
            }, (map1, map2) -> {
                map1.putAll(map2);
            });

    System.out.println(locationMap); // this outputs {AU=[5631], CA=[1326], US=[5423, 6321]}

Map 中,排序无关紧要,键和值应该按照所述方式存在。 - Vinit Solanki

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