У меня есть список строк, например:
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>>
без введения нового класса.
Вы можете сделать это так:
Map<String, List<String>> locationMap = locations.stream()
.map(s -> s.split(":"))
.collect(Collectors.groupingBy(a -> a[0],
Collectors.mapping(a -> a[1], Collectors.toList())));
Гораздо лучшим подходом было бы,
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())));
Я не вижу, чем второй подход лучше. Не могли бы вы уточнить?
Я не уверен, что это правильно. Если вы посмотрите на исходный код String.split, вы увидите, что для односимвольных строк сделано множество оптимизаций.
Последний превосходит первый, поскольку использует предварительно скомпилированный шаблон.
DELIMITER.splitAsStream(s).toArray(String[]::new)
почему бы просто не использовать DELIMITER.split(s)
?
Странный результат обнаружен при попытке с 10 миллионами местоположений, предварительно скомпилированный шаблон занимает около 0,830 секунды, а разделение строки занимает около 0,58 секунды, гораздо лучший подход - не лучший подход, из которого вы упомянули. Поэтому я использую s -> s.split(":")
Попробуй это
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())));
Здесь можно оптимизировать использование split
дважды.
@Наман, верно! только что скопировал ответ OP....map(s->s.split(":")) .map(s -> new AbstractMap.SimpleEntry<String,String>(s[0],s[1]))...
хотя @Ravindra Ranwala лучше
Вы можете просто поместить код в группировку по частям, где вы помещаете первую группу как ключ, а вторую как значение, вместо того, чтобы сопоставлять ее первой.
Map<String, List<String>> locationMap = locations
.stream()
.map(s -> s.split(":"))
.collect( Collectors.groupingBy( s -> s[0], Collectors.mapping( s-> s[1], Collectors.toList() ) ) );
Я заметил это после публикации
А как же ПОЖО. Выглядит несложно по сравнению со стримами.
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;
}
Похоже, ваша карта местоположения должна быть отсортирована на основе ключей, вы можете попробовать следующее
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
, ключ и значение должны присутствовать, как указано.
Отсутствие кортежей в Java снова поражает :(