如何从“设置/地图”中删除多个元素并知道删除了哪些元素?

时间:2019-06-13 14:23:24

标签: java lambda java-stream

我有一种方法必须从某些(可能很大)Set<K> keysToRemove中删除(小)Map<K,V> from中列出的任何元素。但是removeAll()不能这样做,因为我需要返回实际上已删除的所有键,因为映射可能包含或可能不包含需要删除的键。

老式代码很简单:

public Set<K> removeEntries(Map<K, V> from) {
    Set<K> fromKeys = from.keySet();
    Set<K> removedKeys = new HashSet<>();
    for (K keyToRemove : keysToRemove) {
        if (fromKeys.contains(keyToRemove)) {
            fromKeys.remove(keyToRemove);
            removedKeys.add(keyToRemove);
        }
    }
    return removedKeys;
}

相同,使用流编写:

Set<K> fromKeys = from.keySet();
return keysToRemove.stream()
        .filter(fromKeys::contains)
        .map(k -> {
            fromKeys.remove(k);
            return k;
        })
        .collect(Collectors.toSet());

我发现这更加简洁,但是我也发现lambda太笨拙了。

有人建议如何以较少笨拙的方式获得相同的结果吗?

6 个答案:

答案 0 :(得分:23)

“老式代码”应该是

UIImagePickerControllerDelegate

由于您说public Set<K> removeEntries(Map<K, ?> from) { Set<K> fromKeys = from.keySet(), removedKeys = new HashSet<>(keysToRemove); removedKeys.retainAll(fromKeys); fromKeys.removeAll(removedKeys); return removedKeys; } 很小,因此复制开销可能无关紧要。否则,请使用循环,但不要进行两次哈希查找:

keysToRemove

您可以表达与流相同的逻辑

public Set<K> removeEntries(Map<K, ?> from) {
    Set<K> fromKeys = from.keySet();
    Set<K> removedKeys = new HashSet<>();
    for(K keyToRemove : keysToRemove)
        if(fromKeys.remove(keyToRemove)) removedKeys.add(keyToRemove);
    return removedKeys;
}

但是由于这是一个有状态的过滤器,因此不建议使用。一个更干净的变体是

public Set<K> removeEntries(Map<K, ?> from) {
    return keysToRemove.stream()
        .filter(from.keySet()::remove)
        .collect(Collectors.toSet());
}

,如果要最大程度地使用“流式”方法,可以用public Set<K> removeEntries(Map<K, ?> from) { Set<K> result = keysToRemove.stream() .filter(from.keySet()::contains) .collect(Collectors.toSet()); from.keySet().removeAll(result); return result; } 代替from.keySet().removeAll(result);,因为在较大的地图上迭代时,from.keySet().removeIf(result::contains)昂贵,或者{{1} },虽然没有这个缺点,但仍然比result.forEach(from.keySet()::remove)更具可读性。

总而言之,“老式代码”比这要好得多。

答案 1 :(得分:13)

更简洁的解决方案,但在filter调用中仍然具有不必要的副作用

Set<K> removedKeys =
    keysToRemove.stream()
                .filter(fromKeys::remove)
                .collect(Collectors.toSet());
如果true包含指定的元素,

Set.remove已经返回set

最后,我可能会坚持使用“老式代码”。

答案 2 :(得分:5)

我不会为此使用Streams。我会利用retainAll

public Set<K> removeEntries(Map<K, V> from) {
    Set<K> matchingKeys = new HashSet<>(from.keySet());
    matchingKeys.retainAll(keysToRemove);

    from.keySet().removeAll(matchingKeys);

    return matchingKeys;
}

答案 3 :(得分:4)

您可以使用流和removeAll

Set<K> fromKeys = from.keySet();
Set<K> removedKeys = keysToRemove.stream()
    .filter(fromKeys::contains)
    .collect(Collectors.toSet());
fromKeys.removeAll(removedKeys);
return removedKeys;

答案 4 :(得分:4)

您可以使用此:

Set<K> removedKeys = keysToRemove.stream()
        .filter(from::containsKey)
        .collect(Collectors.toSet());
removedKeys.forEach(from::remove);

类似于 Oleksandr 的回答,但避免了副作用。但是,如果您要寻找性能,我会坚持使用该答案。

或者,您可以使用Stream.peek()进行去除,但要注意其他副作用(请参见注释)。所以我不建议这样做。

Set<K> removedKeys = keysToRemove.stream()
        .filter(from::containsKey)
        .peek(from::remove)
        .collect(Collectors.toSet());

答案 5 :(得分:3)

要为这些方法添加另一种变体,还可以对键进行分区,并将所需的Set返回为:

public Set<K> removeEntries(Map<K, ?> from) {
    Map<Boolean, Set<K>> partitioned = keysToRemove.stream()
            .collect(Collectors.partitioningBy(k -> from.keySet().remove(k),
                    Collectors.toSet()));
    return partitioned.get(Boolean.TRUE);
}