Pluck from a Java Collection using stream

62 Views Asked by At

I want to know if there is an impementation of "plucking" elements from a Collection implementation based from a given Predicate, that is to remove said elements after retrieving. To visualize what I mean, consider this use case:

Set<Parent> parentSet = parentDao.getParents();
Set<Long> parentIdSet = parentSet.stream().map(Parent::getId).collect(Collectors.toSet())
Set<Child> childrenSet = childDao.getChildByParentIds(parentIdSet);

for (Parent parent : parentSet) {
   Set<Child> childrenOfThisParent = childrenSet
      .stream()
      .pluck((Child c) -> Objects.equals(parent.getId(), c.getParentId()))
      .collect(Collectors.toSet());

   parent.setChildren(childrenOfThisParent);
}

// At this line, childSet should be empty.
assert(childrenSet.isEmpty());

What I want is every iteration, after Set<Child> was filtered by its parentId and initialized to childrenOfThisParent, I want them to be removed from the childSet and get the size() of childSet reduced. By effect, the searching from childSet should be faster the next iteration. Although at this point, I have to benchmark to know if the faster search time will be significant.

1

There are 1 best solutions below

1
Sweeper On

Streams just don't support "in place" operations like modifying an existing collection. If you want to do this with streams, you would need to produce new collections. You can use the partitioningBy collector to separate the children set into "children of this parent" and "not children of this parent". Both of these are new collections, stored in a new Map.

for (Parent parent : parentSet) {
    var partitions =
        childrenSet.stream()
            .collect(Collectors.partitioningBy(
                c -> Objects.equals(parent.getId(), c.getParentId()),
                Collectors.toSet()
            ));
    childrenSet = partitions.get(false);

    parent.setChildren(partitions.get(true));
}

This allocates a whole lot of new collections, so probably isn't worth it. You can consider doing the whole operation of "grouping children into their parents" in one stream, using groupingBy:

var childrenByParentId = childrenSet.stream().collect(Collectors.groupingBy(
    Child::getParentId,
    Collectors.toSet()
));
for (var parent: parentSet) {
    parent.setChildren(childrenByParentId.get(parent.getId()));
}