In the following code I am trying to remove all nodes and leaves that do not have a root in the key of input map. Input is the Map<rootId: String, listOf(root,nodes,leaves)>
Working logic
#NotNull
private static Map<String, List<Element>> removeOrphanNodes(Map<String, List<Element>> mapOfAllProcesses) {
Map<String,List<Element>> refinedRootMap= new HashMap<>();
for(Map.Entry<String,List<Element>>entrySet: mapOfAllProcesses.entrySet())
{
if(entrySet.getValue().size()>1)
refinedRootMap.put(entrySet.getKey(),entrySet.getValue());
else {
Element loneElement = entrySet.getValue().get(0);
if (entrySet.getKey().equals(loneElement.getIdAsString()))
refinedRootMap.put(entrySet.getKey(),entrySet.getValue());
else if(loneElement.getCurrentOperations()!=null && loneElement.getCurrentOperations().iterator().next().getId().toHexString().equals(entrySet.getKey()))
refinedRootMap.put(entrySet.getKey(),entrySet.getValue());
}
}
return refinedRootMap;
}
The above code works as expected. I wanted to make use streams to achieve the same functionality but getCurrentOperations throws null pointer
My Attempt
return mapOfAllProcesses.entrySet().stream().filter(entry -> entry.getValue().size()>1 || entry.getValue().stream()
.anyMatch(
element-> element.getIdAsString().equals(entry.getKey())||element.getCurrentOperations().stream().findFirst().get().getId().toHexString().equals(entry.getKey())
)).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Don't do this.
The Stream API is not a replacement for loops or conventional iteration constructs.
Ideal code for putting into a Stream would be something that:
Does one function (reads data out, loads data in)
Could be parallelized
Has no side-effects (e.g. doesn't reach out to anything else)
Your code satisfies the last bullet, I'm not sure about the middle bullet and it definitely does a lot more based on conditions, which...isn't ideal for streaming.
Maybe a better way to approach this problem would be to re-think the data structure you're using? You're using a Map<K, List<V>>, and that can be contextualized inside of a Guava Multimap. Maybe that's where the first improvement needs to happen - using a more suitable data structure for this instead?
To avoid raising NPE when collection returned by element.getCurrentOperations() is null you might try to use Stream.ofNullable(). And dummy default value while extracting result from the optional can help in cases when this collection is empty (and consequently optional would be empty).
return mapOfAllProcesses.entrySet().stream()
.filter(entry -> entrySet.getValue().size() > 1 ||
entry.getValue().stream()
.anyMatch(
element -> element.getIdAsString().equals(entry.getKey())
||
Stream.ofNullable(element.getCurrentOperations()).findFirst()
.map(operation -> operation.getId().toHexString())
.orElse("").equals(entry.getKey())
))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
It is worth to note that both snippets (imperative and functional) are extremely convoluted, so it would be wise to consider extracting some pieces of functionality into separate methods.
This should do it, but I don't think there's any advantage to be gained this way. It's slightly shorter, but not really any simpler, and definitely not any clearer.
#NotNull
private static Map<String, List<Element>> removeOrphanNodes(Map<String, List<Element>> mapOfAllProcesses) {
return mapOfAllProcesses.entrySet().stream().filter((entry) ->
(entry.getValue().size() > 1)
|| entry.getKey().equals(entry.getValue().get(0).getIdAsString())
|| Optional.ofNullable(entry.getValue().get(0).getCurrentOperations())
.stream()
.map((ops) -> ops.iterator().next().getId().toHexString())
.anyMatch((s) -> s.equals(entry.getKey()))
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
This version handles the fact that getCurrentOperations() can return null by capturing the return value as an Optional. The Optional's stream will be empty when getCurrentOperations() returns null, and anyMatch() returns false on an empty stream.
The map() of the inner stream could be omitted in favor of a more complex predicate for its anyMatch(), but I think the version with map() is clearer.
Related
Do Java streams have a convenient way to map based upon a predicate, but if the predicate is not met to map to some other value?
Let's say I have Stream.of("2021", "", "2023"). I want to map that to Stream.of(Optional.of(Year.of(2021)), Optional.empty(), Optional.of(Year.of(2023))). Here's one way I could do that:
Stream<String> yearStrings = Stream.of("2021", "", "2023");
Stream<Optional<Year>> yearsFound = yearStrings.map(yearString ->
!yearString.isEmpty() ? Year.parse(yearString) : null)
.map(Optional::ofNullable);
But here is what I would like to do, using a hypothetical filter-map:
Stream<String> yearStrings = Stream.of("2021", "", "2023");
Stream<Optional<Year>> yearsFound = yearStrings.mapIfOrElse(not(String::isEmpty),
Year::parse, null).map(Optional::ofNullable);
Of course I can write my own mapIfOrElse(Predicate<>, Function<>, T) function to use with Stream.map(), but I wanted to check if there is something similar in Java's existing arsenal that I've missed.
There is not a very much better way of doing it than you have it - it might be nicer if you extracted it to a method, but that's really it.
Another way might be to construct Optionals from all values, and then use Optional.filter to map empty values to empty optionals:
yearStreams.map(Optional::of)
.map(opt -> opt.filter(Predicate.not(String::isEmpty)));
Is this better? Probably not.
Yet another way would be to make use of something like Guava's Strings.emptyToNull (other libraries are available), which turns your empty strings into null first; and then use Optional.ofNullable to turn non-nulls and nulls into non-empty and empty Optionals, respectively:
yearStreams.map(Strings::emptyToNull)
.map(Optional::ofNullable)
You can just simply use filter to validate and then only map
Stream<Year> yearsFound = yearStrings.filter(yearString->!yearString.isEmpty()).map(Year::parse)
It's hardly possible to combine all these actions smoothly in well-readable way within a single stream operation.
Here's a weird method-chaining with Java 16 mapMulti():
Stream<Optional<Year>> yearsFound = yearStrings
.mapMulti((yearString, consumer) ->
Optional.of(yearString).filter(s -> !s.isEmpty()).map(Year::parse)
.ifPresentOrElse(year -> consumer.accept(Optional.of(year)),
() -> consumer.accept(Optional.empty()))
);
I have some Java code that provides objects from items. It limits them based on the maxNumber:
items.stream()
.map(this::myMapper)
.filter(item -> item != null)
.limit(maxNumber)
.collect(Collectors.toList());
It works properly, but the question is this: Is there a way of skipping the limiting when the maxNumber == 0?
I know I could do this:
if (maxNumber == 0) {
items.stream()
.map(this::myMapper)
.filter(item -> item != null)
.collect(Collectors.toList());
} else {
items.stream()
.map(this::myMapper)
.filter(item -> item != null)
.limit(maxNumber)
.collect(Collectors.toList());
}
But perhaps there's a better way, does anything come to your mind?
No, the stream pipeline doesn't allow to actually skip around any part of the pipeline, so you're forced to work with either conditional logic inside the steps and including the limit() always in the pipeline, or building the stream in parts which would be a bit more legible (IMHO) than the if/else in the question
Stream<Item> s = items.stream()
.map(this::myMapper)
.filter(Objects::nonNull);
if(maxNumber > 0) {
s = s.limit(maxNumber);
}
List<Item> l = s.collect(Collectors.toList());
In a simple case like here it doesn't make much difference, but you often see in regular code collections being passed through methods, converted to streams and then back to collections. In such cases it might be a better idea to work with streams in parts until you really need to collect().
I suppose that
.limit(maxNumber == 0 ? Long.MAX_VALUE : maxNumber)
will do the trick, as it is highly non probable that you are going to tackle a stream with more than 2^63-1 elements...
At least be careful with parallel streams on this... A note in API docs says:
API Note: While limit() is generally a cheap operation on sequential
stream pipelines, it can be quite expensive on ordered parallel
pipelines, especially for large values of maxSize...
I have the following code:
ArrayList <String> entries = new ArrayList <String>();
entries.add("0");
entries.add("1");
entries.add("2");
entries.add("3");
String firstNotHiddenItem = entries.stream()
.filter(e -> e.equals("2"))
.findFirst()
.get();
I need to know what is the index of that first returned element, since I need to edit it inside of entries ArrayList. As far as I know get() returns the value of the element, not a reference. Should I just use
int indexOf(Object o)
instead?
You can get the index of an element using an IntStream like:
int index = IntStream.range(0, entries.size())
.filter(i -> "2".equals(entries.get(i)))
.findFirst().orElse(-1);
But you should use the List::indexOf method which is the preferred way, because it's more concise, more expressive and computes the same results.
You can't in a straightforward way - streams process elements without context of where they are in the stream.
However, if you're prepared to take the gloves off...
int[] position = {-1};
String firstNotHiddenItem = entries.stream()
.peek(x -> position[0]++) // increment every element encounter
.filter("2"::equals)
.findFirst()
.get();
System.out.println(position[0]); // 2
The use of an int[], instead of a simple int, is to circumvent the "effectively final" requirement; the reference to the array is constant, only its contents change.
Note also the use of a method reference "2"::equals instead of a lambda e -> e.equals("2"), which not only avoids a possible NPE (if a stream element is null) and more importantly looks way cooler.
A more palatable (less hackalicious) version:
AtomicInteger position = new AtomicInteger(-1);
String firstNotHiddenItem = entries.stream()
.peek(x -> position.incrementAndGet()) // increment every element encounter
.filter("2"::equals)
.findFirst()
.get();
position.get(); // 2
This will work using Eclipse Collections with Java 8
int firstIndex = ListIterate.detectIndex(entries, "2"::equals);
If you use a MutableList, you can simplify the code as follows:
MutableList<String> entries = Lists.mutable.with("0", "1", "2", "3");
int firstIndex = entries.detectIndex("2"::equals);
There is also a method to find the last index.
int lastIndex = entries.detectLastIndex("2"::equals);
Note: I am a committer for Eclipse Collections
Yes, you should use indexOf("2") instead. As you might have noticed, any stream based solution has a higher complexity, without providing any benefit.
In this specific situation, there is no significant difference in performance, but overusing streams can cause dramatic performance degradation, e.g. when using map.entrySet().stream().filter(e -> e.getKey().equals(object)).map(e -> e.getValue()) instead of a simple map.get(object).
The collection operations may utilize their known structure while most stream operation imply a linear search. So genuine collection operations are preferable.
Of course, if there is no collection operation, like when your predicate is not a simple equality test, the Stream API may be the right tool. As shown in “Is there a concise way to iterate over a stream with indices in Java 8?”, the solution for any task involving the indices works by using the indices as starting point, e.g. via IntStream.range, and accessing the list via List.get(int). If the source in not an array or a random access List, there is no equally clean and efficient solution. Sometimes, a loop might turn out to be the simplest and most efficient solution.
I have a stream of enum values that I want to reduce. If the stream is empty or contains different values, I want null. If it only contains (multiple instances of) a single value, I want that value.
[] null
[A, B, A] null
[A] A
[A, A, A] A
I tried to do it with a reduce:
return <lots of filtering, mapping, and other stream stuff>
.reduce((enum1, enum2) -> enum1 == enum2 ? enum1 : null)
.orElse(null);
Unfortunately, this does not work, because this reduce method throws a NullPointerException when the result is null. Does anyone know why that happens? Why is null not a valid result?
For now, I solved this like this:
MyEnum[] array = <lots of filtering, mapping, and other stream stuff>
.distinct()
.toArray(MyEnum[]::new);
return array.length == 1 ? array[0] : null;
While this works, I am not satisfied with this "detour". I liked the reduce because it seemed to be the right fit and put everything into one stream.
Can anyone think of an alternative to the reduce (that ideally is not too much code)?
Generally, all Stream methods returning an Optional don’t allow null values as it would be impossible to tell the null result and “no result” (empty stream) apart.
You can work-around this with a place-holder value, which unfortunately requires to suspend the type safety (as there is no type-compatible value outside the enum set):
return <lots of filtering, mapping, and other stream stuff>
.reduce((enum1, enum2) -> enum1 == enum2? enum1: "")
.map(r -> r==""? null: (MyEnum)r)
.orElse(null);
Optional.map will return an empty optional if the mapping function returns null, so after that step, an empty stream and a null result can’t be distinguished anymore and orElse(null) will return null in both cases.
But maybe the array detour only feels to unsatisfying, because the array isn’t the best choice for the intermediate result? How about
EnumSet<MyEnum> set = <lots of filtering, mapping, and other stream stuff>
.collect(Collectors.toCollection(() -> EnumSet.noneOf(MyEnum.class)));
return set.size()==1? set.iterator().next(): null;
The EnumSet is only a bitset, a single long value if the enum type has not more than 64 constants. That’s much cheaper than an array and since Sets are naturally distinct, there is no need for a distinct() operation on the stream, which would create a HashSet under the hood.
Most stream higher-order functions don't allow null either as parameter or function return value. They are to prevent yet another billion-dollar mistake. Such response is documented here:
Optional reduce(BinaryOperator accumulator)
.....
Throws:
NullPointerException - if the result of the reduction is null
How about a really mathematical approach(hard to maintain I agree)?
Arrays.stream(array).map(e -> e.ordinal() + 1).reduce(Integer::sum)
.map(i -> (double) i / array.length == array[0].ordinal() + 1 ? array[0] : null)
.orElse(null)
I have a List of Maps with certain keys that map to String values.
Something like List<Map<String,String>> aMapList;
Objective : Stream over this List of maps and collect values of a single key in all Maps.
How I'm doing this ->
key = "somekey";
aMapList.stream().map(a -> a.get(key)).collect(Collectors.averagingInt());
The Problem:
I get exceptions due to a.get(key) if there is no such key! because averaging this will give a null. How do I check or make lambda ignore any such maps and move on.
I do know that I can add a filter on a -> a.contains(key) and then proceed.
Edit : I can also add more filters or simple check multiple conditions on one filter.
Possible Solution:
aMapList.stream().filter(a -> a.contains(key)).
map(a -> a.get(key)).collect(Collectors.averagingInt());
Can this be made prettier? Instead of halting the operation, simply skip over them?
Is there some more generic way to skip over exceptions or nulls.
For eg. We can expand the lambda and put a try-catch block, but I still need to return something, what if I wish to do an equivalent of "continue".
Eg.
(a -> {return a.get(key) }).
Can be expanded to -->
(a -> {try{return a.get(key)}
catch(Exception e){return null} }).
The above still returns a null, instead of just skipping over.
I'm selecting the best answer for giving two options, But I do not find any of them prettier. Chaining filters seems to be the solution to this.
How about wrapping the result with Optional:
List<Optional<String>> values = aMapList.stream()
.map(a -> Optional.ofNullable(a.get(key)))
.collect(Collectors.toList());
Later code will know to expect possible empty elements.
The solution you propose has a potential bug for maps that allow null values. For example:
Map<String, String> aMap = new HashMap<>();
aMap.put("somekey", null);
aMapList.add(aMap);
aMapList.straem()
.filter(a -> a.contains("somekey")) // true returned for contains
.map(a -> a.get("somekey")) // null returned for get
.collect(Collectors.toList());
Based on the Map documentation, and on your comment under your question, you're not actually getting an exception from a.get(key). Rather, that expression produces a null value, and you're having problems later when you run into these null values. So simply filtering out these null values right away should work just fine:
aMapList.stream()
.map(a -> a.get(key))
.filter(v -> v != null)
.collect(Collectors.toList());
This is prettier, simpler, and performs better than the workaround in your question.
I should mention that I usually prefer the Optional<> type when dealing with null values, but this filtering approach works better in this case since you specifically said you wanted to ignore elements where the key doesn't exist in a map list.
The simplest I could come up with was:
aMapList.stream()
.filter(map -> map.containsKey(key))
.map(map -> map.get(key))
.collect(Collectors.toList());
By formatting the lambda in this fashion, it is easier to see the distinct steps that the code processes.
Although I reckon this is not exactly a prettier approach, you could do:
aMapList.stream().map(a -> a.containsKey(key) ? a.get(key) : null).collect(Collectors.toList());