简体   繁体   English

使用stream()collect()groupingBy()和reduce()的多级映射

[英]Multi-level map using stream() collect() groupingBy() and reducing()

I managed to write Collector.groupingBy(Function, groupingBy(classifier, toList()) 我设法写了Collector.groupingBy(Function, groupingBy(classifier, toList())

Map<String, Map<Person.Sex, List<Person>>> groupByYearThenSex = personList.stream().collect(
    groupingBy(p ->{
            if(p.getYear() > 2015) return "New members";
            else if(p.getYear() < 2009) return "Senior members";
            else return "Normal members";
        },
        groupingBy(Person::getSex)
    )
);
groupByYearThenSex.forEach((String y, Map<Person.Sex, List<Person>> m) ->{
    System.out.println("\n" + y);
    m.forEach((Person.Sex s, List<Person> p) -> {
        System.out.println("   " + s);
        p.forEach((Person prsn) -> {
            System.out.println("      " + prsn.toString());
        });

    });
});

The corresponding result is: 相应的结果是:

New members

       MALE
           Person{name: Gawel, age: 23, sex: MALE}

Normal members

       MALE
          Person{name: Patryk, age: 34, sex: MALE}
          Person{name: Pawel, age: 21, sex: MALE}
          Person{name: Bolek, age: 12, sex: MALE}
          Person{name: Lolek, age: 12, sex: MALE}
      FEMALE
          Person{name: Jola, age: 70, sex: FEMALE}
          Person{name: Ala, age: 25, sex: FEMALE}

Senior members

      FEMALE
          Person{name: Iwona, age: 34, sex: FEMALE}
          Person{name: Oliwia, age: 17, sex: FEMALE}

Now I'm trying to produce the multi-level map that lists only the oldest members as follows: 现在我正在尝试生成多级映射,其中仅列出最旧的成员,如下所示:

New members

       MALE
           Person{name: Gawel, age: 23, sex: MALE}

Normal members

       MALE
          Person{name: Patryk, age: 34, sex: MALE}
      FEMALE
          Person{name: Jola, age: 70, sex: FEMALE}

Senior members

      FEMALE
          Person{name: Iwona, age: 34, sex: FEMALE}

The code below does not work. 下面的代码不起作用。 I get "inference variable RR has incompatible bounds error, which is I think Function<R,RR> => collectingAndThen(reducing(), Optional) . Is there any way I can reduce the stream of Persons to get the oldest ones and get the output above? 我得到“推理变量RR有不兼容的边界错误,我认为Function<R,RR> => collectingAndThen(reducing(), Optional) 。有没有办法我可以减少人流来获取最老的并得到上面的输出?

Map<String, Map<Person.Sex, List<Person>>> groupByYearThenSexOldest = personList.stream().collect(
    groupingBy(p ->{
            if(p.getYear() > 2015) return "New members";
            else if(p.getYear() < 2009) return "Senior members";
            else return "Normal members";
        },
        groupingBy(Person::getSex, collectingAndThen(
            reducing((p1, p2) -> p1.getAge() > p2.getAge() ? p1 : p2),
            Optional::get)
        )
    )
);

The problem is the type parameters of your map. 问题是地图的类型参数。 It should give you a Map<String, Map<Person.Sex, Person>> instead of a Map<String, Map<Person.Sex, List<Person>>> since you are reducing the list. 它应该为您提供Map<String, Map<Person.Sex, Person>>而不是Map<String, Map<Person.Sex, List<Person>>>因为您正在减少列表。

But you can combine the second groupingBy collector with maxBy and collectingAndThen : 但是,你可以结合第二groupingBy集电极与maxBycollectingAndThen

groupingBy(Person::getSex, collectingAndThen(maxBy(Comparator.comparingInt(Person::getAge)), Optional::get)

Sometimes if you need to group by multiple fields, you can use a list that groups the fields (assuming they have a proper equals and hashcode methods). 有时,如果您需要按多个字段进行分组,则可以使用对字段进行分组的列表(假设它们具有正确的equals和hashcode方法)。 For instance: 例如:

Function<Person, String> groupingYear = p -> {
     if (p.getYear() > 2015) return "New members";
     else if (p.getYear() < 2009) return "Senior members";
     else return "Normal members";
};

Map<Object, Optional<Person>> map2 = 
    personList.stream()
              .collect(groupingBy(p -> Arrays.asList(groupingYear.apply(p), p.getSex()), maxBy(Comparator.comparingInt(Person::getAge))));

which gives: 这使:

[Normal members, MALE] => Optional[Person{name='Patryk', age=34, sex=MALE}]
[Senior members, FEMALE] => Optional[Person{name='Iwona', age=34, sex=FEMALE}]
[Normal members, FEMALE] => Optional[Person{name='Jola', age=70, sex=FEMALE}]
[New members, MALE] => Optional[Person{name='Gawel', age=23, sex=MALE}]

But then you may start by using the toMap collector and compare the ages in the merge function you supply when 2 values are grouped together: 但是,您可以从使用toMap收集器开始,并比较在将2个值组合在一起时提供的合并函数中的年龄:

Map<Object, Person> map =
    personList.stream()
              .collect(toMap(p -> Arrays.asList(groupingYear.apply(p), p.getSex()),
                             p -> p,
                             (p1, p2) -> {
                                 if(p1.getAge() > p2.getAge()) return p1;
                                 else return p2;
                             },
                             HashMap::new));

You have an Object as key in your Map (the real type is awful and long to write), but what you can do is create a dedicated class MemberStatusAndSex that combines both attributes (instead of a List - don't forget to implement equals and hashcode for this class), and get a Map<MemberStatusAndSex, Person> at the end. 你的Map有一个Object作为键(真正的类型很糟糕且写MemberStatusAndSex长),但你可以做的是创建一个专用的类MemberStatusAndSex ,它结合了两个属性(而不是List - 不要忘记实现equals和这个类的哈希码),并在最后得到一个Map<MemberStatusAndSex, Person>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM