This all depends on how complex you want to get, but the long story short is that you have to make a new stream for your second list each time, so you cannot be given two streams -- you can get one stream (which you act upon) and a Collection
/List
, but not two streams. This is because you cannot stream1.forEach(stream2)
without throwing an exception that the second stream was closed or run through.
Here's my setup:
public static void main(String[] args) {
Collection<List<String>> list1 = new ArrayList<>(5);
Collection<List<String>> list2 = new ArrayList<>(5);
list1.add(Arrays.stream(new String[] {"ABC", "123", "456"}).collect(Collectors.toList()));
list1.add(Arrays.stream(new String[] {"DEF", "234", "567"}).collect(Collectors.toList()));
list1.add(Arrays.stream(new String[] {"GHI", "345", "678"}).collect(Collectors.toList()));
list2.add(Arrays.stream(new String[] {"ABC", "789", "012"}).collect(Collectors.toList()));
list2.add(Arrays.stream(new String[] {"DEF", "890", "123"}).collect(Collectors.toList()));
list2.add(Arrays.stream(new String[] {"GHI", "901", "234"}).collect(Collectors.toList()));
}
Now, depending on how much checking you have to do and if you know that both streams have entries with matching keys (really a map would be better here than two Collections), as well as if you can skip a lot of error checks and so-on:
private static Stream<List<String>> combineTwoStreams(Collection<List<String>> list1,
Collection<List<String>> list2) {
Map<String, List<String>> newCollection = new LinkedHashMap<>();
list1.stream()
// Run through each entry in list1
.forEach(row -> {
// Find the entries in list2 that have the same 'key' and add them to newCollection
list2.stream().filter(row2 -> row.get(0).equals(row2.get(0))).forEach(row2 -> {
String key = row.get(0);
// Pull from newCollection
List<String> fromNewCollection = newCollection.get(key);
// If there wasn't an entry in newCollection for the key, add one now and pre-populate
// with list1's data
if (Objects.isNull(fromNewCollection)) {
fromNewCollection = new LinkedList<>(row);
newCollection.put(key, fromNewCollection);
}
// Add list2's data to the new collection (note that we can do an addAll instead
row2.subList(1, row2.size()).forEach(fromNewCollection::add);
});
});
// Return a Stream of our values
return newCollection.values().stream();
}
Now, if you have to also check list2's stuff, you probably want this:
private static Stream<List<String>> combineTwoStreams(Collection<List<String>> list1, Collection<List<String>> list2) {
Map<String, List<String>> newCollection = new LinkedHashMap<>();
list1.forEach(row -> newCollection.put(row.get(0), row));
list2.forEach(row -> {
String key = row.get(0);
// Pull from newCollection
List<String> fromNewCollection = newCollection.get(key);
// If there wasn't an entry in newCollection for the key, add one now and pre-populate
// with list1's data
if (Objects.isNull(fromNewCollection)) {
fromNewCollection = new LinkedList<>(row);
newCollection.put(key, fromNewCollection);
} else
// Add list2's data to the new collection (note that we can do an addAll instead
row.subList(1, row.size()).forEach(fromNewCollection::add);
});
// Return a Stream of our values
return newCollection.values().stream();
}
If you want to prevent duplicates, a LinkedHashSet
is great, so you might have newCollection
be Map<String, Set<String>> newCollection = new LinkedHashMap<>();
and then you would simply make LinkedHashSet
s.
And lastly, if you like to make yourself cross-eyed:
private static Stream<List<Object>> combineTwoStreams(Collection<List<String>> list1, Collection<List<String>> list2) {
Map<String, List<String>> map1 = // Turn our first list into map map
list1.stream().collect(Collectors.toMap(row1 -> row1.get(0), row1 -> row1));
Map<String, List<String>> map2 = // Turn our second list into a map
list2.stream().collect(Collectors.toMap(row2 -> row2.get(0), row2 -> row2.subList(1, row2.size())));
return Stream
.of(map1, map2)
// Give me the EntrySets as a stream
.flatMap(m -> m.entrySet().stream())
// Collect it all down
.collect(
// Group together things by the "key" so that we have a Map<String,List<List<String>>>
Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
// Merge the lists within each list down so that we have
// Map<String,List<String>>
Collectors.collectingAndThen(
Collectors.toList(),
row -> row.stream().flatMap(Collection::stream)
.collect(Collectors.toList())))))
// Get the values from our Map
.values()
// Return the stream of List<String>
.stream();
}
> result = zip(list1.stream(), list2.stream(), (l1, l2) -> { List l = new ArrayList<>(l1); l.addAll(l2.subList(1, l2.size())); return l; }).collect(toList());` And that's it. It's only complicated because there's no simple functional way to `addAll`... I really wish `zip` existed in the Stream API.
– Tunaki Apr 19 '16 at 21:23