I have this situation where it seems that the Java 8 Streams API would be helpful, but I'm not fully sure how it could be.
From two Collections with distinct element types, I want to build a third collection whose elements are all the possible Pairs of elements from both collections. Basically:
The two distinct element types...
public class A {}
public class B {}
A "pair" of As and Bs.
public class Pair {
private A a;
private B b;
public Pair(A a, B b){
this a = a;
this b = b;
}
}
The " combination " made using old-style java.util.Collection
API:
public Collection<Pair> combine(Collection<A> as, Collection<B> bs){
Collection<Pair> pairs = new ArrayList();
foreach(A a: as){
foreach(B b: bs){
Pair pair = new Pair(a,b);
pairs.add(pair);
}
}
return pairs;
}
The ordering in the resulting pairs collection is not important. So, every instance of Pair could be created and added to the resulting collection in parallel. How could I achieve this?
The best I could figure out by myself was to use the Streams version of foreach
:
as.foreach(
a -> {
bs.foreach(
b -> {
Pair pair = new Pair(a,b);
pairs.add(pair);
}
}
);
This example was made trivial for the sake of simplification. The class Pair
is an example of processing two elements into a third one (that is, a java.util.function.BiFunction
), and adding them to a Collection
is just an example of a mutable reduction.
Is there a more elegant way to do that? Or preferable, in a more profitable way in regards to efficiency? Something like
BiFunction<A,B,Pair> combinator = Pair::new; //or any other function f(a,b)=c;
Stream<Pair> pairStream =
Streams.unknownElegantMethod(as.stream(), bs.stream(), combinator);
I hope I don't have any silly typos, but basically what you can do is :
List<Pair> list = as
.stream()
.flatMap(a -> bs.stream().map (b -> new Pair(a,b)))
.collect (Collectors.toList());
Stream<A>
from as
. a
instance Stream<B>
of bs
b
to a pair of (a,b)
If you're open to using a third-party library, you could use Eclipse Collections Sets.cartesianProduct()
. This would require that your a's and b's are both Sets. Eclipse Collections has a Pair
type built-in, so you wouldn't need to create it.
public class A {}
public class B {}
public List<Pair<A, B>> combine(Set<A> as, Set<B> bs)
{
return Sets.cartesianProduct(as, bs).toList();
}
If your a's and b's are not Sets, then you could use a CollectionAdapter
flatCollect
and collect
, which are equivalent to flatMap
and map
on Stream
.
public Collection<Pair<A, B>> combine(Collection<A> as, Collection<B> bs)
{
MutableCollection<B> adaptB = CollectionAdapter.adapt(bs);
return CollectionAdapter.adapt(as)
.flatCollect(a -> adaptB.asLazy().collect(b -> Tuples.pair(a, b)));
}
Another possible option using Stream
would be to define your own Collector
for cartesianProduct
. This is more complex than the other Stream
solution, and would only be useful if you used cartesianProduct
a few times in your code.
List<Pair<A, B>> pairs = as.stream().collect(cartesianProduct(bs));
public static <T1, T2> Collector<T1, ?, List<Pair<T1, T2>>>
cartesianProduct(Collection<T2> other)
{
return Collector.of(
ArrayList::new,
(list, a) -> list.addAll(
other.stream().map(b -> new Pair(a, b))).collect(Collectors.toList())),
(list1, list2) ->
{
list1.addAll(list2);
return list1;
},
Collector.Characteristics.UNORDERED
);
}
Note: I am a committer for Eclipse Collections .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.