如何根据 Java 8 中另一个列表的元素对列表的元素进行分组
How to group elements of a List by elements of another in Java 8
我有以下问题:鉴于这些 类,
class Person {
private String zip;
...
public String getZip(){
return zip;
}
}
class Region {
private List<String> zipCodes;
...
public List<String> getZipCodes() {
return zipCodes;
}
}
使用 Java 8 流 API,如何根据 Region
是否包含 Person
的邮政编码来获取 Map<Person, List<Region>>
?换句话说,我如何根据邮政编码属于这些地区的人对地区进行分组?
我在 Java 7 中以老式方式完成了此操作,但现在我必须迁移代码以利用 Java 8 中的新功能。
谢谢,
因佩托
原来的答案用元组做了一个不必要的映射,所以你看到了最终的解决方案。您可以删除映射,直接过滤 regions
列表:
//A Set<Region> is more appropriate, IMO
.stream()
.collect(toMap(p -> p,
p -> regions.stream()
.filter(r -> r.getZipCodes().contains(p.getZip()))
.collect(toSet())));
如果我理解得很好,你可以这样做:
import java.util.AbstractMap.SimpleEntry;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toList;
...
List<Person> persons = ...;
List<Region> regions = ...;
Map<Person, List<Region>> map =
persons.stream()
.map(p -> new SimpleEntry<>(p, regions))
.collect(toMap(SimpleEntry::getKey,
e -> e.getValue().stream()
.filter(r -> r.getZipCodes().contains(e.getKey().getZip()))
.collect(toList())));
从 List<Person>
你得到一个 Stream<Person>
。然后将每个实例映射到包含所有区域的元组 <Person, List<Region>>
。从那里,您使用 toMap
收集器在地图中收集数据,并为每个人构建一个 Region
列表,其中包含该人的邮政编码。
例如,给定输入:
List<Person> persons = Arrays.asList(new Person("A"), new Person("B"), new Person("C"));
List<Region> regions =
Arrays.asList(new Region(Arrays.asList("A", "B")), new Region(Arrays.asList("A")));
它输出:
Person{zip='A'} => [Region{zipCodes=[A, B]}, Region{zipCodes=[A]}]
Person{zip='B'} => [Region{zipCodes=[A, B]}]
Person{zip='C'} => []
另外我猜每个 Region
的 zipCodes
可能是 Set
.
我没有对此代码进行任何测试,但它编译了所以它一定是正确的(:eyeroll:)。
public Map<Person,List<Region>> mapPeopleToRegion(List<Person> people, List<Region> regions){
final Map<Person,List<Region>> personToRegion = new HashMap<>();
people.forEach(person ->
personToRegion.put(
person,regions.stream().filter(
region -> region.getZipCodes().contains(person.getZip()))
.collect(Collectors.toList())));
return personToRegion;
}
我怀疑最简洁的方法是——我对发布的其他答案不太满意——是
persons.stream().collect(Collectors.toMap(
person -> person,
person -> regions.stream()
.filter(region -> region.getZipCodes().contains(person.getZip()))
.collect(Collectors.toList())));
它仍然很丑陋,我认为通过稍微改变你的建模方式会有所改善,但到目前为止我只设法提出以下内容:
public static void main(String[] args) {
Person[] people = {new Person("00001"), new Person("00002"), new Person("00005")};
Region[] regions = {
new Region("Region 1", Arrays.asList("00001", "00002", "00003")),
new Region("Region 2", Arrays.asList("00002", "00003", "00004")),
new Region("Region 3", Arrays.asList("00001", "00002", "00005"))
};
Map<Person, List<Region>> result = Stream.of(regions)
.flatMap(region -> region.getZipCodes().stream()
.map(zip -> new SimpleEntry<>(zip, region)))
.flatMap(entry -> Stream.of(people)
.filter(person -> person.getZip().equals(entry.getKey()))
.map(person -> new SimpleEntry<>(person, entry.getValue())))
.collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, Collectors.toList())));
result.entrySet().forEach(entry -> System.out.printf("[%s]: {%s}\n", entry.getKey(), entry.getValue()));
// Output:
// [Person: 0]: {[name: Region 1, name: Region 3]}
// [Person: 1]: {[name: Region 1, name: Region 2, name: Region 3]}
// [Person: 2]: {[name: Region 3]}
}
有一个包含映射并且可以键入的 ZipCode
class 会让事情变得更干净:
public static void main(String[] args) {
Region r1 = new Region("Region 1");
Region r2 = new Region("Region 2");
Region r3 = new Region("Region 3");
ZipCode zipCode1 = new ZipCode("00001", Arrays.asList(r1, r3));
ZipCode zipCode2 = new ZipCode("00002", Arrays.asList(r1, r2, r3));
ZipCode zipCode3 = new ZipCode("00003", Arrays.asList());
ZipCode zipCode4 = new ZipCode("00004", Arrays.asList());
ZipCode zipCode5 = new ZipCode("00005", Arrays.asList(r3));
Person[] people = {
new Person(zipCode1),
new Person(zipCode2),
new Person(zipCode5)
};
Map<Person, List<Region>> result = Stream.of(people)
.collect(Collectors.toMap(person -> person,
person -> person.getZip().getRegions()));
result.entrySet().forEach(entry -> System.out.printf("[%s]: {%s}\n", entry.getKey(), entry.getValue()));
// Output:
// [Person: 0]: {[name: Region 1, name: Region 3]}
// [Person: 1]: {[name: Region 1, name: Region 2, name: Region 3]}
// [Person: 2]: {[name: Region 3]}
}
其他一些答案包含在列表中进行大量线性搜索的代码。我认为 Java 8 Stream 解决方案应该不会比经典变体慢很多。
因此,这是一个在不牺牲太多性能的情况下利用 Streams 的解决方案。
List<Person> people = ...
List<Region> regions = ...
Map<String, List<Region>> zipToRegions =
regions.stream().collect(
() -> new HashMap<>(),
(map, region) -> {
for(String zipCode: region.getZipCodes()) {
List<Region> list = map.get(zipCode);
if(list == null) list = new ArrayList<>();
list.add(region);
map.put(zipCode, list);
}
},
(m1, m2) -> m1.putAll(m2)
);
Map<Person, List<Region>> personToRegions =
people.stream().collect(
Collectors.toMap(person -> person,
person -> zipToRegions.get(person.getZip()))
);
我有以下问题:鉴于这些 类,
class Person {
private String zip;
...
public String getZip(){
return zip;
}
}
class Region {
private List<String> zipCodes;
...
public List<String> getZipCodes() {
return zipCodes;
}
}
使用 Java 8 流 API,如何根据 Region
是否包含 Person
的邮政编码来获取 Map<Person, List<Region>>
?换句话说,我如何根据邮政编码属于这些地区的人对地区进行分组?
我在 Java 7 中以老式方式完成了此操作,但现在我必须迁移代码以利用 Java 8 中的新功能。
谢谢,
因佩托
原来的答案用元组做了一个不必要的映射,所以你看到了最终的解决方案。您可以删除映射,直接过滤 regions
列表:
//A Set<Region> is more appropriate, IMO
.stream()
.collect(toMap(p -> p,
p -> regions.stream()
.filter(r -> r.getZipCodes().contains(p.getZip()))
.collect(toSet())));
如果我理解得很好,你可以这样做:
import java.util.AbstractMap.SimpleEntry;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toList;
...
List<Person> persons = ...;
List<Region> regions = ...;
Map<Person, List<Region>> map =
persons.stream()
.map(p -> new SimpleEntry<>(p, regions))
.collect(toMap(SimpleEntry::getKey,
e -> e.getValue().stream()
.filter(r -> r.getZipCodes().contains(e.getKey().getZip()))
.collect(toList())));
从 List<Person>
你得到一个 Stream<Person>
。然后将每个实例映射到包含所有区域的元组 <Person, List<Region>>
。从那里,您使用 toMap
收集器在地图中收集数据,并为每个人构建一个 Region
列表,其中包含该人的邮政编码。
例如,给定输入:
List<Person> persons = Arrays.asList(new Person("A"), new Person("B"), new Person("C"));
List<Region> regions =
Arrays.asList(new Region(Arrays.asList("A", "B")), new Region(Arrays.asList("A")));
它输出:
Person{zip='A'} => [Region{zipCodes=[A, B]}, Region{zipCodes=[A]}]
Person{zip='B'} => [Region{zipCodes=[A, B]}]
Person{zip='C'} => []
另外我猜每个 Region
的 zipCodes
可能是 Set
.
我没有对此代码进行任何测试,但它编译了所以它一定是正确的(:eyeroll:)。
public Map<Person,List<Region>> mapPeopleToRegion(List<Person> people, List<Region> regions){
final Map<Person,List<Region>> personToRegion = new HashMap<>();
people.forEach(person ->
personToRegion.put(
person,regions.stream().filter(
region -> region.getZipCodes().contains(person.getZip()))
.collect(Collectors.toList())));
return personToRegion;
}
我怀疑最简洁的方法是——我对发布的其他答案不太满意——是
persons.stream().collect(Collectors.toMap(
person -> person,
person -> regions.stream()
.filter(region -> region.getZipCodes().contains(person.getZip()))
.collect(Collectors.toList())));
它仍然很丑陋,我认为通过稍微改变你的建模方式会有所改善,但到目前为止我只设法提出以下内容:
public static void main(String[] args) {
Person[] people = {new Person("00001"), new Person("00002"), new Person("00005")};
Region[] regions = {
new Region("Region 1", Arrays.asList("00001", "00002", "00003")),
new Region("Region 2", Arrays.asList("00002", "00003", "00004")),
new Region("Region 3", Arrays.asList("00001", "00002", "00005"))
};
Map<Person, List<Region>> result = Stream.of(regions)
.flatMap(region -> region.getZipCodes().stream()
.map(zip -> new SimpleEntry<>(zip, region)))
.flatMap(entry -> Stream.of(people)
.filter(person -> person.getZip().equals(entry.getKey()))
.map(person -> new SimpleEntry<>(person, entry.getValue())))
.collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, Collectors.toList())));
result.entrySet().forEach(entry -> System.out.printf("[%s]: {%s}\n", entry.getKey(), entry.getValue()));
// Output:
// [Person: 0]: {[name: Region 1, name: Region 3]}
// [Person: 1]: {[name: Region 1, name: Region 2, name: Region 3]}
// [Person: 2]: {[name: Region 3]}
}
有一个包含映射并且可以键入的 ZipCode
class 会让事情变得更干净:
public static void main(String[] args) {
Region r1 = new Region("Region 1");
Region r2 = new Region("Region 2");
Region r3 = new Region("Region 3");
ZipCode zipCode1 = new ZipCode("00001", Arrays.asList(r1, r3));
ZipCode zipCode2 = new ZipCode("00002", Arrays.asList(r1, r2, r3));
ZipCode zipCode3 = new ZipCode("00003", Arrays.asList());
ZipCode zipCode4 = new ZipCode("00004", Arrays.asList());
ZipCode zipCode5 = new ZipCode("00005", Arrays.asList(r3));
Person[] people = {
new Person(zipCode1),
new Person(zipCode2),
new Person(zipCode5)
};
Map<Person, List<Region>> result = Stream.of(people)
.collect(Collectors.toMap(person -> person,
person -> person.getZip().getRegions()));
result.entrySet().forEach(entry -> System.out.printf("[%s]: {%s}\n", entry.getKey(), entry.getValue()));
// Output:
// [Person: 0]: {[name: Region 1, name: Region 3]}
// [Person: 1]: {[name: Region 1, name: Region 2, name: Region 3]}
// [Person: 2]: {[name: Region 3]}
}
其他一些答案包含在列表中进行大量线性搜索的代码。我认为 Java 8 Stream 解决方案应该不会比经典变体慢很多。 因此,这是一个在不牺牲太多性能的情况下利用 Streams 的解决方案。
List<Person> people = ...
List<Region> regions = ...
Map<String, List<Region>> zipToRegions =
regions.stream().collect(
() -> new HashMap<>(),
(map, region) -> {
for(String zipCode: region.getZipCodes()) {
List<Region> list = map.get(zipCode);
if(list == null) list = new ArrayList<>();
list.add(region);
map.put(zipCode, list);
}
},
(m1, m2) -> m1.putAll(m2)
);
Map<Person, List<Region>> personToRegions =
people.stream().collect(
Collectors.toMap(person -> person,
person -> zipToRegions.get(person.getZip()))
);