如何在针对每个键删除一些整体后从一个地图收集值并使用流生成另一个地图 (Java 8)

How to collect values from one map after removing some entires against each key and produce another map using streams (Java 8)

我有一张地图。在映射中对应于每个键有一个包含 50 个对象的列表。基本上我使用 Streams 制作了那张地图。我所做的是,我使用了流的 groupingBy 函数。这是我采取的步骤。

Map<Long, List<Learner>> learnersMap = learnersDataList.stream()
            .collect(Collectors.groupingBy(Learner::getLearnerEnrollmentId));

然后我使用下面的代码将那些计数小于 50 或等于 50 的记录与每个键分开

Map<Long, List<Learner>> answersLessThan50CountLearnersMap = 
             learnersMap.entrySet()
            .stream()
            .filter(p -> p.getValue().stream().count() < 50)
            .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));

Map<Long, List<Learner>> validLearnersMap = 
            learnersMap.entrySet()
            .stream()
            .filter(p -> p.getValue().stream().count() == 50)
            .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));

现在我想要 answersLessThan50CountLearnersMap 中的那个,我生成了一个地图,其中仅包含 List 中的一条记录。我的意思是假设 Map 看起来像这样

1-->[Learner, Learner, Learner, ....]
2-->[Learner, Learner, Learner, ....]
3-->[Learner, Learner, Learner, ....]

那我想制作这样的地图

1--> Learner
2--> Learner
3--> Learner

其实List里面的所有记录都是多余的。就像所有 50 个学习者反对键 1 的名字一样。只有一个记录是不同的,但我不需要它,因为我必须通过从 Learner Object 获取学习者记录来发送电子邮件,并且不需要那个不同的记录。键 23 相同。就像我可以说的

entrySet.getValue.stream.findFirst() or entrySet.getValue.stream.limit(1)

对于 entrySet.In 中的每个条目,换句话说,我想从 answersLessThan50CountLearnersMap

生成像 Map<Long, Learner> 这样的地图

我可以通过对其应用流操作来实现吗?请帮忙

谢谢

但我不知道它有多好,因为我是流媒体的新手。这是我所做的。

Map<Long, Learner> invalidLearnersMap = 
            answersLessThan50CountLearnersMap.entrySet()
            .stream()
            .collect(Collectors.toMap(p -> p.getKey(), p ->  {
                Optional<Learner> learner = p.getValue().stream().findFirst();
                if (learner.isPresent()) {
                    Learner learner = learner.get();
                    return learner;
                } else {
                    return null;
                }

            }));

我也可以在我得到 lessThan50CountMap 的地方做到这一点。无论如何,谢谢。

请注意,并非所有事情都因为您使用 Streams 而变得更好。

首先映入我眼帘的是p.getValue().stream().count(),而p.getValue()实际上是returns一个List。所以这里p.getValue().size()显然更合理

此外,如果您只需要 List 的第一个元素,调用 list.get(0) 是一种行之有效的方法,比 list.stream().findFirst() 更简单。如果列表的大小可能为零,您必须检查一下,但在您的一种情况下,它已经被证明是非零的(因为 size 等于 50 Predicate).

您可以在定义 Map 应包含的值的地方进行操作:

Map<Long, Learner> answersLessThan50CountLearnersMap = 
     learnersMap.entrySet().stream()
    .filter(p -> p.getValue().size() < 50)
    .collect(Collectors.toMap(p -> p.getKey(),
                              p -> p.getValue().isEmpty()? null: p.getValue().get(0)));
Map<Long, Learner> validLearnersMap = 
    learnersMap.entrySet().stream()
    .filter(p -> p.getValue().size() == 50)// implies non-empty list
    .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue().get(0)));

如果你想为了效率而放弃紧凑,你可能会认为你只对每个第一个元素感兴趣,就在你创建第一个地图时,换句话说,你只需要第一个元素和计数而不是完全填充 List。所以我们需要一个替代容器,只容纳第一个元素和计数,适合收集数据:

class LearnerAndCount {
    Learner first;
    int count;
    LearnerAndCount add(Learner l) {
        if(first==null) first=l;
        count++;
        return this;
    }
    LearnerAndCount merge(LearnerAndCount lac) {
        if(first==null) first=lac.first;
        count+=lac.count;
        return this;
    }
}

收集信息

Map<Long, LearnerAndCount> learnersMap = learnersDataList.stream().collect(
  Collectors.groupingBy(Learner::getLearnerEnrollmentId,
    Collector.of(LearnerAndCount::new, LearnerAndCount::add, LearnerAndCount::merge)));

并用它来创建两个地图

Map<Long, Learner> answersLessThan50CountLearnersMap = 
     learnersMap.entrySet().stream()
    .filter(p -> p.getValue().count < 50)
    .collect(Collectors.toMap(Map.Entry::getKey, p -> p.getValue().first));
Map<Long, Learner> validLearnersMap = 
     learnersMap.entrySet().stream()
    .filter(p -> p.getValue().count == 50)
    .collect(Collectors.toMap(Map.Entry::getKey, p -> p.getValue().first));