I am quite new to java streams. Do I need to re-create the stream each time in this loop or is there a better way to do this? Creating the stream once and using the .noneMatch twice results in "stream already closed" exception.
for ( ItemSetNode itemSetNode : itemSetNodeList )
{
Stream<Id> allUserNodesStream = allUserNodes.stream().map( n -> n.getNodeId() );
Id nodeId = itemSetNode.getNodeId();
//if none of the user node ids match the node id, the user is missing the node
if ( allUserNodesStream.noneMatch( userNode -> userNode.compareTo( nodeId ) == 0 ) )
{
isUserMissingNode = true;
break;
}
}
Thank you !
I would suggest you make a list of all the user ids outside the loop. Just make sure the class Id overrides equals() function.
List<Id> allUsersIds = allUserNodes.stream().map(n -> n.getNodeId()).collect(Collectors.toList());
for (ItemSetNode itemSetNode : itemSetNodeList)
{
Id nodeId = itemSetNode.getNodeId();
if (!allUsersIds.contains(nodeId))
{
isUserMissingNode = true;
break;
}
}
The following code should be equivalent, except that the value of the boolean is reversed so it's false if there are missing nodes.
First all the user node Ids are collected to a TreeSet (if Id implements hashCode() and equals() you should use a HashSet). Then we stream itemSetNodeList to see if all those nodeIds are contained in the set.
TreeSet<Id> all = allUserNodes
.stream()
.map(n -> n.getNodeId())
.collect(Collectors.toCollection(TreeSet::new));
boolean isAllNodes = itemSetNodeList
.stream()
.allMatch(n -> all.contains(n.getNodeId()));
There are many ways to write equivalent (at least to outside eyes) code, this uses a Set to improve the lookup so we don't need to keep iterating the allUserNodes collection constantly.
You want to avoid using a stream in a loop, because that will turn your algorithm into O(n²) when you're doing a linear loop and a linear stream operation inside it. This approach is O(n log n), for the linear stream operation and O(log n) TreeSet lookup. With a HashSet this goes down to just O(n), not that it matters much unless you're dealing with large amount of elements.
You also could do something like this:
Set<Id> allUserNodeIds = allUserNodes.stream()
.map(ItemSetNode::getNodeId)
.collect(Collectors.toCollection(TreeSet::new));
return itemSetNodeList.stream()
.anyMatch(n -> !allUserNodeIds.contains(n.getNodeId())); // or firstMatch
Or even:
Collectors.toCollection(() -> new TreeSet<>(new YourComparator()));
Terminal operations of a Stream such as noneMatch() close the Stream and make it so not reusable again.
If you need to reuse this Stream :
Stream<Id> allUserNodesStream = allUserNodes.stream().map( n -> n.getNodeId() );
just move it into a method :
public Stream<Id> getAllUserNodesStream(){
return allUserNodes.stream().map( n -> n.getNodeId());
}
and invoke it as you need it to create it :
if (getAllUserNodesStream().noneMatch( userNode -> userNode.compareTo( nodeId ) == 0 ))
Now remember that Streams become loops in the byte code after compilation.
Performing multiple times the same loop may not be desirable. So you should consider this point before instantiating multiple times the same stream.
As alternative to create multiple streams to detect match with nodeId :
if (allUserNodesStream.noneMatch( userNode -> userNode.compareTo( nodeId ) == 0 ) ) {
isUserMissingNode = true;
break;
}
use rather a structure of type Set that contains all id of allUserNodes :
if (idsFromUserNodes.contains(nodeId)){
isUserMissingNode = true;
break;
}
It will make the logic more simple and the performance better.
Of course it supposes that compareTo() be consistent with equals() but it is strongly recommended (though not required).
It will take each item from the itemSetNodeList and check it if present in the using the noneMatch(). If it is not present will get true returned. The anyMatch if atleast once item is not found will stop the search and return false. If all the item is found, we will return true.
Stream<Id> allUserNodesStream = allUserNodes.stream().map( n -> n.getNodeId() );
boolean isUserMissing=itemSetNodeList.stream()
.anyMatch(n-> allUserNodes.stream().noneMatch(n));
Related
I tried to write the following code using the stream API, but found that being not able to access the Parent objects in the stream of Grandchild objects, it is way easier to use classic for-loops. How could a solution using the stream API look like? And are there any advantages to such a solution?
The task is to find certain elements in a structure like this: parent -> List -> List and to get for each matching Grandchildren a String of nameOfParent.nameOfGrandchild.
Simple, working code with for-loops:
List<String> grandchilds = new ArrayList<>();
for (Parent parent : parents) {
String parentName = parent.getName() + ".";
for (Child child : parent.getChilds()) {
if (null != child.getTags() && child.getTags().size() == 1
&& SEARCHED_TAG.equals(child.getTags().get(0))) {
for (Grandchild gc : child.getGrandchilds()) {
grandchilds.add(parentName + gc.getName());
}
}
}
}
I was at this point using stream API where i realized that accessing properties of parent is no longer possible when i have the stream of Grandchild.
List<Grandchild> grandchildren = parents.stream()
.flatMap(parent -> parent.getChilds().stream())
.filter(child -> null != child.getTags() && child.getTags().size() == 1 && SEARCHED_TAG.equals(child.getTags().get(0)))
.flatMap(child -> child.getGrandchilds().stream())
.collect(Collectors.toList());
I found some hints like in the following questions, but the resulting code seemed unnecessarily complex and there is the occasional recommendation to use for-loops instead.
Access element of previous step in a stream or pass down element as "parameter" to next step?
Get parent object from stream
How to iterate nested for loops referring to parent elements using Java 8 streams?
Rather than chaining flatMap() calls in a single pipeline, nest one inside the other:
List<String> grandchilds = parents.stream()
.flatMap(parent -> parent.getChilds().stream()
.filter(tagMatch(SEARCHED_TAG))
.flatMap(child -> child.getGrandchilds().stream().map(gc -> parent.getName() + "." + gc.getName())))
.toList();
private static Predicate<Child> tagMatch(String tag) {
return child -> {
List<String> tags = child.getTags();
return tags != null && tags.size() == 1 && tags.get(0).equals(tag);
};
}
I didn't test that and may have missed a parens in there, but that's the gist of it.
As far as advantages go, I don't see any. That aspect is opinion-based, but in my opinion, the for-loops are easier to read and verify, even after factoring out the rather complicated filtering predicate.
I have some imperative Java conditional code that I want to refactor to use Streams.
Specifically, I have this map that I want to filter into a List based on specific filter criteria.
private Map<Integer,Thing> thingMap = new HashMap<Integer,Thing>();
// populate thingMap
And here's the code that uses it:
List<Thing> things = new ArrayList<Thing>();
for (Thing thing : thingMap.values()) {
if (thing.getCategory().equals(category)) {
if (location == null) {
things.add(thing);
} else if (thing.getLocation().equals(location)) {
things.add(thing);
}
}
}
I refactored that to the following. But what's missing is I want the location to be checked only if the category filter passes. Also, I suspect there's a better way to do this:
List<Thing> things = thingMap.entrySet()
.stream()
.filter(t -> t.getValue().getCategory().equals(category))
.filter(t ->
location == null ||
t.getValue().getLocation().equals(location)
)
.map(Map.Entry::getValue)
.collect(Collectors.toList());
What would be the idiomatic approach to retaining the layered conditional checks using Streams?
Operations chained after a filter will only be executed for elements accepted by the predicate. So there is no need to worry about that.
You could also join the conditions into a single filter step, just like you could join the nested if statements into a single if, by combining the conditions using &&. The result is the same.
But note that the loop uses the condition location == null, referring to the variable declared outside the code snippet you have posted, not thing.getLocation() == null.
Besides that, you made other unnecessary changes compared to the loop. The loop iterates over the values() view of the map whereas you used entrySet() for the Stream instead, introducing the need to call getValue() on a Map.Entry four times.
A straight-forward translation of the loop logic is much simpler:
List<Thing> things = thingMap.values().stream()
.filter(thing -> thing.getCategory().equals(category))
.filter(thing -> location == null || thing.getLocation().equals(location))
.collect(Collectors.toList());
I have the following stream code:
List<Data> results = items.stream()
.map(item -> requestDataForItem(item))
.filter(data -> data.isValid())
.collect(Collectors.toList());
Data requestDataForItem(Item item) {
// call another service here
}
The problem is that I want to call
requestDataForItem only when all elements in the stream are valid.
For example,
if the first item is invalid I don't wont to make the call for any element in the stream.
There is .allMatch in the stream API,
but it returns a boolean.
I want to do the same as .allMatch than
.collect the result when everything matched.
Also, I want to process stream only once,
with two loops it is easy.
Is this possible with the Java Streams API?
This would be a job for Java 9:
List<Data> results = items.stream()
.map(item -> requestDataForItem(item))
.takeWhile(data -> data.isValid())
.collect(Collectors.toList());
This operation will stop at the first invalid element. In a sequential execution, this implies that no subsequent requestDataForItem calls are made. In a parallel execution, some additional elements might get processed concurrently, before the operation stops, but that’s the price for efficient parallel processing.
In either case, the result list will only contain the elements before the first encountered invalid element and you can easily check using results.size() == items.size() whether all elements were valid.
In Java 8, there is no such simple method and using an additional library or rolling out your own implementation of takeWhile wouldn’t pay off considering how simple the non-stream solution would be
List<Data> results = new ArrayList<>();
for(Item item: items) {
Data data = requestDataForItem(item);
if(!data.isValid()) break;
results.add(data);
}
You could theoretically use .allMatch then collect if .allMatch returns true, but then you'd be processing the collection twice. There's no way to do what you're trying to do with the streams API directly.
You could create a method to do this for you and simply pass your collection to it as opposed to using the stream API. This is slightly less elegant than using the stream API but more efficient as it processes the collection only once.
List<Data> results = getAllIfValid(
items.stream().map(item ->
requestDataForItem(item).collect(Collectors.toList())
);
public List<Data> getAllIfValid(List<Data> items) {
List<Data> results = new ArrayList<>();
for (Data d : items) {
if (!d.isValid()) {
return new ArrayList<>();
}
results.add(d);
}
return results;
}
This will return all the results if every element passes and only processes the items collection once. If any fail the isValid() check, it'll return an empty list as you want all or nothing. Simply check to see if the returned collection is empty to see whether or not all items passed the isValid() check.
Implement a two step process:
test if allMatch returns true.
If it does return true, do the collect with a second stream.
Try this.
List<Data> result = new ArrayList<>();
boolean allValid = items.stream()
.map(item -> requestDataForItem(item))
.allMatch(data -> data.isValid() && result.add(data));
if (!allValid)
result.clear();
Can somebody let me know how to exit from forEach loop if some condition matches.I am using parallel stream.
Below is my code.
Map<int[], String[]> indexAndColNamePairs = depMapEntry.getKey();
Set<List<String>> dataRecords = depMapEntry.getValue();
for(Map.Entry<int[], String[]> indexAndColNamePair: indexAndColNamePairs.entrySet())
{
int refColIndex = indexAndColNamePair.getKey()[0];
Stream<List<String>> dataRecs = dataRecords.parallelStream();
dataRecs.forEach((row) -> {
if(referencingValue.equals(row.get(refColIndex)))
{
requiredColValueAndName.put(row.get(indexAndColNamePair.getKey()[1]),
indexAndColNamePair.getValue()[1]);
}
});
if(referencingValue.equals(row.get(refColIndex))) then i am inserting value to map and then i need to exit.
As I understand your requirement, you want to execute one statement (requiredColValueAndName.put) for only one item in the list. Usage of Stream.forEach is not relevant for this use case. Instead find the item for which you want execute the statement first and then execute.
Optional<List<String>> expectedRow = dataRecs.filter(row -> referencingValue.equals(row.get(refColIndex))).findFirst();
if(expectedRow.isPresent()) {
requiredColValueAndName.put(
expectedRow.get().get(indexAndColNamePair.getKey()[1]),
indexAndColNamePair.getValue()[1]);
}
You can't. From the documentation:
In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning. Only the terminal operations iterator() and spliterator() are not; these are provided as an "escape hatch" to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task.
What you want is either filter() and findFirst() or iterate().
Somewhat more functional variation of #MrinalKSamanta answer:
indexAndColNamePairs.forEach((indices, colNames) -> {
int refColIndex = indices[0];
dataRecords.parallelStream()
.filter(row -> referencingValue.equals(row.get(refColIndex)))
.findFirst()
.ifPresent(row ->
requiredColValueAndName.put(row.get(indices[1]), colNames[1]));
});
Note that if you are not restricted to put exactly the first matching value (or you expect at most one matching value), you may have better performance if you replace .findFirst() with .findAny().
private String checkforlogin(List<String[]> ld, String iname, String ipass) {
for (String[] u : ld) {
Log.e("u[1]" ,u[1]);
Log.e("u[2]" ,u[2]);
if(u[1].toString().equals(iname) && u[2].toString().equals(ipass)){
System.out.println("Bingo!!!" + u[0] + " " + u[1] + " " + u[2]);
//go the the MainActivity
retur_val = u[0];
}else{
if(retur_val.equals("-1"))
{
retur_val = "-1";
}else {
Log.e("already" , "got the value");
}
// return retur_val;
}
}
return retur_val;
}
//++++++++++++++++++++++++++++++++++++++++++++
retur_val is a calss variable which is "-1"
I want to iterate nested lists using java8 streams, and extract some results of the lists on first match.
Unfortunately I have to also get a values from the parent content if a child element matches the filter.
How could I do this?
java7
Result result = new Result();
//find first match and pupulate the result object.
for (FirstNode first : response.getFirstNodes()) {
for (SndNode snd : first.getSndNodes()) {
if (snd.isValid()) {
result.setKey(first.getKey());
result.setContent(snd.getContent());
return;
}
}
}
java8
response.getFirstNodes().stream()
.flatMap(first -> first.getSndNodes())
.filter(snd -> snd.isValid())
.findFirst()
.ifPresent(???); //cannot access snd.getContent() here
When you need both values and want to use flatMap (as required when you want to perform a short-circuit operation like findFirst), you have to map to an object holding both values
response.getFirstNodes().stream()
.flatMap(first->first.getSndNodes().stream()
.map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
.filter(e->e.getValue().isValid())
.findFirst().ifPresent(e-> {
result.setKey(e.getKey().getKey());
result.setContent(e.getValue().getContent());
});
In order to use standard classes only, I use a Map.Entry as Pair type whereas a real Pair type might look more concise.
In this specific use case, you can move the filter operation to the inner stream
response.getFirstNodes().stream()
.flatMap(first->first.getSndNodes().stream()
.filter(snd->snd.isValid())
.map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
.findFirst().ifPresent(e-> {
result.setKey(e.getKey().getKey());
result.setContent(e.getValue().getContent());
});
which has the neat effect that only for the one matching item, a Map.Entry instance will be created (well, should as the current implementation is not as lazy as it should but even then it will still create lesser objects than with the first variant).
It should be like this:
Edit: Thanks Holger for pointing out that the code won't stop at the first valid FirstNode
response.getFirstNodes().stream()
.filter(it -> {it.getSndNodes().stream().filter(SndNode::isValid).findFirst(); return true;})
.findFirst()
.ifPresent(first -> first.getSndNodes().stream().filter(SndNode::isValid).findFirst().ifPresent(snd -> {
result.setKey(first.getKey());
result.setContent(snd.getContent());
}));
A test can be found here