I am playing around with lazy functional operations in Java SE 8, and I want to map
an index i
to a pair / tuple (i, value[i])
, then filter
based on the second value[i]
element, and finally output just the indices.
Must I still suffer this: What is the equivalent of the C++ Pair<L,R> in Java? in the bold new era of lambdas and streams?
Update: I presented a rather simplified example, which has a neat solution offered by @dkatzel in one of the answers below. However, it does not generalize. Therefore, let me add a more general example:
package com.example.test;
import java.util.ArrayList;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
boolean [][] directed_acyclic_graph = new boolean[][]{
{false, true, false, true, false, true},
{false, false, false, true, false, true},
{false, false, false, true, false, true},
{false, false, false, false, false, true},
{false, false, false, false, false, true},
{false, false, false, false, false, false}
};
System.out.println(
IntStream.range(0, directed_acyclic_graph.length)
.parallel()
.mapToLong(i -> IntStream.range(0, directed_acyclic_graph[i].length)
.filter(j -> directed_acyclic_graph[j][i])
.count()
)
.filter(n -> n == 0)
.collect(() -> new ArrayList<Long>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
);
}
}
This gives incorrect output of [0, 0, 0]
which corresponds to the counts for the three columns that are all false
. What I need are the indices of these three columns. The correct output should be [0, 2, 4]
. How can I get this result?
This question is related to
java
lambda
functional-programming
java-8
java-stream
Since Java 9, you can create instances of Map.Entry
easier than before:
Entry<Integer, String> pair = Map.entry(1, "a");
Map.entry
returns an unmodifiable Entry
and forbids nulls.
Since you only care about the indexes, you don't need to map to tuples at all. Why not just write a filter that uses the looks up elements in your array?
int[] value = ...
IntStream.range(0, value.length)
.filter(i -> value[i] > 30) //or whatever filter you want
.forEach(i -> System.out.println(i));
Vavr (formerly called Javaslang) (http://www.vavr.io) provides tuples (til size of 8) as well. Here is the javadoc: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html.
This is a simple example:
Tuple2<Integer, String> entry = Tuple.of(1, "A");
Integer key = entry._1;
String value = entry._2;
Why JDK itself did not come with a simple kind of tuples til now is a mystery to me. Writing wrapper classes seems to be an every day business.
Yes.
Map.Entry
can be used as a Pair
.
Unfortunately it does not help with Java 8 streams as the problem is that even though lambdas can take multiple arguments, the Java language only allows for returning a single value (object or primitive type). This implies that whenever you have a stream you end up with being passed a single object from the previous operation. This is a lack in the Java language, because if multiple return values was supported AND streams supported them we could have much nicer non-trivial tasks done by streams.
Until then, there is only little use.
EDIT 2018-02-12: While working on a project I wrote a helper class which helps handling the special case of having an identifier earlier in the stream you need at a later time but the stream part in between does not know about it. Until I get around to release it on its own it is available at IdValue.java with a unit test at IdValueTest.java
Sadly, Java 8 did not introduce pairs or tuples. You can always use org.apache.commons.lang3.tuple of course (which personally I do use in combination with Java 8) or you can create your own wrappers. Or use Maps. Or stuff like that, as is explained in the accepted answer to that question you linked to.
UPDATE: JDK 14 is introducing records as a preview feature. These aren't tuples, but can be used to save many of the same problems. In your specific example from above, that could look something like this:
public class Jdk14Example {
record CountForIndex(int index, long count) {}
public static void main(String[] args) {
boolean [][] directed_acyclic_graph = new boolean[][]{
{false, true, false, true, false, true},
{false, false, false, true, false, true},
{false, false, false, true, false, true},
{false, false, false, false, false, true},
{false, false, false, false, false, true},
{false, false, false, false, false, false}
};
System.out.println(
IntStream.range(0, directed_acyclic_graph.length)
.parallel()
.mapToObj(i -> {
long count = IntStream.range(0, directed_acyclic_graph[i].length)
.filter(j -> directed_acyclic_graph[j][i])
.count();
return new CountForIndex(i, count);
}
)
.filter(n -> n.count == 0)
.collect(() -> new ArrayList<CountForIndex>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
);
}
}
When compiled and run with JDK 14 (at the time of writing, this an early access build) using the --enable-preview
flag, you get the following result:
[CountForIndex[index=0, count=0], CountForIndex[index=2, count=0], CountForIndex[index=4, count=0]]
You can have a look on these built-in classes :
Eclipse Collections has Pair
and all combinations of primitive/object Pairs (for all eight primitives).
The Tuples
factory can create instances of Pair
, and the PrimitiveTuples
factory can be used to create all combinations of primitive/object pairs.
We added these before Java 8 was released. They were useful to implement key/value Iterators for our primitive maps, which we also support in all primitive/object combinations.
If you're willing to add the extra library overhead, you can use Stuart's accepted solution and collect the results into a primitive IntList
to avoid boxing. We added new methods in Eclipse Collections 9.0 to allow for Int/Long/Double
collections to be created from Int/Long/Double
Streams.
IntList list = IntLists.mutable.withAll(intStream);
Note: I am a committer for Eclipse Collections.
It appears that the full example can be solved without the use of any kind of Pair structure. The key is to filter on the column indexes, with the predicate checking the entire column, instead of mapping the column indexes to the number of false
entries in that column.
The code that does this is here:
System.out.println(
IntStream.range(0, acyclic_graph.length)
.filter(i -> IntStream.range(0, acyclic_graph.length)
.noneMatch(j -> acyclic_graph[j][i]))
.boxed()
.collect(toList()));
This results in output of [0, 2, 4]
which is I think the correct result requested by the OP.
Also note the boxed()
operation that boxes the int
values into Integer
objects. This enables one to use the pre-existing toList()
collector instead having to write out collector functions that do the boxing themselves.
Source: Stackoverflow.com