I have a Set<String> set1 and Set<String> set2, as well as 2 functions getSet1ElementScore(String s) and getSet2ElementScore(String s) (that return Integers) and want to insert all elements from both sets into a HashMap as its keys, with each key's value either calculated from getSet1ElementScore or getSet2ElementScore depending on which set the key came from.
Can I use a stream to pipeline this?
I'm not 100% sure I got your question right. This might achieve what you want:
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
Map<String, String> mapFromSet1 =
set1.stream().collect( Collectors.toMap(Function.identity(), p -> getSet1ElementScore(p)) );
Map<String, String> mapFromSet2 =
set2.stream().collect( Collectors.toMap(Function.identity(), p -> getSet2ElementScore(p)) );
Map<String, String> resultMap = new HashMap<>();
resultMap.putAll(mapFromSet1);
resultMap.putAll(mapFromSet2);
In order to transform it in one single pipeline, I think it is possible but you'd need to use (unnecessarily) more code than that.
You can process the elements of the two sets calling the appropriate function as:
Map<String, String> result = set1.stream()
.collect(Collectors.toMap(Function.identity(), this::getSet1ElementScore,
(old, new) -> old,
HashMap::new));
result.putAll(
set2.stream()
.collect(Collectors.toMap(Function.identity(), this::getSet2ElementScore))
);
I explicitly created a HashMap in the first processing so that it is mutable and we can merge the second into it.
Related
I am trying to make use of the stream API in place of loops, and have the follow problem.
I have a method which takes an Enum and returns a SortedMap. I store each map in a list, however I now wish to now store each SortedMap in a map with the Enum name being the key and the SortedMap returned fromlistCritieraResults()
as the value, how can I do this with the streams API?
Current working List Method
List<SortedMap<Date, String>> collectedMaps = Arrays.stream(Criteria.values())
.map(searcher::listCritieraResults)
.collect(Collectors.toList());
My incorrect attempt to use Collectors.toMap..
Map<String,SortedMap<Date,String>> map = new HashMap<>();
map = Arrays.stream(Criteria.values()).collect(Collectors.toMap(c -> c.name(),c -> searcher.listCritieraResults(c)));
Working loop
Map<Critiera, SortedMap<Date, String>> map = new HashMap<>();
for ( Criteria criteria : Criteria.values()){
map.put(criteria.name(),searcher.listCritieraResults(criteria));
}
HashMap<Criteria,SortedMap<Date,String>> map = new HashMap<>();
map = Arrays.stream(Criteria.values()).collect(Collectors.toMap(c -> c.name(),c -> searcher.listCritieraResults(c)));
In this, you are calling c.name() which will return a string and not Enum Criteria
Use Map<Criteria,SortedMap<Date,String>> and use Criteria as key in toMap if you want to Criteria as Map's key.
Map<Criteria,SortedMap<Date,String>> map = Arrays.stream(Criteria.values()).collect(Collectors.toMap(c -> c, c -> searcher.listCritieraResults(c)));
Or use string as Map key if you use c.name() in toMap
Map<String ,SortedMap<Date,String>> map = Arrays.stream(Criteria.values()).collect(Collectors.toMap(c -> c.name(), c -> searcher.listCritieraResults(c)));
Facing a challenge to come up with an efficient way of merging two ArrayLists of Maps.
The map looks like this:
{Username=User1, Role=Admin}
So one list looks like this:
List1 = [{Username=User1, Role=Admin},{Username=User2, Role=Auditor}]
and so on.
There is another list:
List 2 = [{Username=User1, Role=Integrator},{Username=User2, Role=Manager}]
Note: The users have different roles in different lists.
What i want to end up with is:
MergedList = [{Username=User1, Role=[Admin,Integrator]},{Username=User2, Role=[Auditor,Manager}]
Another Note: The actual list has 50,000 maps and each map has 20entries!! Just tried to keep it simple here.
Below are the stuff i tried. But failed.
Tried putAll.
Tried merge.
Tried something that I found in another post
map2.forEach((k, v) -> map3.merge(k, v, String::concat));
With regard to the performance and massive amount of data, I recommend you to avoid any usage of java-stream (although it is quite quick itself) and the Map::merge method.
Here you have to stick with the constructs closes to the JVM level and for-loops are your friends, here is the simplest approach I am aware of that might work:
final Map<String, Set<String>> newMap = new HashMap<>();
for (Map<String, String> map: list) { // iterate the List<Map>
for (Entry<String, String> entry: map.entrySet()) { // iterate the entries
final String key = entry.getKey(); // get the entry's key
newMap.computeIfAbsent(key, k -> new HashSet<>()); // compute a new pair
newMap.get(key).add(entry.getValue()); // add a value in any case
}
}
Set prevents duplicate values.
This solution assumes the following data structure. Slight variations are easy to apply to the solution above.
List<Map<String, String>> list = new ArrayList<>();
Map<String, String> map1 = new HashMap<>();
map1.put("User1", "Admin");
map1.put("User2", "Auditor");
Map<String, String> map2 = new HashMap<>();
map2.put("User1", "Integrator");
map2.put("User2", "Manager");
map2.put("User3", "Coffee machine");
list.add(map1);
list.add(map2);
You can use Java Streams to achieve this:
Map<String, List<String>> result = Stream.concat(users1.stream(), users2.stream())
.collect(Collectors.groupingBy(m -> m.get("Username"), Collectors.mapping(m -> m.get("Role"), Collectors.toList())));
This groups all the users and collects their roles.
The result will be:
{User1=[Admin, Integrator], User2=[Auditor, Manager]}
I have a database object that has a field that contains a list of strings. I retrieve all these objects and then use the flatMap and distinct stream methods on the resulting list to get a new list that holds all possible unique values that a database object string list can contain.
Next i want to make a map where the keys are the unique values list of the stringlist that i made earlier, and the values of the map are a list of database objects whose stringlist contains the value of the respective string mapkey.
So what I want is groupingBy the following:
if(object.stringList().contains(respectiveMapKeyFromUniqeStringCollection) put object in object values list of that respective keymap.
Is something like this possible using the groupingBy method?
Edit: I will explain further
class VegetableMaker{
#ElementCollection
private List<String> vegetableList;
}
Lets assume the possible values that a vegetableList can contain are: "Lettuce, Tomato, spinache, rubarbe, onion"
Set<String> produceNames = vegetableMakers.stream().flatMap(vegetableMaker -> vegetableMaker.getVegetableList().stream())
.distinct().collect(Collectors.toSet());
Now we have the list that contains all the possible values mentioned before.
We want to use the values in this list as the keys in the map.
So the Map will look like:
Map<uniqueStringsAsKeys, List<VegetableMaker>> map
The list value contains all the VegetableMaker instances of which the vegetableList contains the key of the map. So the list of key Onion will contain all the VegetableMaker instances whose list includes "Onion".
Is it possible to achieve such a map using the groupingBy method of a java stream?
EDIT 2:
This is the solution i have now, that doesn't use groupingBy but clarifies even more what I want.
EDIT: changed variable in code to match variables used in previous examples.
Set<VegetableMaker> vegetableMakers = vegetableMakerDao.findAll();
Set<String> uniqueVegetableList = vegetableMakers.stream().flatMap(vegetableMaker -> affiliateLink.getKeywords().stream()).distinct().collect(Collectors.toSet());
Map<String,Set<VegetableMaker>> vegetableMakersContainingKeywordInTheirList = new HashMap<>();
uniqueVegetableList.forEach(produceName ->{
Set<VegetableMaker> vegetableMakerSet = new HashSet<>();
vegetableMakers.forEach( vegetableMaker -> {
if(vegetableMaker.getVegetableList().contains(produceName))
vegetableMakerSet.add(vegetableMaker);
});
vegetableMakersContainingKeywordInTheirList.put(produceName, vegetableMakerSet);
});
If I understood you correctly:
List<VegetableMaker> dbObjects = List.of(
new VegetableMaker("Salad", List.of("Onion", "Cucumber")),
new VegetableMaker("Italian Salad", List.of("Cheese")),
new VegetableMaker("Greek Salad", List.of("Onion")));
Map<String, List<VegetableMaker>> map = dbObjects.stream()
.flatMap(x -> x.getVegetableList().stream().map(y -> new SimpleEntry<>(x, y)))
.collect(Collectors.groupingBy(
Entry::getValue,
Collectors.mapping(Entry::getKey, Collectors.toList())));
System.out.println(map);
Resulting being something like:
{Onion=[Salad, Greek Salad], Cheese=[Italian Salad], Cucumber=[Salad]}
EDIT
This is not much different than what I posted above:
Map<String, Set<VegetableMaker>> result = vegetableMakerList.stream()
.flatMap(x -> x.getKeywords().stream().distinct().map(y -> new SimpleEntry<>(x, y)))
.collect(Collectors.groupingBy(
Entry::getValue,
Collectors.mapping(Entry::getKey, Collectors.toSet())));
final Set<VegetableMaker> vegetableMakers = vegetableMakerDao.findAll();
final Map<String, Set<VegetableMaker>> vegetableMakersContainingKeywordInTheirList = vegetableMakers.stream()
.map(VegetableMaker::getKeywords)
.flatMap(Collection::stream)
.distinct()
.collect(Collectors.toMap(
Function.identity(),
vegetable -> vegetableMakers.stream()
.filter(vegetableMaker -> vegetableMaker.getKeywords().contains(vegetable))
.collect(Collectors.toSet())
));
I would like to flatten a Map which associates an Integer key to a list of String, without losing the key mapping.
I am curious as though it is possible and useful to do so with stream and lambda.
We start with something like this:
Map<Integer, List<String>> mapFrom = new HashMap<>();
Let's assume that mapFrom is populated somewhere, and looks like:
1: a,b,c
2: d,e,f
etc.
Let's also assume that the values in the lists are unique.
Now, I want to "unfold" it to get a second map like:
a: 1
b: 1
c: 1
d: 2
e: 2
f: 2
etc.
I could do it like this (or very similarly, using foreach):
Map<String, Integer> mapTo = new HashMap<>();
for (Map.Entry<Integer, List<String>> entry: mapFrom.entrySet()) {
for (String s: entry.getValue()) {
mapTo.put(s, entry.getKey());
}
}
Now let's assume that I want to use lambda instead of nested for loops. I would probably do something like this:
Map<String, Integer> mapTo = mapFrom.entrySet().stream().map(e -> {
e.getValue().stream().?
// Here I can iterate on each List,
// but my best try would only give me a flat map for each key,
// that I wouldn't know how to flatten.
}).collect(Collectors.toMap(/*A String value*/,/*An Integer key*/))
I also gave a try to flatMap, but I don't think that it is the right way to go, because although it helps me get rid of the dimensionality issue, I lose the key in the process.
In a nutshell, my two questions are :
Is it possible to use streams and lambda to achieve this?
Is is useful (performance, readability) to do so?
You need to use flatMap to flatten the values into a new stream, but since you still need the original keys for collecting into a Map, you have to map to a temporary object holding key and value, e.g.
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
.flatMap(e->e.getValue().stream()
.map(v->new AbstractMap.SimpleImmutableEntry<>(e.getKey(), v)))
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
The Map.Entry is a stand-in for the nonexistent tuple type, any other type capable of holding two objects of different type is sufficient.
An alternative not requiring these temporary objects, is a custom collector:
Map<String, Integer> mapTo = mapFrom.entrySet().stream().collect(
HashMap::new, (m,e)->e.getValue().forEach(v->m.put(v, e.getKey())), Map::putAll);
This differs from toMap in overwriting duplicate keys silently, whereas toMap without a merger function will throw an exception, if there is a duplicate key. Basically, this custom collector is a parallel capable variant of
Map<String, Integer> mapTo = new HashMap<>();
mapFrom.forEach((k, l) -> l.forEach(v -> mapTo.put(v, k)));
But note that this task wouldn’t benefit from parallel processing, even with a very large input map. Only if there were additional computational intense task within the stream pipeline that could benefit from SMP, there was a chance of getting a benefit from parallel streams. So perhaps, the concise, sequential Collection API solution is preferable.
You should use flatMap as follows:
entrySet.stream()
.flatMap(e -> e.getValue().stream()
.map(s -> new SimpleImmutableEntry(e.getKey(), s)));
SimpleImmutableEntry is a nested class in AbstractMap.
Hope this would do it in simplest way. :))
mapFrom.forEach((key, values) -> values.forEach(value -> mapTo.put(value, key)));
This should work. Please notice that you lost some keys from List.
Map<Integer, List<String>> mapFrom = new HashMap<>();
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
.flatMap(integerListEntry -> integerListEntry.getValue()
.stream()
.map(listItem -> new AbstractMap.SimpleEntry<>(listItem, integerListEntry.getKey())))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
Same as the previous answers with Java 9:
Map<String, Integer> mapTo = mapFrom.entrySet()
.stream()
.flatMap(entry -> entry.getValue()
.stream()
.map(s -> Map.entry(s, entry.getKey())))
.collect(toMap(Entry::getKey, Entry::getValue));
I have the following Object and a Map:
MyObject
String name;
Long priority;
foo bar;
Map<String, List<MyObject>> anotherHashMap;
I want to convert the Map in another Map. The Key of the result map is the key of the input map. The value of the result map ist the Property "name" of My object, ordered by priority.
The ordering and extracting the name is not the problem, but I could not put it into the result map. I do it the old Java 7 way, but it would be nice it is possible to use the streaming API.
Map<String, List<String>> result = new HashMap<>();
for (String identifier : anotherHashMap.keySet()) {
List<String> generatedList = anotherHashMap.get(identifier).stream()...;
teaserPerPage.put(identifier, generatedList);
}
Has anyone an idea? I tried this, but got stuck:
anotherHashMap.entrySet().stream().collect(Collectors.asMap(..., ...));
Map<String, List<String>> result = anotherHashMap
.entrySet().stream() // Stream over entry set
.collect(Collectors.toMap( // Collect final result map
Map.Entry::getKey, // Key mapping is the same
e -> e.getValue().stream() // Stream over list
.sorted(Comparator.comparingLong(MyObject::getPriority)) // Sort by priority
.map(MyObject::getName) // Apply mapping to MyObject
.collect(Collectors.toList())) // Collect mapping into list
);
Essentially, you stream over each entry set and collect it into a new map. To compute the value in the new map, you stream over the List<MyOjbect> from the old map, sort, and apply a mapping and collection function to it. In this case I used MyObject::getName as the mapping and collected the resulting names into a list.
For generating another map, we can have something like following:
HashMap<String, List<String>> result = anotherHashMap.entrySet().stream().collect(Collectors.toMap(elem -> elem.getKey(), elem -> elem.getValue() // can further process it);
Above I am recreating the map again, but you can process the key or the value according to your needs.
Map<String, List<String>> result = anotherHashMap.entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream()
.sorted(comparing(MyObject::getPriority))
.map(MyObject::getName)
.collect(Collectors.toList())));
Similar to answer of Mike Kobit, but sorting is applied in the correct place (i.e. value is sorted, not map entries) and more concise static method Comparator.comparing is used to get Comparator for sorting.