Suppose I have a Collection
, and a Predicate
that matches elements I'd like to remove from the Collection
. But I don't just want to discard them, I want to move the matched elements into a new collection. I'd do something like this in Java 7:
List<E> removed = new LinkedList<>();
for (Iterator<E> i = data.iterator(); i.hasNext();) {
E e = i.next();
if (predicate.test(e)) {
removed.add(e);
i.remove();
}
}
I'm curious if there's a streams / Java 8 way to do it. Collections.removeIf()
unfortunately simply returns a boolean
(not even a count of the number of removed elements? Too bad.) I envision something like this (though of course .removeAndYield(Predicate)
doesn't exist):
List<E> removed = data.removeAndYield(predicate).collect(Collectors.toList());
Note: this question was inspired by a similar question; this question is about the more general case of getting a stream over the items removed from a collection. As pointed out in the linked question, the imperative solution may be more readable, but I'm curious if this is even possible with streams.
Edit: Clearly we can split the task into two separate steps, and assuming the appropriate data structures it will be efficient. The question is can this be done on arbitrary collections (which may not have efficient .contains()
etc.).
Using Streams, we can apply lambda functions known as Predicates. To read more about Streams and Predicates, we have another article here. Internally, removeIf uses an Iterator to iterate over the list and match the elements using the predicate. We can now remove any matching elements from the list.
An element can be removed from a Collection using the Iterator method remove(). This method removes the current element in the Collection. If the remove() method is not preceded by the next() method, then the exception IllegalStateException is thrown.
clear() deletes every element from the collection and removeAll() one only removes the elements matching those from another Collection.
If you don't mind, let me bend your requirements a little bit. :-)
One characteristic of the desired result is that the matching elements should end up in one collection, and the non-matching elements should end up in a different collection. In the pre-Java-8 mutative world, the easiest way to think about getting a collection of non-matching elements is to remove the matching elements from the original collection.
But is removal -- modification of the original list -- an intrinsic part of the requirement?
If it isn't, then the result can be achieved via a simple partitioning operation:
Map<Boolean, List<E>> map = data.stream().collect(partitioningBy(predicate));
The result map is essentially two lists, which contain the matching (key = true) and non-matching (key = false) elements.
The advantage is that this technique can be done in one pass and in parallel if necessary. Of course, this creates a duplicate list of non-matching elements compared to removing the matches from the original, but this is the price to pay for immutability. The tradeoffs might be worth it.
I'd keep it simple:
Set<E> removed = set.stream()
.filter(predicate)
.collect(Collectors.toSet());
set.removeAll(removed);
If you want a functional way to do this, you could write your own method.
static <E> Set<E> removeIf(Collection<? extends E> collection, Predicate<? super E> predicate) {
Set<E> removed = new HashSet<>();
for (Iterator<? extends E> i = collection.iterator(); i.hasNext();) {
E e = i.next();
if (predicate.test(e)) {
removed.add(e);
i.remove();
}
}
return removed;
}
This could be used to remove all odd numbers from a List
.
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6));
Set<Integer> removed = removeIf(set, i -> i % 2 != 0);
System.out.println(set);
System.out.println(removed);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With