How to specify Predicate in Java Stream when found too many items - java

I am looking for some object in map:
mapObjects.entrySet().stream().map(map -> map.getValue()).filter(predicateA)
When I find more then one item, I want to specify a second predicate to filter on some additional attribute. Is there some way I can do this in just one iteration of stream, or do I need to iterate once and when count > 1 then I need to iterate a second time with another predicate ?
For example, say I have list of persons. First I am looking for name=John. When there is more than one John, I look for surname=Smith. Now I don't care if there is more than one and I just take the first.

It could be done by first filtering the Person instances by name then grouping by surname. The result will be put into a LinkedHashMap in order to get the first match if there is no full match (name and surname), finally we rely on Map#getOrDefault(key, defaultValue) to get the full match if it exists otherwise it will get the first entry as default value.
Map<String, Person> map = mapObjects.values().stream()
.filter(p -> Objects.equals(p.getName(), name))
.collect(
Collectors.groupingBy(
Person::getSurname,
LinkedHashMap::new,
Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0))
)
);
Optional<Person> result =
map.isEmpty() ?
Optional.absent() :
Optional.of(
map.getOrDefault(surname, map.entrySet().iterator().next().getValue())
);
This way you iterate only once to get your result and you don't use a stateful Predicate.

You can use a reduction operation which prioritizes the second predicate when possible:
mapObjects.values().stream()
.filter(predicateA)
.reduce((acc, obj) -> predicateB.test(obj) ? obj : acc)
.ifPresent(doThing);
Unfortunately, the reduction can't be short-circuited. If this is important, keep reading.
You could give predicateB a wrapping class which only tries to return true if it never has before and the argument meets the criteria. Here is an atomic implementation so that it still works in parallel streams.
public class ShortCircuitPredicate<T> implements Predicate<T> {
private final AtomicBoolean hasBeenTrue;
private final Predicate<T> predicate;
private ShortCircuitPredicate(Predicate<T> pred) {
hasBeenTrue = new AtomicBoolean(false);
predicate = pred;
}
public static <T> ShortCircuitPredicate<T> of(Predicate<T> pred) {
return new ShortCircuitPredicate<>(pred);
}
#Override
public boolean test(T t) {
return hasBeenTrue.get()
? false
: predicate.test(t) && hasBeenTrue.compareAndSet(false, true);
}
}
You can wrap predicateB using ShortCircuitPredicate.of(predicateB).

I am not convinced this problem is a good fit for using the stream API, but an option would be to rely on Stream#peek() to keep a reference to one of the elements that matches the first filter, if none match the second one:
List<Person> people = ...
Person[] holder = new Person[1];
Person result = people.stream()
.filter(p -> p.getName().equals("John"))
.peek(p -> holder[0] = p)
.filter(p -> p.getSurname().equals("Smith"))
.findAny()
.orElse(holder[0]);
This is short-circuiting in case there is any match for both filters. On the other hand, peek() and the second filter will have to be executed on all matches of the first predicate before findAny() returns an empty optional. Consequently, the holder will always be filled, except when there is no match to the first predicate.
I suggested carefully reading In Java streams is peek really only for debugging? though, and make your own opinion on whether this is an appropriate option for your specific case.

Related

Initializing a String on basis on java streams results in an empty String [duplicate]

The JavaDoc for Stream.collect() says that it returns "the result of the reduction". That doesn't tell me if code like this can return null for filteredList:
List<String> filteredList = inputList.stream()
.filter(c -> c.isActive())
.collect(Collectors.toList());
I would expect that if it could return null then it would return an Optional, but it doesn't say that either.
Is it documented anywhere whether Stream.collect() can return null?
Collector.toList() will return an empty List for you.
Here is the implementation:
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
As you can see ArrayList::new is being used as a container for your items.
From JavaDoc of Collector:
A mutable reduction operation that
accumulates input elements into a mutable result container, optionally
transforming the accumulated result into a final representation after
all input elements have been processed. Reduction operations can be
performed either sequentially or in parallel.
A Collector is specified by four functions that work together to
accumulate entries into a mutable result container, and optionally
perform a final transform on the result. They are:
creation of a new result container (supplier())
incorporating a new data element into a result container (accumulator())
combining two result containers into one (combiner())
performing an optional final transform on the container (finisher())
And
A sequential implementation of a reduction using a collector would
create a single result container using the supplier function, and
invoke the accumulator function once for each input element. A
parallel implementation would partition the input, create a result
container for each partition, accumulate the contents of each
partition into a subresult for that partition, and then use the
combiner function to merge the subresults into a combined result.
So as long as you don't do weird things like combine function return null, the Collector always return at least a mutable container using your provided supplier function.
And I think it's very counter-intuitive if an implementation would ever return null container.
This is not dependent on Stream.collect, but on the individual Collector. Collectors.toList() will return an empty ArrayList.
That said, there's no reason someone couldn't use a weird Collector to return null in certain circumstances:
.collect(
Collector.of(
ArrayList::new,
ArrayList::add,
(a, b) -> {
a.addAll(b);
return a;
},
a -> a.isEmpty() ? null : a // finisher replaces empty list with null
)
);
So the Collector is the thing you need to remember to check. I believe all of the Collectors available out-of-the-box will return empty collections, as you'd expect.
You could use Collectors::collectingAndThen to pass collect() result to a Function<T,R>. Return value of the Function<T,R> will be return value of collect().
List<String> filteredList = inputList.stream()
.filter(c -> c.isActive())
.collect(Collectors.collectingAndThen(Collectors.toList(), c -> !c.isEmpty()?c:null));
I think this part of the documentation says that it cannot be null:
Returns a Collector that accumulates the input elements into a new
List.
Highlights added by me. I think this new List means that something that isn't null.
I started to check ReferencePipeline.collect() to check whether it's true for the actual implementation. Unfortunately, it was a futile attempt. There are so many cases here, like is it parallel? is it after a forEach? etc.
This is collector-dependant. The one You're using (Collectors.toList()) returns an empty list.
No collect will never return null, in order to check use isEmpty() instead of null
I believe the following code is a good implementation if you really need to return null when the list is empty (assuming the type of the variable c is MyObj):
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
...
List<String> filteredList = inputList.stream()
.filter(MyObj::isActive)
.collect(collectingAndThen(toList(), Stream::of)
.filter(List::isEmpty)
.findAny()
.orElse(null);

Accumulate values and return result in Java stream

I have a class with a collection of Seed elements. One of the method's return type of Seed is Optional<Pair<Boolean, Boolean>>.
I'm trying to loop over all seeds, keeping the return type (Optional<Pair<Boolean, Boolean>>), but I would like to be able to say if there was at least true value (in any of the Pairs) and override the result with it. Basically, if the collection is (skipping the Optional wrapper to make things simpler): [Pair<false, false>, Pair<false, true>, Pair<false, false>] I would like to return and Optional of Pair<false, true> because the second element had true. In the end, I'm interested if there was a true value and that's about it.
public Optional<Pair<Boolean, Boolean>> hadAnyExposure() {
return seeds.stream()
.map(Seed::hadExposure)
...
}
I was playing with reduce but couldn't come up with anything useful.
My question is related with Java streams directly. I can easily do this with a for loop, but I aimed initially for streams.
Straighforward
Since you're Java 11, you can use Optional::stream (introduced in Java 9) to get rid of the Optional wrapper. As a terminal operation, reduce is your friend:
public Optional<Pair<Boolean, Boolean>> hadAnyExposure() {
// wherever the seeds come from
Stream<Optional<Pair<Boolean, Boolean>>> seeds = seeds();
return seeds
.flatMap(Optional::stream)
.reduce((pair1, pair2) -> new Pair<>(
pair1.left() || pair2.left(),
pair1.right() || pair2.right())
);
}
Extended
If you want to go a step further and give your Pair a general way to be folded with another Pair into a new instance, you can make the code a bit more expressive:
public class Pair<LEFT, RIGHT> {
private final LEFT left;
private final RIGHT right;
// constructor, equals, hashCode, toString, ...
public Pair<LEFT, RIGHT> fold(
Pair<LEFT, RIGHT> other,
BinaryOperator<LEFT> combineLeft,
BinaryOperator<RIGHT> combineRight) {
return new Pair<>(
combineLeft.apply(left, other.left),
combineRight.apply(right, other.right));
}
}
// now you can use fold and Boolean::logicalOr
// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Boolean.html#logicalOr(boolean,boolean)
public Optional<Pair<Boolean, Boolean>> hadAnyExposure() {
Stream<Optional<Pair<Boolean, Boolean>>> seeds = seeds();
return seeds
.flatMap(Optional::stream)
.reduce((pair1, pair2) -> pair1
.fold(pair2, Boolean::logicalOr, Boolean::logicalOr))
);
}
I probably wouldn't create Pair::fold just for this use case, but I would be tempted. ;)
Your thoughts on reduce look like the right way to go, using || to reduce both sides of each Pair together. (Not exactly sure what your Optional semantics are, so going to filter out empty ones here and that might get what you want, but you may need to adjust):
Optional<Pair<Boolean, Boolean>> result = seeds.stream().map(Seed::hadExposure)
.filter(Optional::isPresent)
.map(Optional::get)
.reduce((a, b) -> new Pair<>(a.first || b.first, a.second || b.second));
As you've tagged this question with java-11, you can make use of the Optional.stream method:
public Optional<Pair<Boolean, Boolean>> hadAnyExposure() {
return Optional.of(
seeds.stream()
.flatMap(seed -> seed.hadExposure().stream())
.collect(
() -> new Pair<Boolean, Boolean>(false, false),
(p, seed) -> {
p.setLeft(p.getLeft() || seed.getLeft());
p.setRight(p.getRight() || seed.getRight());
},
(p1, p2) -> {
p1.setLeft(p1.getLeft() || p2.getLeft());
p1.setRight(p1.getRight() || p2.getRight());
}));
}
This first gets rid of the Optional by means of the Optional.stream method (keeping just the pairs) and then uses Stream.collect to mutably reduce the pairs by means of the OR associative operation.
Note: using Stream.reduce would also work, but it would create a lot of unnecessary intermediate pairs. That's why I've used Stream.collect instead.
using Collectors.partitioningBy you can get a Map with boolean keys after that you can easily retrieve values indexed with the key true
Optional<Pair<Boolean, Boolean>> collect = Arrays.asList(pair1, pair2, par3).stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.collectingAndThen(Collectors.partitioningBy(p -> p.getFirst() == true || p.getSecond() == true),
m -> m.get(true).stream().findAny()));

Java 8 Streams : Count the occurrence of elements(List<String> list1) from list of text data(List<String> list2)

Input :
List<String> elements= new ArrayList<>();
elements.add("Oranges");
elements.add("Figs");
elements.add("Mangoes");
elements.add("Apple");
List<String> listofComments = new ArrayList<>();
listofComments.add("Apples are better than Oranges");
listofComments.add("I love Mangoes and Oranges");
listofComments.add("I don't know like Figs. Mangoes are my favorites");
listofComments.add("I love Mangoes and Apples");
Output : [Mangoes, Apples, Oranges, Figs] -> Output must be in descending order of the number of occurrences of the elements. If elements appear equal no. of times then they must be arranged alphabetically.
I am new to Java 8 and came across this problem. I tried solving it partially; I couldn't sort it. Can anyone help me with a better code?
My piece of code:
Function<String, Map<String, Long>> function = f -> {
Long count = listofComments.stream()
.filter(e -> e.toLowerCase().contains(f.toLowerCase())).count();
Map<String, Long> map = new HashMap<>(); //creates map for every element. Is it right?
map.put(f, count);
return map;
};
elements.stream().sorted().map(function).forEach(e-> System.out.print(e));
Output: {Apple=2}{Figs=1}{Mangoes=3}{Oranges=2}
In real life scenarios you would have to consider that applying an arbitrary number of match operations to an arbitrary number of comments can become quiet expensive when the numbers grow, so it’s worth doing some preparation:
Map<String,Predicate<String>> filters = elements.stream()
.sorted(String.CASE_INSENSITIVE_ORDER)
.map(s -> Pattern.compile(s, Pattern.LITERAL|Pattern.CASE_INSENSITIVE))
.collect(Collectors.toMap(Pattern::pattern, Pattern::asPredicate,
(a,b) -> { throw new AssertionError("duplicates"); }, LinkedHashMap::new));
The Predicate class is quiet valuable even when not doing regex matching. The combination of the LITERAL and CASE_INSENSITIVE flags enables searches with the intended semantic without the need to convert entire strings to lower case (which, by the way, is not sufficient for all possible scenarios). For this kind of matching, the preparation will include building the necessary data structure for the Boyer–Moore Algorithm for more efficient search, internally.
This map can be reused.
For your specific task, one way to use it would be
filters.entrySet().stream()
.map(e -> Map.entry(e.getKey(), listofComments.stream().filter(e.getValue()).count()))
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.forEachOrdered(e -> System.out.printf("%-7s%3d%n", e.getKey(), e.getValue()));
which will print for your example data:
Mangoes 3
Apple 2
Oranges 2
Figs 1
Note that the filters map is already sorted alphabetically and the sorted of the second stream operation is stable for streams with a defined encounter order, so it only needs to sort by occurrences, the entries with equal elements will keep their relative order, which is the alphabetical order from the source map.
Map.entry(…) requires Java 9 or newer. For Java 8, you’d have to use something like
new AbstractMap.SimpleEntry(…) instead.
You can still modify your function to store Map.Entry instead of a complete Map
Function<String, Map.Entry<String, Long>> function = f -> Map.entry(f, listOfComments.stream()
.filter(e -> e.toLowerCase().contains(f.toLowerCase())).count());
and then sort these entries before performing a terminal operation forEach in your case to print
elements.stream()
.map(function)
.sorted(Comparator.comparing(Map.Entry<String, Long>::getValue)
.reversed().thenComparing(Map.Entry::getKey))
.forEach(System.out::println);
This will then give you as output the following:
Mangoes=3
Apples=2
Oranges=2
Figs=1
First thing is to declare an additional class. It'll hold element and count:
class ElementWithCount {
private final String element;
private final long count;
ElementWithCount(String element, long count) {
this.element = element;
this.count = count;
}
String element() {
return element;
}
long count() {
return count;
}
}
To compute count let's declare an additional function:
static long getElementCount(List<String> listOfComments, String element) {
return listOfComments.stream()
.filter(comment -> comment.contains(element))
.count();
}
So now to find the result we need to transform stream of elements to stream of ElementWithCount objects, then sort that stream by count, then transform it back to stream of elements and collect it into result list.
To make this task easier, let's define comparator as a separate variable:
Comparator<ElementWithCount> comparator = Comparator
.comparing(ElementWithCount::count).reversed()
.thenComparing(ElementWithCount::element);
and now as all parts are ready, final computation is easy:
List<String> result = elements.stream()
.map(element -> new ElementWithCount(element, getElementCount(listOfComments, element)))
.sorted(comparator)
.map(ElementWithCount::element)
.collect(Collectors.toList());
You can use Map.Entry instead of a separate class and inline getElementCount, so it'll be "one-line" solution:
List<String> result = elements.stream()
.map(element ->
new AbstractMap.SimpleImmutableEntry<>(element,
listOfComments.stream()
.filter(comment -> comment.contains(element))
.count()))
.sorted(Map.Entry.<String, Long>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey()))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
But it's much harder to understand in this form, so I recommend to split it to logical parts.

Java8 lambda approach

I have this piece of code that filters from a list of objects based on a set of String identifiers passed in and returns a map of string-id and objects. Something similar to follows:
class Foo {
String id;
String getId() {return id};
};
// Get map of id --> Foo objects whose string are in fooStr
Map<String,Foo> filterMethod (Set<String> fooStr) {
List<Foo> fDefs; // list of Foo objects
Map<String,Foo> fObjMap = new HashMap<String, Foo>(); // map of String to Foo objects
for (Foo f : fDefs) {
if (fooStr.contains(f.getId()))
fObjMap.put(f.getId(),f);
}
return (fObjMap);
}
Is there a better Java8 way of doing this using filter or map?
I could not figure it out and tried searching on stackoverflow but could not find any hints, so am posting as a question.
Any help is much appreciated.
~Ash
Just use the filter operator with the same predicate as above and then the toMap collector to build the map. Also notice that your iterative solution precludes any possibility of key conflict, hence, I have omitted that, too.
Map<String, Foo> idToFooMap = fDefs.stream()
.filter(f -> fooStr.contains(f.getId()))
.collect(Collectors.toMap(Foo::getId, f -> f));
When including items conditionally in the final output use filter and when going from stream to a map use Collectors.toMap. Here's what you end up with:
Map<String,Foo> filterMethod (final Set<String> fooStr) {
List<Foo> fDefs; // list of Foo objects
return fDefs.stream()
.filter(foo -> fooStr.contains(foo.getId()))
.collect(Collectors.toMap(Foo::getId, Function.identity()));
}
Though ggreiner has already provided a working solution, when there are duplicates you'd better handle it including a mergeFunction.
Directly using Collectors.toMap(keyMapper, valueMapper), one or another day you will encounter this following issue.
If the mapped keys contains duplicates (according to Object.equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys may have duplicates, use toMap(Function, Function, BinaryOperator) instead.
Based on the OP's solution, I think it would be better using
import static java.util.stream.Collectors.*; // save some typing and make it cleaner;
fDefs.stream()
.filter(foo -> fooStr.contains(foo.getId()))
.collect(toMap(Foo::getId, foo -> foo, (oldFoo, newFoo) -> newFoo));
Maybe something like this?
Map<String,Foo> filterMethod (Set<String> fooStr) {
List<Foo> fDefs; // get this list from somewhere
Map<String, Foo> fObjMap = new HashMap<> ();
fDefs.stream()
.filter(foo -> fooStr.contains(foo.getId()))
.forEach(foo -> fObjMap.put(foo.getId(), foo))
return fObjMap;
}

How to use if-else logic in Java 8 stream forEach

What I want to do is shown below in 2 stream calls. I want to split a collection into 2 new collections based on some condition. Ideally I want to do it in 1. I've seen conditions used for the .map function of streams, but couldn't find anything for the forEach. What is the best way to achieve what I want?
animalMap.entrySet().stream()
.filter(pair-> pair.getValue() != null)
.forEach(pair-> myMap.put(pair.getKey(), pair.getValue()));
animalMap.entrySet().stream()
.filter(pair-> pair.getValue() == null)
.forEach(pair-> myList.add(pair.getKey()));
Just put the condition into the lambda itself, e.g.
animalMap.entrySet().stream()
.forEach(
pair -> {
if (pair.getValue() != null) {
myMap.put(pair.getKey(), pair.getValue());
} else {
myList.add(pair.getKey());
}
}
);
Of course, this assumes that both collections (myMap and myList) are declared and initialized prior to the above piece of code.
Update: using Map.forEach makes the code shorter, plus more efficient and readable, as Jorn Vernee kindly suggested:
animalMap.forEach(
(key, value) -> {
if (value != null) {
myMap.put(key, value);
} else {
myList.add(key);
}
}
);
In most cases, when you find yourself using forEach on a Stream, you should rethink whether you are using the right tool for your job or whether you are using it the right way.
Generally, you should look for an appropriate terminal operation doing what you want to achieve or for an appropriate Collector. Now, there are Collectors for producing Maps and Lists, but no out of-the-box collector for combining two different collectors, based on a predicate.
Now, this answer contains a collector for combining two collectors. Using this collector, you can achieve the task as
Pair<Map<KeyType, Animal>, List<KeyType>> pair = animalMap.entrySet().stream()
.collect(conditional(entry -> entry.getValue() != null,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue),
Collectors.mapping(Map.Entry::getKey, Collectors.toList()) ));
Map<KeyType,Animal> myMap = pair.a;
List<KeyType> myList = pair.b;
But maybe, you can solve this specific task in a simpler way. One of you results matches the input type; it’s the same map just stripped off the entries which map to null. If your original map is mutable and you don’t need it afterwards, you can just collect the list and remove these keys from the original map as they are mutually exclusive:
List<KeyType> myList=animalMap.entrySet().stream()
.filter(pair -> pair.getValue() == null)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
animalMap.keySet().removeAll(myList);
Note that you can remove mappings to null even without having the list of the other keys:
animalMap.values().removeIf(Objects::isNull);
or
animalMap.values().removeAll(Collections.singleton(null));
If you can’t (or don’t want to) modify the original map, there is still a solution without a custom collector. As hinted in Alexis C.’s answer, partitioningBy is going into the right direction, but you may simplify it:
Map<Boolean,Map<KeyType,Animal>> tmp = animalMap.entrySet().stream()
.collect(Collectors.partitioningBy(pair -> pair.getValue() != null,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
Map<KeyType,Animal> myMap = tmp.get(true);
List<KeyType> myList = new ArrayList<>(tmp.get(false).keySet());
The bottom line is, don’t forget about ordinary Collection operations, you don’t have to do everything with the new Stream API.
The problem by using stream().forEach(..) with a call to add or put inside the forEach (so you mutate the external myMap or myList instance) is that you can run easily into concurrency issues if someone turns the stream in parallel and the collection you are modifying is not thread safe.
One approach you can take is to first partition the entries in the original map. Once you have that, grab the corresponding list of entries and collect them in the appropriate map and list.
Map<Boolean, List<Map.Entry<K, V>>> partitions =
animalMap.entrySet()
.stream()
.collect(partitioningBy(e -> e.getValue() == null));
Map<K, V> myMap =
partitions.get(false)
.stream()
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
List<K> myList =
partitions.get(true)
.stream()
.map(Map.Entry::getKey)
.collect(toList());
... or if you want to do it in one pass, implement a custom collector (assuming a Tuple2<E1, E2> class exists, you can create your own), e.g:
public static <K,V> Collector<Map.Entry<K, V>, ?, Tuple2<Map<K, V>, List<K>>> customCollector() {
return Collector.of(
() -> new Tuple2<>(new HashMap<>(), new ArrayList<>()),
(pair, entry) -> {
if(entry.getValue() == null) {
pair._2.add(entry.getKey());
} else {
pair._1.put(entry.getKey(), entry.getValue());
}
},
(p1, p2) -> {
p1._1.putAll(p2._1);
p1._2.addAll(p2._2);
return p1;
});
}
with its usage:
Tuple2<Map<K, V>, List<K>> pair =
animalMap.entrySet().parallelStream().collect(customCollector());
You can tune it more if you want, for example by providing a predicate as parameter.
I think it's possible in Java 9:
animalMap.entrySet().stream()
.forEach(
pair -> Optional.ofNullable(pair.getValue())
.ifPresentOrElse(v -> myMap.put(pair.getKey(), v), v -> myList.add(pair.getKey())))
);
Need the ifPresentOrElse for it to work though. (I think a for loop looks better.)

Categories

Resources