Been a while since this question was asked but these days I'm partial to something like:
public static <K, V> Map<K, V> zipToMap(List<K> keys, List<V> values) {
return IntStream.range(0, keys.size()).boxed()
.collect(Collectors.toMap(keys::get, values::get));
}
For those unfamiliar with streams, what this does is gets an IntStream from 0 to the length, then boxes it, making it a Stream<Integer> so that it can be transformed into an object, then collects them using Collectors.toMap which takes two suppliers, one of which generates the keys, the other the values.
This could stand some validation (like requiring keys.size() be less than values.size()) but it works great as a simple solution.
EDIT: The above works great for anything with constant time lookup, but if you want something that will work on the same order (and still use this same sort of pattern) you could do something like:
public static <K, V> Map<K, V> zipToMap(List<K> keys, List<V> values) {
Iterator<K> keyIter = keys.iterator();
Iterator<V> valIter = values.iterator();
return IntStream.range(0, keys.size()).boxed()
.collect(Collectors.toMap(_i -> keyIter.next(), _i -> valIter.next()));
}
The output is the same (again, missing length checks, etc.) but the time complexity isn't dependent on the implementation of the get method for whatever list is used.
Been a while since this question was asked but these days I'm partial to something like:
public static <K, V> Map<K, V> zipToMap(List<K> keys, List<V> values) {
return IntStream.range(0, keys.size()).boxed()
.collect(Collectors.toMap(keys::get, values::get));
}
For those unfamiliar with streams, what this does is gets an IntStream from 0 to the length, then boxes it, making it a Stream<Integer> so that it can be transformed into an object, then collects them using Collectors.toMap which takes two suppliers, one of which generates the keys, the other the values.
This could stand some validation (like requiring keys.size() be less than values.size()) but it works great as a simple solution.
EDIT: The above works great for anything with constant time lookup, but if you want something that will work on the same order (and still use this same sort of pattern) you could do something like:
public static <K, V> Map<K, V> zipToMap(List<K> keys, List<V> values) {
Iterator<K> keyIter = keys.iterator();
Iterator<V> valIter = values.iterator();
return IntStream.range(0, keys.size()).boxed()
.collect(Collectors.toMap(_i -> keyIter.next(), _i -> valIter.next()));
}
The output is the same (again, missing length checks, etc.) but the time complexity isn't dependent on the implementation of the get method for whatever list is used.
I'd often use the following idiom. I admit it is debatable whether it is clearer.
Iterator<String> i1 = names.iterator();
Iterator<String> i2 = things.iterator();
while (i1.hasNext() && i2.hasNext()) {
map.put(i1.next(), i2.next());
}
if (i1.hasNext() || i2.hasNext()) complainAboutSizes();
It has the advantage that it also works for Collections and similar things without random access or without efficient random access, like LinkedList, TreeSets or SQL ResultSets. For example, if you'd use the original algorithm on LinkedLists, you've got a slow Shlemiel the painter algorithm which actually needs n*n operations for lists of length n.
As 13ren pointed out, you can also use the fact that Iterator.next throws a NoSuchElementException if you try to read after the end of one list when the lengths are mismatched. So you'll get the terser but maybe a little confusing variant:
Iterator<String> i1 = names.iterator();
Iterator<String> i2 = things.iterator();
while (i1.hasNext() || i2.hasNext()) map.put(i1.next(), i2.next());
Creating Map composed of 2 Lists using stream().collect in Java - Stack Overflow
collections - Java 8 Collect two Lists to Map by condition - Stack Overflow
Best way in java to merge two lists to one map? - Stack Overflow
java - Streaming two lists into a map - Stack Overflow
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)));
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));
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.
Assuming, that both lists are of equal length and that keys and values have the same index in both lists, then this is an easy approach:
for (int i = 0; i < strings.size(); i++) {
map.put(strings.get(i), integers.get(i));
}
Over here in Scala land, we'd write it as:
integers.zip(strings).toMap
:-P
First of all, assuming you are going to create a HashMap, your key class (NameAndAge) must override equals and hashCode().
Second of all, in order to be efficient, I suggest you first create a Map<String,NameAndSalary> from the second List:
Map<String,NameAndSalary> helper =
nameAndSalaryList.stream()
.collect(Collectors.toMap(NameAndSalary::getName,
Function.identity()));
Finally, you can create the Map you want:
Map<NameAndAge,NameAndSalary> output =
nameAndAgeList.stream()
.collect(Collectors.toMap(Function.identity(),
naa->helper.get(naa.getName())));
This should do the trick, too:
Map<NameAndAge, NameAndSalary> map = new HashMap<>();
nameAndAgeList.forEach(age -> {
NameAndSalary salary = nameAndSalaryList.stream().filter(
s -> age.getName().equals(s.getName())).
findFirst().
orElseThrow(IllegalStateException::new);
map.put(age, salary);
});
Mind that it would throw an IllegalStateException if a matching name can't be found.
final List<HashMap<String, String>> joinedById = list1.stream()
.flatMap(m1 -> list2.stream()
.filter(y -> m1.get("id").equals(y.get("id")))
.map(m2 -> new HashMap<String, String>() {{
putAll(m1);
putAll(m2);
}}))
.collect(Collectors.toList());
There are lots of ways, with and without streams. The best way would depend on the more exact input requirements, which you haven’t given us. The following would work for the example lists in your question:
if (list1.size() != list2.size()) {
throw new IllegalStateException("Lists don’t match, not same size");
}
List<Map<String, Character>> comnbinedList = IntStream.range(0, list1.size())
.mapToObj(i -> {
Map<String, Character> m1 = list1.get(i);
Character id1 = m1.get("id");
Map<String, Character> m2 = list2.get(i);
Character id2 = m1.get("id");
if (! id1.equals(id2)) {
throw new IllegalStateException("Lists don’t match, id " + id1 + " != " + id2);
}
HashMap<String, Character> mergedMap = new HashMap<>(m1);
mergedMap.putAll(m2);
return mergedMap;
})
.collect(Collectors.toList());
The result is:
[{id=1, attr2=b, attr1=a, attr4=y, attr3=x}, {id=2, attr2=d, attr1=c, attr4=z, attr3=x}, {id=3, attr2=f, attr1=e, attr4=y, attr3=z}]
You will want to declare "id" a constant, of course.
Replace value -> true with a lambda item -> list2.contains(item) or a method reference list2::contains
itemList.stream()
.collect(Collectors.toMap(Function.identity(), list2::contains));
and read @michalk's comment.
Try this: for better performance use Set.
Set<String> selected = new HashSet<>(list2);
Map<String, Boolean> mapOfDataListTest = itemList.stream()
.collect(Collectors.toMap(key -> key, value ->selected.contains(value) ));
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()));