Instead of using an auxiliary list to hold the indices, you can have them generated by an IntStream.
Map<Double, String> map = IntStream.range(0, list1.size())
.boxed()
.collect(Collectors.toMap(i -> list1.get(i), i -> list2.get(i)));
Answer from svarog on Stack OverflowInstead of using an auxiliary list to hold the indices, you can have them generated by an IntStream.
Map<Double, String> map = IntStream.range(0, list1.size())
.boxed()
.collect(Collectors.toMap(i -> list1.get(i), i -> list2.get(i)));
Indeed the best approach is to use IntStream.range(startInclusive, endExclusive) in order to access to each element of both lists with get(index) and finally use Math.min(a, b) to avoid getting IndexOutOfBoundsException if the lists are not of the exact same size, so the final code would be:
Map<Double, String> map2 = IntStream.range(0, Math.min(list1.size(), list2.size()))
.boxed()
.collect(Collectors.toMap(list1::get, list2::get));
Java 8 stream with two Lists - Stack Overflow
java - Set object properties in a list based on matching object in another list using the Stream API - Stack Overflow
java - Streaming two lists into a map - Stack Overflow
collections - Java 8 Collect two Lists to Map by condition - Stack Overflow
Seems that this is what you are after:
customers.stream()
.filter(c -> c.getTrack() != null)
.map(c -> c.getTrack().getId())
.flatMap(id -> tracks.stream().filter(track -> Long.valueOf(track.getId()).equals(id)))
.collect(Collectors.toSet());
Just note that for each id you are iterating the entire list of tracks; this has O(n*m) complexity. This is generally see as bad and you can improve it.
To make it better you would first create a HashSet of ids from Customer; having that HashSet you can now call contains on it with the ids you are interested in, since contains has a time complexity of O(1) (it's really called amortized complexity of O(1)). So now your complexity becomes O(n) + O(1), but since O(1) is a constant, it's really O(n) - much better that what you had before. In code:
Set<Long> set = customers.stream()
.filter(c -> c.getTrack() != null)
.map(c -> c.getTrack().getId())
.collect(Collectors.toSet());
Set<CusomerTrack> tracksToSave = tracks.stream()
.filter(track -> set.contains(track.getId())
.collect(Collectors.toSet()));
An additional way favoring method reference usage :
Set<Track> tracks =
customers.stream()
.map(Customer::getTrack) // customer to track
.filter(Objects::nonNull) // keep non null track
.map(Track::getId) // track to trackId
.flatMap(trackId -> tracks.stream() // collect tracks matching with trackId
.filter(t-> Long.valueOf(t.getId()).equals(trackId))
)
.collect(toSet());
Just to be clear, I think your code is intended to do the following: update the name of each item in list1 to be the name of any item in list2 that has the same ID. There doesn't seem to be anything checking if the names of items in list1 are null.
If that's correct, then:
list2.forEach(obj2 -> list1.stream()
.filter(obj1 -> obj1.getId().equals(obj2.getId()))
.forEach(obj1 -> obj1.setName(obj2.getName()));
If you want to check if name is null, then add a new filter before setting the name:
.filter(Objects::isNull)
As I mentioned in the comments. If the id is a uniqe identifier for your objects, then a Map is more appropriate than a List.
So you better work on such a map (assuming id is an integer):
Map<Integer, Object1> obj1map;
You could create that map from your first list with
obj1map = list1.stream().collect(toMap(Object1::getId, Function.identity()));
Now you can stream over your second list and update the map accordingly:
list2
.stream()
.filter(o -> o.getName() != null) // remove null names
.filter(o -> obj1map.containsKey(o.getId())) // just to make sure
.forEach(o -> obj1map.get(o.getId()).setName(o.getName()));
This should be optimal. You first build a map from the currencies to their commercial banks. Then you run through your centrals building a map from commercial to central (looked up in the first map).
List<CurrencyItem> currenciesByCommercialBank = new ArrayList<>();
List<CurrencyItem> currenciesByCentralBank = new ArrayList<>();
// Build my lookup from CurrencyName to CommercialBank.
Map<CurrencyName, CurrencyItem> commercials = currenciesByCommercialBank
.stream()
.collect(
Collectors.toMap(
// Map from currency name.
ci -> ci.getName(),
// To the commercial bank itself.
ci -> ci));
Map<CurrencyItem, CurrencyItem> commercialToCentral = currenciesByCentralBank
.stream()
.collect(
Collectors.toMap(
// Map from the equivalent commercial
ci -> commercials.get(ci.getName()),
// To this central.
ci -> ci
));
The following code is O(n2), but it should be OK for small collections (which your lists probably are):
return currenciesByCommercialBank
.stream()
.map(c ->
new AbstractMap.SimpleImmutableEntry<>(
c, currenciesByCentralBank.stream()
.filter(c2 -> c.currencyName == c2.currencyName)
.findFirst()
.get()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
The above is appropriate if you want to assert that currenciesByCentralBank contains a match for each item in currenciesByCommercialBank. If the two lists can have mismatches, then the following would be appropriate:
currenciesByCommercialBank
.stream()
.flatMap(c ->
currenciesByCentralBank.stream()
.filter(c2 -> c.currencyName == c2.currencyName)
.map(c2 -> new AbstractMap.SimpleImmutableEntry<>(c, c2)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
In this case the map will contain all the matches and won't complain about missing entries.
Can we collect two lists from Java 8 streams?
You cannot but if you have a way to group elements of the Lists according to a condition.
In this case you could for example use Collectors.groupingBy() that will return Map<Foo, List<Bar>> where the values of the Map are the two List.
Note that in your case you don't need stream to do only filter.
Filter the invalid list with removeIf() and add all element of that in the first list :
invalidList.removeIf(o -> conditionToRemove);
goodList.addAll(invalidList);
If you don't want to change the state of goodList you can do a shallow copy of that :
invalidList.removeIf(o -> conditionToRemove);
List<Foo> terminalList = new ArrayList<>(goodList);
terminalList.addAll(invalidList);
One way using Java 8 streams API is to use the Collectors.partitioningBy collector.
Consider I have a List of String elements: the input list has strings with various lengths; only strings with length 3 are valid.
List<String> input = Arrays.asList("one", "two", "three", "four", "five");
Map<Boolean, List<String>> map =
input.collect(Collectors.partitioningBy(s -> s.length() == 3));
System.out.println(map); // {false=[three, four, five], true=[one, two]}
List<String> valid = map.get(true); // [one, two]
List<String> invalid = map.get(false); // [three, four, five]
The resulting Map has only two records; one with valid and the other with not-valid input list elements. The map record with key=true has the valid string as a List: one, two. The other is a key=false and the not-valid strings: three, four, five.
Note that Collectors.partitioningBy produces always two records in the resulting map irrespective of the existence of valid or not-valid values.
use IntStream and collect to a map:
IntStream.range(0, a.length)
.boxed()
.collect(toMap(i -> a[i], i -> b[i]));
I’d probably go for the solution by Ousmane D. myself. For the sake of completeness, if you don’t like the boxing of the IntStream (it feels unnecessary), you may do for example:
Double[] a = new Double[]{1.,2.,3.};
Double[] b = new Double[]{10.,20.,30.};
Map<Double, Double> myMap = IntStream.range(0, a.length)
.collect(HashMap::new, (m, i) -> m.put(a[i], b[i]), Map::putAll);
System.out.println(myMap);
Output from this snippet is:
{1.0=10.0, 2.0=20.0, 3.0=30.0}
As the code stands, there is an important difference between the working of the code by Ousmane D. and my code though: That code checks for duplicate keys and objects if there are any. My code tacitly drops them. My code could be extended to include the check too, but it would add a complication that I don’t think we want.
Why the boxed() call helps: the collectors in the Collectors class, of which Ousmane D. used toMap, work only on streams of objects, not on streams of primitives like IntStream.
Link: Answer by Ousmane D.
Ah now I understand what you want :) I don't know if there is a better way with streams but here is a solution which would work.
List<Map<String, String>> resultList = l1.stream()
.map(m1 -> {
Map<String, String> map = new HashMap<>();
map.putAll(m1);
l2.stream()
.filter(m2 -> map.get("ID").equals(m2.get("ID")))
.findFirst()
.ifPresent(m2 -> map.put("director", m2.get("director")));
return map;
})
.collect(Collectors.toList());
The above code generates a new List resultList and does not modify the other lists List1 and List2. If it does not matter if List1 gets modified or not you could do it in a cleaner, more readable way.
l1.forEach(m1 -> l2.stream()
.filter(m2 -> m1.get("ID").equals(m2.get("ID")))
.findFirst()
.ifPresent(m2 -> m1.putIfAbsent("director", m2.get("director"))));
This way the entries of list1 get modified. So with the above example list1 is becomes the joined list. But it's actually good practice to have methods without any side effects. So I would not prefer the above example.
I would recommend a method getJoinedList which returns a new List and does not modify the other lists. And in this case I would not use streams but the old-fashioned for-loop.
private static List<Map<String, String>> getJoinedList(
List<Map<String, String>> l1, List<Map<String, String>> l2) {
List<Map<String, String>> result = new ArrayList<>();
for (Map<String, String> m1 : l1) {
Map<String, String> newMap = new HashMap<>();
newMap.putAll(m1);
for (Map<String, String> m2 : l2) {
if (m1.get("ID").equals(m2.get("ID"))) {
newMap.put("director", m2.get("director"));
break;
}
}
result.add(newMap);
}
return result;
}
Then you just can call the method like this.
List<Map<String, String>> joinedList = getJoinedList(l1, l2);
If performance matters, you should first build an index of directors:
Map<Object, Object> directors = list2.stream()
.collect(Collectors.toMap(m -> m.get("ID"), m -> m.get("director")));
Then you can merge the directors to the list entries easily:
list1.stream().forEach(m -> m.put("director", directors.get(m.get("ID"))));
Accesing the director via a Map will be faster than searching the director for each list entry.
Using Collectors.groupingBy, you can generate a Map from your Key to a List of Values, provided you can compute the Key from the Value. Alternatively, you can use Collectors.toMap, provided you can compute both the Key and the Value from an upstream element. You probably want the version of toMap with a merge function, because that will allow you to handle multiple keys with the same value (by putting them in a list together).
Edit:
If you want ordering, there are overloads for toMap and groupingBy that allow you to provide a mapFactory (Supplier<Map>) , such as TreeMap::new.
To invert a map, so that its distinct values become keys, and its keys are added to a collection under the corresponding value, use groupingBy() on the map entries. It's important that the values from the original map implement equals() and hashCode() correctly to be used as a key in the new hash table .
static <K, V> Map<V, Set<K>> invert(Map<? extends K, ? extends V> original) {
return original.entrySet().stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toSet())
));
}
If you want to groups to be sorted, you can create a specialized "downstream" collector:
static <K, V> Map<V, SortedSet<K>> invert(
Map<? extends K, ? extends V> original,
Comparator<? super K> order) {
Collector<K, ?, SortedSet<K>> toSortedSet =
Collectors.toCollection(() -> new TreeSet<>(order));
return original.entrySet().stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, toSortedSet)
));
}
I have two lists containing maps, the four keys are instrument, party, quantity, and entity.
If the values match between query1 and query2 results I want to add the matching maps into a list. So we can tell map1 in query1 matches map3 in query2. This is for reconciliation of data where there will be a plethora of records so essentially we want to go through each map in query1 results and compare to all maps in query2 results until there is a match. Once there is a match, we want to create a unique identifier or some sort of a pair to show that the two maps are equal and thus can be deemed a match. If we go through a full iteration and map1 in query1 does not equal any maps in query 2 we can then mark map1 in query1 a break, which would be adding the key to a break list.
Please let me know if you need more information. I've added the 3 different attempts I have tried, however none seem to make a match.
public void reconcile(LocalDate date) {
List<Map<String, Object>> query1Records;
List<Map<String, Object>> query2Records;
try (Stream<Map<String, Object>> stream = executeQuery1(date)) {
query1Records = stream.collect(Collectors.toList());
}
try (Stream<Map<String, Object>> stream = executeQuery2(date)) {
query2Records = stream.collect(Collectors.toList());
}
// ATTEMPT #1
List<Map<String,Object>> unMatchedRecords = query1Records.parallelStream().filter(searchData ->
query2Records.parallelStream().noneMatch( inputMap ->
searchData.entrySet().stream().noneMatch(value ->
inputMap.entrySet().stream().noneMatch(value1 ->
(value1.getKey().equals(value.getKey()) &&
value1.getValue().equals(value.getValue()))))
)).collect(Collectors.toList());
// ATTEMPT #2
List<Map<String, Object>> matching = query1Records.parallelStream().filter(searchData ->
query2Records.parallelStream().anyMatch(inputMap ->
String.valueOf(searchData.get("quantity")).equals(inputMap.get("quantity"))
&& searchData.get("instrument").equals(inputMap.get("instrument"))
&& searchData.get("entity").equals(inputMap.get("entity"))
&& searchData.get("party").equals(inputMap.get("party"))))
.collect(Collectors.toList());
// ATTEMPT #3
List<String> keysToReconcile = Arrays.asList("quantity", "instrument", "entity", "party");
Function<Map<String,Object>,List<?>> getKey
= m -> keysToReconcile.stream().map(m::get).collect(Collectors.toList());
Map<List<?>,Map<String, Object>> inputKeys = query1Records.stream()
.collect(Collectors.toMap(
getKey,
m -> m,
(a,b) -> { throw new IllegalStateException("duplicate "+a+" and "+b); },
LinkedHashMap::new));
List<Map<String,Object>> matchingRecords = query2Records.stream()
.filter(m -> inputKeys.containsKey(getKey.apply(m)))
.collect(Collectors.toList());
matchingRecords.forEach(m -> inputKeys.remove(getKey.apply(m)));
List<Map<String,Object>> notMatchinRecords = new ArrayList<>(inputKeys.values());
}
}