How to group elements of a List by elements of another in Java 8

前端 未结 5 1403
伪装坚强ぢ
伪装坚强ぢ 2021-02-16 00:09

I have the following problem: Given these classes,

class Person {
    private String zip;
    ...
    public String getZip(){
        return zip;
    }
}

class          


        
相关标签:
5条回答
  • 2021-02-16 00:24

    Some of the other answers contain code that does a lot of linear searching through lists. I think the Java 8 Stream solution should not be much slower than the classical variant. So here is a solution that takes advantage of Streams without sacrificing much performance.

    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()))
      );
    
    0 讨论(0)
  • 2021-02-16 00:30

    I have not done any testing of this code, but it compiles so it must be right (: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;
    }
    
    0 讨论(0)
  • 2021-02-16 00:46

    I suspect the cleanest way to do this -- I'm not quite happy with the other answers posted -- would be

     persons.stream().collect(Collectors.toMap(
        person -> person,
        person -> regions.stream()
           .filter(region -> region.getZipCodes().contains(person.getZip()))
           .collect(Collectors.toList())));
    
    0 讨论(0)
  • 2021-02-16 00:46

    The original answer does an unnecessary mapping with tuples, so you see there the final solution. You could remove the mapping, and simply filter directly the regions list:

    //A Set<Region> is more appropriate, IMO
    .stream()
    .collect(toMap(p -> p, 
                   p -> regions.stream()
                               .filter(r -> r.getZipCodes().contains(p.getZip()))
                               .collect(toSet())));
    


    If I understand well, you could do something like this:

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

    From the List<Person> you get a Stream<Person>. Then you map each instance to a tuple <Person, List<Region>> that contains all the regions. From there, you collect the data in a map with the toMap collector and, for each person, you build a List of Region that contains the zip code of that person.

    For example, given the input:

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

    It outputs:

    Person{zip='A'} => [Region{zipCodes=[A, B]}, Region{zipCodes=[A]}]
    Person{zip='B'} => [Region{zipCodes=[A, B]}]
    Person{zip='C'} => []
    

    Also I guess the zipCodes for each Region could be a Set.

    0 讨论(0)
  • 2021-02-16 00:46

    It's still pretty ugly, and I think it would be improved by changing how you model things a bit, but I've only managed to come up with the following so far:

    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]}
    }
    

    Having a ZipCode class that contained the mapping and could be keyed on would make things cleaner:

    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]}
    }
    
    0 讨论(0)
提交回复
热议问题