java - ¿Cómo eliminar varios elementos de Set/Map Y saber cuáles fueron eliminados?
lambda java-8 (6)
Tengo un método que tiene que eliminar cualquier elemento listado en un (pequeño)
Set<K> keysToRemove
para
Set<K> keysToRemove
de algún
Map<K,V> from
(potencialmente grande)
Map<K,V> from
.
Pero
removeAll()
no funciona, ya que necesito devolver todas las claves que realmente se eliminaron, ya que el mapa puede o no contener claves que requieren ser eliminadas.
El código de la vieja escuela es sencillo:
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;
}
Lo mismo, escrito usando streams:
Set<K> fromKeys = from.keySet();
return keysToRemove.stream()
.filter(fromKeys::contains)
.map(k -> {
fromKeys.remove(k);
return k;
})
.collect(Collectors.toSet());
Lo encuentro un poco más conciso, pero también encuentro que la lambda es demasiado torpe.
¿Alguna sugerencia de cómo lograr el mismo resultado en formas menos torpes?
El "código de la vieja escuela" debería ser
public Set<K> removeEntries(Map<K, ?> from) {
Set<K> fromKeys = from.keySet(), removedKeys = new HashSet<>(keysToRemove);
removedKeys.retainAll(fromKeys);
fromKeys.removeAll(removedKeys);
return removedKeys;
}
Ya que dijo que
keysToRemove
es bastante pequeño, es probable que la sobrecarga de copia no importe.
De lo contrario, usa el bucle, pero no hagas la búsqueda de hash dos veces:
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;
}
Puedes expresar la misma lógica que una secuencia como
public Set<K> removeEntries(Map<K, ?> from) {
return keysToRemove.stream()
.filter(from.keySet()::remove)
.collect(Collectors.toSet());
}
pero dado que este es un filtro de estado, es altamente desanimado. Una variante más limpia sería
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;
}
y si desea maximizar el uso "continuo", puede reemplazar
from.keySet().removeAll(result);
con
from.keySet().removeIf(result::contains)
, que es
from.keySet().removeIf(result::contains)
caro, ya que está iterando sobre el mapa más grande, o con
result.forEach(from.keySet()::remove)
, que no tiene esa desventaja, pero aún así, no es más legible que
removeAll
.
Con todo, el "código de la vieja escuela" es mucho mejor que eso.
No usaría Streams para esto. Me aprovecharía de 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;
}
Para agregar otra variante a los enfoques, también se podrían dividir las claves y devolver el
Set
requerido como:
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);
}
Puedes usar el stream y el removeAll
Set<K> fromKeys = from.keySet();
Set<K> removedKeys = keysToRemove.stream()
.filter(fromKeys::contains)
.collect(Collectors.toSet());
fromKeys.removeAll(removedKeys);
return removedKeys;
Puedes usar esto:
Set<K> removedKeys = keysToRemove.stream()
.filter(from::containsKey)
.collect(Collectors.toSet());
removedKeys.forEach(from::remove);
Es similar a la respuesta de Oleksandr , pero evitando el efecto secundario. Pero me quedo con esa respuesta, si buscas rendimiento.
Alternativamente, puede usar
Stream.peek()
para eliminar, pero tenga cuidado con otros efectos secundarios (vea los comentarios).
Así que no lo recomendaría.
Set<K> removedKeys = keysToRemove.stream()
.filter(from::containsKey)
.peek(from::remove)
.collect(Collectors.toSet());
Solución más concisa, pero aún con
efectos secundarios
no deseados en la llamada de
filter
:
Set<K> removedKeys =
keysToRemove.stream()
.filter(fromKeys::remove)
.collect(Collectors.toSet());
Set.remove
ya devuelve
true
si el
set
contiene el elemento especificado.
PS Al final, probablemente me quedaría con el "código de la vieja escuela".