Stream an entrySet to groupingBy instead of keySet - java

I was watching a JavaOne video By Venkat Subramanian about lambdas.
He had an example like this:
Map<String, Integer> scores = new HashMap<>();
scores.put("Jack", 12);
scores.put("Jill", 15);
scores.put("Tom", 11);
scores.put("Darla", 15);
scores.put("Nick", 15);
scores.put("Nancy", 11);
System.out.println(groupByScores(scores));
with this method:
public static Map<Integer, List<String>> groupByScores(Map<String, Integer> scores) {
return scores.keySet().stream().collect(Collectors.groupingBy(scores::get));
}
The one thing that's a little bothersome about this is that it's essentially iterating over the keySet, calling map.get(key) for each key. But that's an antipattern in plain non-lambda code.
How can I get the same result, producing a "peopleByAge" map, but by iterating over the entrySet instead of the keySet? The hard part is obviously everything past stream().

return scores.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey,
Collectors.toList())));

Related

Reverse a Map (A - B List) to (B - A List)

I made up this function, it seems to make the work, but I wondered if there was an even cleaner solution.
public static <K, V> Map<V, List<K>> reverseMap(Map<K, List<V>> map) {
return map.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(value -> new AbstractMap.SimpleEntry<>(value, entry.getKey())))
.collect(Collectors.groupingBy(
AbstractMap.SimpleEntry::getKey,
Collectors.mapping(AbstractMap.SimpleEntry::getValue, Collectors.toList())));
}
Bonus question: I have a java 8 constraint for this one, but how could later versions improve it? I assume I'd no longer have to use AbstractMap.SimpleEntry since Java 9 introduced the Map.entry(k, v) function.
Since you did not necessitate streams in your question, I'm going to advocate for a non-stream solution:
//example input
Map<Integer, List<Integer>> map = new HashMap<>();
map.put(1, Arrays.asList(11, 12, 13, 4));
map.put(2, Arrays.asList(21, 22, 23, 4));
map.put(3, Arrays.asList(31, 32, 33, 4));
//reversing
Map<Integer, List<Integer>> reversed = new HashMap<>();
map.forEach((key, list) -> {
list.forEach(value -> {
reversed.computeIfAbsent(value, k -> new ArrayList<>()).add(key);
});
});
//end result:
//{32=[3], 33=[3], 4=[1, 2, 3], 21=[2], 22=[2], 23=[2], 11=[1], 12=[1], 13=[1], 31=[3]}
In your stream-based solution, you will be creating new Entry objects per key-(list-value) pair in the map, which will then have to make additional entries when recombined into a map. By using a direct approach, you avoid this excess object creation and directly create the entries you need.
Note that not everything has to be a Stream, the "old" way of doing things can still be correct, if not better (in readability and performance terms) than a Stream implementation.

Priority Data Structures and Finding a winning program using Java 8 Streams

I have a List of programTypes:
List<String> programTypes = {ACF, VCX, IFL}
Note: This is a map hardcoded in code.
Here, I want to attach priorities to these programTypes:
ACF->priority=2, VCX->priority=1, IFL->priority=3
What data structure should I use? Priority Queues?
Also, now I have a list of inputProgramTypes: {ABC, VCX, IFL}
I want the output to be the winningProgramType: VCX
I can code it by iterating on inputProgramTypes and setting the winningProgramType if each next has a priority greater that the set one (Like finding max problem).
But I want to know if I can optimise? And how I can use streams to write code for same to make it look clean? I am new to streams and learning my way through it.
I suggest you use an enum with a parameter constructor.
Such, the method public static Map<ProgramType, Integer> getPrioMap() will elegantly and in a type-safe way return the data structure you need.
public enum ProgramType {
ACF(2), VCX(1), IFL(3);
private int prio;
private ProgramType(int prio) {
this.prio = prio;
}
public Integer getPriority() {
return prio;
}
public static Map<ProgramType, Integer> getPrioMap() {
return List.of(ProgramType.values()).stream()
.collect(Collectors.toMap(e -> e, e -> e.getPriority()));
}
}
Possibly, a simple Map will do fine:
public Map<String, Integer> buildMapOfPriorityProgramTypes() {
Map<String, Integer> priorityProgramTypes = new HashMap<>();
priorityProgramTypes.put("ACF", 2);
priorityProgramTypes.put("VCX", 1);
priorityProgramTypes.put("IFL", 3);
return priorityProgramTypes;
}
public String getTopPriorityType(Map<String, Integer> priorityTypes) {
return priorityTypes.entrySet().stream()
.min(Map.Entry.comparingByValue())
.get().getKey();
}
If you use Java 9 or newer, you may use shorter Map.of:
Map<String, Integer> priorityProgramTypes = Map.of(
"ACF", Integer.valueOf(2),
"VCX", Integer.valueOf(1),
"IFL", Integer.valueOf(3)
);
Use SortedMap interface and its implementation TreeMap:
SortedMap<Integer, List<String>> map = new TreeMap<>();
map.put(2, Collections.singletonList("ACF"));
map.put(1, Collections.singletonList("VCX"));
map.put(3, Collections.singletonList("IFL"));
The advantages are:
The keys are sorted, you can manage the order of them defining a Comparator in the constructor new TreeMap<>(comparator);. The order of processing from the sample above will be:
map.values().forEach(System.out::print);
// [VCX][ACF][IFL]
If more of the strings have the same priority, List<String> as the values of the map are more suitable.
Adding new priority (key) and value (List<String>) to the map will not break the sorted characteristics. For safe adding, I recommend Map::computeIfPresent .

How do I reverse the values of a Map when one type is a Set of Integers

For example how do I reverse the key and value datatypes from something like this:
TreeMap<Set<Integer>, Integer>
...into this:
TreeMap<Integer, Set<Integer>>
If you're using Java8, you can do:
TreeMap<Set<Integer>, Integer> map = ....;
Map<Integer, Set<Integer>> result =
map.entrySet()
.stream()
.map(entry -> new AbstractMap.SimpleEntry(entry.getValue(), entry.getKey())
.collect(Collectors.toMap());
Iterate over the entry set and put them reversely in a new TreeMap. The following generic method will reverse every map.
public static <K, V> TreeMap<V, K> reverse(TreeMap<K, V> map) {
TreeMap<V, K> result = new TreeMap<V, K>();
for (Entry<K, V> e : map.entrySet())
result.put(e.getValue(), e.getKey());
return result;
}
Important
If a value is present two times, you will lose the key with the smallest value.
Example:
public static void main(String[] args) {
TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
map.put(1, 2);
map.put(2, 2);
System.out.println(reverse(map));
}
Output:
{2=2}
The tuple {1,2} is lost since the value 2 was presents multiple times!
Maps are not easilly reversible, And rightly so.
Reversing maps might not work.
In a Map Keys are obviously unique but values have no such constraint.
I don't know what the problem you're facing is, but it sounds as if there is a better design to solve it.
Perhaps you should use a BiMap?
It's essentialy a two way map in which both the "keys" and the "values" are unique. It allows efficient lookup both from the keys and from the values.
Java doesn't have a built in BiMap but google Guava and Apache Commons do.

Java: How to get set of keys having same value in hashmap

I have a hashmap as below:
1->x
2->y
3->x
4->z
Now i want to know all keys whose value is x (ans: [1,3] ). what is best way to do?
Brute force way is to just iterate over map and store all keys in array whose value is x.
Is there any efficient way for this.
Thanks
A hashmap is a structure that is optimized for associative access of the values using the keys, but is in no way better in doing the reverse then an array for instance. I don't think you can do any better then just iterate. Only way to improve efficiency is if you have a reverse hash map as well(i.e. hash map where you hold an array of keys pointing to a given value for all values).
You can use a MultiMap to easily get all those duplicate values.
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "x");
map.put(2, "y");
map.put(2, "z");
map.put(3, "x");
map.put(4, "y");
map.put(5, "z");
map.put(6, "x");
map.put(7, "y");
System.out.println("Original map: " + map);
Multimap<String, Integer> multiMap = HashMultimap.create();
for (Entry<Integer, String> entry : map.entrySet()) {
multiMap.put(entry.getValue(), entry.getKey());
}
System.out.println();
for (Entry<String, Collection<Integer>> entry : multiMap.asMap().entrySet()) {
System.out.println("Original value: " + entry.getKey() + " was mapped to keys: "
+ entry.getValue());
}
Prints out:
Original map: {1=x, 2=z, 3=x, 4=y, 5=z, 6=x, 7=y}
Original value: z was mapped to keys: [2, 5]
Original value: y was mapped to keys: [4, 7]
Original value: x was mapped to keys: [1, 3, 6]
Per #noahz's suggestion, forMap and invertFrom takes fewer lines, but is arguably more complex to read:
HashMultimap<String, Integer> multiMap =
Multimaps.invertFrom(Multimaps.forMap(map),
HashMultimap.<String, Integer> create());
in place of:
Multimap<String, Integer> multiMap = HashMultimap.create();
for (Entry<Integer, String> entry : map.entrySet()) {
multiMap.put(entry.getValue(), entry.getKey());
}
If Java 8 is an option, you could try a streaming approach:
Map<Integer, String> map = new HashMap<>();
map.put(1, "x");
map.put(2, "y");
map.put(3, "x");
map.put(4, "z");
Map<String, ArrayList<Integer>> reverseMap = new HashMap<>(
map.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getValue)).values().stream()
.collect(Collectors.toMap(
item -> item.get(0).getValue(),
item -> new ArrayList<>(
item.stream()
.map(Map.Entry::getKey)
.collect(Collectors.toList())
))
));
System.out.println(reverseMap);
Which results in:
{x=[1, 3], y=[2], z=[4]}
If Java 7 is preferred:
Map<String, ArrayList<Integer>> reverseMap = new HashMap<>();
for (Map.Entry<Integer,String> entry : map.entrySet()) {
if (!reverseMap.containsKey(entry.getValue())) {
reverseMap.put(entry.getValue(), new ArrayList<>());
}
ArrayList<Integer> keys = reverseMap.get(entry.getValue());
keys.add(entry.getKey());
reverseMap.put(entry.getValue(), keys);
}
As an interesting aside, I experimented with the time required for each algorithm when executing large maps of (index,random('a'-'z') pairs.
10,000,000 20,000,000
Java 7: 615 ms 11624 ms
Java 8: 1579 ms 2176 ms
If you are open to using a library, use Google Guava's Multimaps utilities, specifically forMap() combined with invertFrom()
Yup, just brute force. You can make it fast by also storing a Multimap from Value -> Collection of Key, at the expense of memory and runtime cost for updates.
HashMap computes the hashcode() of the key, not of the values. Unless you store some kind of additional information, or consider using a different data structure, I think the only way you can get this is brute force.
If you need to perform efficient operation on the values, you should think whether you're using the appropriate data structure.
If you are using a hashmap there is no efficient way doing it but iterating the values
If you already have a map, you should consider using Google's Guava library to filter the entries you're interested in. You can do something along the lines of:
final Map<Integer, Character> filtered = Maps.filterValues(unfiltered, new Predicate<Character>() {
#Override
public boolean apply(Character ch) {
return ch == 'x';
}
});
I agree with George Campbell but for java 8 I would do it a bit easier:
Map<String, List<Integer>> reverseMap = map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(
Map.Entry::getKey,
Collectors.toList())));
Try This.....
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("cust_tenure", "3_sigma");
hashMap.put("cust_age", "3_sigma");
hashMap.put("cust_amb_6m_sav", "3_sigma");
hashMap.put("cust_amb_6m_chq", "3_sigma");
hashMap.put("cust_total_prod_6m", "3_sigma");
HashMap<String, ArrayList<String>> result = new LinkedHashMap<String, ArrayList<String>>();
for (String key : hashMap.keySet()) {
ArrayList<String> colName = null;
if (!result.containsKey(hashMap.get(key))) {
colName = new ArrayList<String>();
colName.add(key);
result.put(hashMap.get(key), colName);
} else {
colName = result.get(hashMap.get(key));
colName.add(key);
result.put(hashMap.get(key), colName);
}
System.out.println(key + "\t" + hashMap.get(key));
}
for (String key : result.keySet()) {
System.out.println(key + "\t" + result.get(key));
}
System.out.println(hashMap.size());
}

How does one convert a HashMap to a List in Java?

In Java, how does one get the values of a HashMap returned as a List?
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put (1, "Mark");
map.put (2, "Tarryn");
List<String> list = new ArrayList<String>(map.values());
for (String s : list) {
System.out.println(s);
}
Assuming you have:
HashMap<Key, Value> map; // Assigned or populated somehow.
For a list of values:
List<Value> values = new ArrayList<Value>(map.values());
For a list of keys:
List<Key> keys = new ArrayList<Key>(map.keySet());
Note that the order of the keys and values will be unreliable with a HashMap; use a LinkedHashMap if you need to preserve one-to-one correspondence of key and value positions in their respective lists.
Basically you should not mess the question with answer, because it is confusing.
Then you could specify what convert mean and pick one of this solution
List<Integer> keyList = Collections.list(Collections.enumeration(map.keySet()));
List<String> valueList = Collections.list(Collections.enumeration(map.values()));
Collection Interface has 3 views
keySet
values
entrySet
Other have answered to to convert Hashmap into two lists of key and value. Its perfectly correct
My addition: How to convert "key-value pair" (aka entrySet)into list.
Map m=new HashMap();
m.put(3, "dev2");
m.put(4, "dev3");
List<Entry> entryList = new ArrayList<Entry>(m.entrySet());
for (Entry s : entryList) {
System.out.println(s);
}
ArrayList has this constructor.
Solution using Java 8 and Stream Api:
private static <K, V> List<V> createListFromMapEntries (Map<K, V> map){
return map.values().stream().collect(Collectors.toList());
}
Usage:
public static void main (String[] args)
{
Map<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
List<String> result = createListFromMapEntries(map);
result.forEach(System.out :: println);
}
If you only want it to iterate over your HashMap, no need for a list:
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put (1, "Mark");
map.put (2, "Tarryn");
for (String s : map.values()) {
System.out.println(s);
}
Of course, if you want to modify your map structurally (i.e. more than only changing the value for an existing key) while iterating, then you better use the "copy to ArrayList" method, since otherwise you'll get a ConcurrentModificationException. Or export as an array:
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put (1, "Mark");
map.put (2, "Tarryn");
for (String s : map.values().toArray(new String[]{})) {
System.out.println(s);
}
If you wanna maintain the same order in your list, say:
your Map looks like:
map.put(1, "msg1")
map.put(2, "msg2")
map.put(3, "msg3")
and you want your list looks like
["msg1", "msg2", "msg3"] // same order as the map
you will have to iterate through the Map:
// sort your map based on key, otherwise you will get IndexOutofBoundException
Map<String, String> treeMap = new TreeMap<String, String>(map)
List<String> list = new List<String>();
for (treeMap.Entry<Integer, String> entry : treeMap.entrySet()) {
list.add(entry.getKey(), entry.getValue());
}
I use usually map.values() to get values, then convert them to list
let say you have this Hashmap:
HashMap<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
You can get values from the map, then convert them to a list in one code line like that:
List<Integer> values = map.values().stream()
.collect(Collectors.toList());

Categories