问题
trying to filter
a stream against data within a different list:
It works, but I use a for
loop in the middle of the stream. I cannot find any information of how to convert the for
loop to a stream.
I could just .stream()
the selction.getItems()
than .forEach()
and have a new .stream()
of DATA.accounts, but that is poor code as it would have to restream on every .forEach.
y=1;
DATA.accounts.stream()
.flatMap(estimate -> estimate.getElements().stream())
.filter( ele-> {
// different list;
for (Element element:selection.getItems()){
if (element.getId()==ele.getId()){
return true;
}
}
return false;
})
.forEach(element -> {
element.setDateSchedualed(selectedDate);
element.setOrder(y);
y++;
});
回答1:
You can express the filter
as
.filter(ele -> selection.getItems().stream()
.anyMatch(element -> element.getId()==ele.getId())
The fact that this “would have to restream” shouldn’t bother you more than the fact that the original code will loop for every element. You have created an operation with O(n×m)
time complexity in either case. This is acceptable if you can surely predict that one of these lists will always be very small.
Otherwise, there is no way around preparing this operation by storing the id values in a structure with a fast (O(1)
in the best case) lookup. I.e.
Set<IdType> id = selection.getItems().stream()
.map(element -> element.getId())
.collect(Collectors.toSet());
…
.filter(ele -> id.contains(ele.getId())
Besides that, your forEach
approach incrementing the y
variable clearly is an anti-pattern and it doesn’t even compile, when y
is a local variable. And if y
is a field, it would make this code even worse. Here, it’s much cleaner to accept a temporary storage into a List
:
Set<IdType> id = selection.getItems().stream().map(element -> element.getId());
List<ElementType> list = DATA.accounts.stream()
.flatMap(estimate -> estimate.getElements().stream())
.filter(ele -> id.contains(ele.getId())
.collect(Collectors.toList());
IntStream.range(0, list.size())
.forEach(ix -> {
ElementType element = list.get(ix);
element.setDateSchedualed(selectedDate);
element.setOrder(ix+1);
});
回答2:
I think what you really need is:
list1.removeAll(list2);
No streams involved though.
回答3:
Put the other list's IDs in a Set selectedIds
, then filter based on ele-> selectedIds.contains(ele.getId())
.
That will give you (amortized) linear time complexity.
Since you need to check presence among all elements in selected
for each item in the stream, I don't expect there will be any straightforward method using only streams (because you cannot really stream the selected
collection for this task).
回答4:
I think there is actually nothing wrong with using a for-each loop if you want to search for the id in linear time, because for example if your items list was an ArrayList
and you used its contains
method for filtering, it would actually also just loop over the elements. You could write a general contains function like:
public static <E1, E2> boolean contains(Collection<E1> collection, E2 e2, BiPredicate<E1, E2> predicate){
for (E1 e1 : collection){
if (predicate.test(e1, e2)){
return true;
}
}
return false;
}
and replace your for-each loop with it:
ele -> contains(selection.getItems(), ele, (e1, e2) -> e1.getId() == e2.getId())
来源:https://stackoverflow.com/questions/41921835/filtering-a-stream-against-items-in-another-list