[英]How to remove multiple elements from Set/Map AND knowing which ones were removed?
I have a method that has to remove any element listed in a (small) Set<K> keysToRemove
from some (potentially large) Map<K,V> from
. 我有一个方法,必须删除(小)
Set<K> keysToRemove
列出的任何元素从一些(可能很大) Map<K,V> from
。 But removeAll()
doesn't do, as I need to return all keys that were actually removed, since the map might or might not contain keys that require removal. 但是
removeAll()
不能这样做,因为我需要返回实际删除的所有键,因为map可能包含也可能不包含需要删除的键。
Old-school code is straight forward: 老派代码很简单:
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;
}
The same, written using streams: 同样,使用流编写:
Set<K> fromKeys = from.keySet();
return keysToRemove.stream()
.filter(fromKeys::contains)
.map(k -> {
fromKeys.remove(k);
return k;
})
.collect(Collectors.toSet());
I find that a bit more concise, but I also find that lambda too clunky. 我发现它更简洁,但我也发现lambda太笨重了。
Any suggestions how to achieve the same result in less clumsy ways? 有什么建议如何以不太笨拙的方式达到相同的结果?
The “old-school code” should rather be 应该是“老派代码”
public Set<K> removeEntries(Map<K, ?> from) {
Set<K> fromKeys = from.keySet(), removedKeys = new HashSet<>(keysToRemove);
removedKeys.retainAll(fromKeys);
fromKeys.removeAll(removedKeys);
return removedKeys;
}
Since you said that keysToRemove
is rather small, the copying overhead likely doesn't matter. 既然你说
keysToRemove
相当小,那么复制开销可能并不重要。 Otherwise, use the loop, but don't do the hash lookup twice: 否则,请使用循环,但不要执行两次哈希查找:
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;
}
You can express the same logic as a stream as 您可以表示与流相同的逻辑
public Set<K> removeEntries(Map<K, ?> from) {
return keysToRemove.stream()
.filter(from.keySet()::remove)
.collect(Collectors.toSet());
}
but since this is a stateful filter, it is highly discouraged. 但由于这是一个有状态的过滤器,因此非常气馁。 A cleaner variant would be
一个更清洁的变体将是
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;
}
and if you want to maximize the “streamy” usage, you can replace from.keySet().removeAll(result);
如果你想最大化“流”用法,你可以替换
from.keySet().removeAll(result);
with from.keySet().removeIf(result::contains)
, which is quiet expensive, as it is iterating over the larger map, or with result.forEach(from.keySet()::remove)
, which doesn't have that disadvantage, but still, isn't more readable than removeAll
. with
from.keySet().removeIf(result::contains)
,这是安静的,因为它迭代更大的地图,或者使用result.forEach(from.keySet()::remove)
,它没有这个缺点,但仍然不如removeAll
可读。
All in all, the “old-school code” is much better than that. 总而言之,“老派代码”要好得多。
More concise solution, but still with unwanted side effect in the filter
call: 更简洁的解决方案,但在
filter
调用中仍有不必要的副作用 :
Set<K> removedKeys =
keysToRemove.stream()
.filter(fromKeys::remove)
.collect(Collectors.toSet());
Set.remove
already returns true
if the set
contained the specified element. 如果
set
包含指定的元素,则Set.remove
已经返回true
。
PS In the end, I would probably stick with the "old-school code". PS最后,我可能会坚持使用“老派代码”。
I wouldn't use Streams for this. 我不会为此使用Streams。 I would take advantage of retainAll :
我会利用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;
}
You can use the stream and the removeAll 您可以使用流和removeAll
Set<K> fromKeys = from.keySet();
Set<K> removedKeys = keysToRemove.stream()
.filter(fromKeys::contains)
.collect(Collectors.toSet());
fromKeys.removeAll(removedKeys);
return removedKeys;
You can use this: 你可以用这个:
Set<K> removedKeys = keysToRemove.stream()
.filter(from::containsKey)
.collect(Collectors.toSet());
removedKeys.forEach(from::remove);
It's similar to Oleksandr 's answer, but avoiding the side effect. 它类似于Oleksandr的回答,但避免了副作用。 But I would stick with that answer, if you are looking for performance.
但如果你正在寻找表现,我会坚持这个答案。
Alternatively you could use Stream.peek()
for the remove, but be careful with other side effects (see the comments). 或者,您可以使用
Stream.peek()
进行删除,但要注意其他副作用(请参阅注释)。 So I would not recommend that. 所以我不建议这样做。
Set<K> removedKeys = keysToRemove.stream()
.filter(from::containsKey)
.peek(from::remove)
.collect(Collectors.toSet());
To add another variant to the approaches, one could also partition the keys and return the required Set
as: 要为方法添加另一个变体,还可以对键进行分区并将所需的
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);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.