I'm a little green on this functional programming and streams stuff, but what little I do know has been very useful!
I've had this situation come up several times:
List<SomeProperty> distinctProperties = someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.toList());
if (distinctProperties.size() == 1) {
SomeProperty commonProperty = distinctProperties.get(0);
// take some action knowing that all share this common property
}
What I really want is:
Optional<SomeProperty> universalCommonProperty = someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.singleOrEmpty());
I think the singleOrEmpty
thing can be useful in other situations besides just in combination with distinct
. When I was an uber n00b I spent a lot of time reinventing the Java Collections Framework because I didn't know it was there, so I'm trying not to repeat my mistakes. Does Java come with a good way to do this singleOrEmpty
thing? Am I formulating it wrong?
Thanks!
EDIT: Here's some example data for the distinct
case. If you ignore the map
step:
Optional<SomeProperty> universalCommonProperty = someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.singleOrEmpty());
[] -> Optional.empty()
[1] -> Optional.of(1)
[1, 1] -> Optional.of(1)
[2, 2] -> Optional.of(2)
[1, 2] -> Optional.empty()
I find I need this when I screw up my types, or have legacy code. It's really nice to be able to quickly say "All the elements of this collection share this property, so now I can take some action using this shared property." Another example is when a user multi-selects some diverse elements, and you're trying to see what stuff you can do (if anything) that's valid for all of them.
EDIT2: Sorry if my example is a misleading. The key is singleOrEmpty. I commonly find that I put a distinct
in front, but it could just as easily be a filter
of some other kind.
Optional<SomeProperty> loneSpecialItem = someList.stream()
.filter(obj -> obj.isSpecial())
.collect(Collectors.singleOrEmpty());
[special] -> Optional.of(special)
[special, special] -> Optional.empty()
[not] -> Optional.empty()
[not, special] -> Optional.of(special)
[not, special, not] -> Optional.of(special)
EDIT3: I think I screwed up by motivating the singleOrEmpty instead of just asking for it on its own.
Optional<Int> value = someList.stream().collect(Collectors.singleOrEmpty())
[] -> Optional.empty()
[1] -> Optional.of(1)
[1, 1] -> Optional.empty()
In Java 8 Stream, filter with Set. Add() is the fastest algorithm to find duplicate elements, because it loops only one time. Set<T> items = new HashSet<>(); return list. stream() .
To find an element matching specific criteria in a given list, we: invoke stream() on the list. call the filter() method with a proper Predicate. call the findAny() construct, which returns the first element that matches the filter predicate wrapped in an Optional if such an element exists.
distinct() returns a stream consisting of distinct elements in a stream. distinct() is the method of Stream interface. This method uses hashCode() and equals() methods to get distinct elements. In case of ordered streams, the selection of distinct elements is stable.
With Eclipse Collections (formerly GS Collections), you can make use of a data structure called Bag that can hold the number of occurrences of each element. Using IntBag , the following will work: MutableList<Person> personsEC = ListAdapter. adapt(persons); IntBag intBag = personsEC.
This will incur an overhead of creating a set but it's simple and will work correctly even if you forget to distinct() the stream first.
static<T> Collector<T,?,Optional<T>> singleOrEmpty() { return Collectors.collectingAndThen( Collectors.toSet(), set -> set.size() == 1 ? set.stream().findAny() : Optional.empty() ); }
"Hacky" solution that only evaluates the first two elements:
.limit(2)
.map(Optional::ofNullable)
.reduce(Optional.empty(),
(a, b) -> a.isPresent() ^ b.isPresent() ? b : Optional.empty());
Some basic explanation:
Single element [1] -> map to [Optional(1)] -> reduce does
"Empty XOR Present" yields Optional(1)
= Optional(1)
Two elements [1, 2] -> map to [Optional(1), Optional(2)] -> reduce does:
"Empty XOR Present" yields Optional(1)
"Optional(1) XOR Optional(2)" yields Optional.Empty
= Optional.Empty
Here is the complete testcase:
public static <T> Optional<T> singleOrEmpty(Stream<T> stream) {
return stream.limit(2)
.map(Optional::ofNullable)
.reduce(Optional.empty(),
(a, b) -> a.isPresent() ^ b.isPresent() ? b : Optional.empty());
}
@Test
public void test() {
testCase(Optional.empty());
testCase(Optional.of(1), 1);
testCase(Optional.empty(), 1, 1);
testCase(Optional.empty(), 1, 1, 1);
}
private void testCase(Optional<Integer> expected, Integer... values) {
Assert.assertEquals(expected, singleOrEmpty(Arrays.stream(values)));
}
Kudos to Ned (the OP) who has contributed the XOR idea and the above testcase!
If you don't mind using Guava, you can wrap your code with Iterables.getOnlyElement
, so it would look something like that:
SomeProperty distinctProperty = Iterables.getOnlyElement(
someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.toList()));
IllegalArgumentException
will be raised if there is more than one value or no value, there is also a version with default value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With