根据toMap集合中的值过滤流

时间:2015-12-13 16:53:42

标签: java java-8 java-stream

我的情况是我在开发项目中有Player个对象,而任务只是测量距离并返回低于某个阈值的结果。当然,我想以最简洁的方式使用流。

目前,我有一个映射流的解决方案,然后通过迭代器进行过滤:

Stream<Player> str = /* source of my player stream I'm filtering */;
Map<Player, Double> dists = str.collect(Collectors.toMap(...)); //mapping function
Iterator<Map.Entry<Player, Double>> itr = map.entrySet().iterator();
while (itr.hasNext()) {
    if (itr.next().getValue() <= radiusSquared) {
        itr.remove();
    }
}

然而,我想要实现的是在对流进行操作时执行此过滤的事情,其中​​说“如果此谓词失败,则不收集&#34;”以尝试和保存第二次迭代。另外,我不想两次计算距离,所以通过映射函数进行过滤,然后重新映射不是一个合理的解决方案。

我想到的唯一真正可行的解决方案是映射到Pair<A, B>,但如果对某种形式的二进制流有原生支持,那就更好了。< / p>

java的流API中是否有本机支持?

3 个答案:

答案 0 :(得分:5)

之后过滤Map并不像看起来那么糟糕,请记住,迭代Map并不意味着执行查找(例如散列)的成本相同。

但不是

Iterator<Map.Entry<Player, Double>> itr = map.entrySet().iterator();
while (itr.hasNext()) {
    if (itr.next().getValue() <= radiusSquared) {
        itr.remove();
    }
}

你可以简单地使用

map.values().removeIf(value -> value <= radiusSquared);

即使您坚持将其作为collect操作的一部分,也可以将其作为后缀操作:

Map<Player, Double> dists = str.collect(
    Collectors.collectingAndThen(Collectors.toMap(p->p, p->calculate(p)),
    map -> { map.values().removeIf(value -> value <= radiusSquared); return map; }));

可以首先避免put个不需要的条目,但这意味着手动回溯现有toMap收集器所做的事情:

Map<Player, Double> dists = str.collect(
    HashMap::new,
    (m, p) -> { double value=calculate(p); if(value > radiusSquared) m.put(p, value); },
    Map::putAll);

答案 1 :(得分:2)

请注意,旧式迭代器循环可以使用Collection.removeIf在Java-8中重写:

map.values().removeIf(dist -> dist <= radiusSquared);

所以它实际上并不那么糟糕。不要忘记keySet()values()是可修改的。

如果你想使用单一管道解决这个问题(例如,大部分条目都要删除),那么对你来说就是坏消息。似乎当前的Stream API不允许您在没有明确使用具有对语义的类的情况下执行此操作。创建Map.Entry实例非常自然,尽管现有选项是AbstractMap.SimpleEntry,其名称相当长且令人不快:

str.map(player -> new AbstractMap.SimpleEntry(player, getDistance(player)))
   .filter(entry -> entry.getValue() > radiusSquared)
   .toMap(Entry::getKey, Entry::getValue);

请注意,在Java-9中可能会有Map.entry()静态方法,因此您可以使用Map.entry(player, getDistance(player))。有关详细信息,请参阅JEP-269

像往常一样,我的StreamEx库有一些语法糖来以更清洁的方式解决这个问题:

StreamEx.of(str).mapToEntry(player -> getDistance(player))
                .filterValues(dist -> dist > radiusSquared)
                .toMap();

关于评论:是的,toMap()收集器使用逐个插入,但不要担心:要映射的批量插入很少提高速度。您甚至无法预先确定哈希表的大小(如果您的地图是基于哈希的),因为您不太了解要插入的元素。您可能希望使用相同的密钥插入数百万个对象:为百万条目分配哈希表只是为了发现插入后只有一个条目太浪费。

答案 2 :(得分:0)

如果您的目标只是进行一次迭代并且只计算一次距离,那么您可以这样做:

Stream<Player> str = /* source of my player stream I'm filtering */;
Map<Player, Double> dists = new HashMap<>();
str.forEach(p -> {
    double distance = /* calculate distance */;
    if (distance <= radiusSquared) {
        dists.put(p, distance);
    }
});

不再有收藏家,但这是否重要?