I have a HashMap which contains the following values:
Map<String, Integer> map = new HashMap<>();
map.put("name1", 3);
map.put("name2", 14);
map.put("name3", 4);
map.put("name4", 14);
map.put("name5", 2);
map.put("name6", 6);
How do I get all keys with the highest value? So that I get the following keys in this example:
name2
name4
The first step is to find the highest value at all.
int max = Collections.max(map.values());
Now iterate through all the entries of the map and add to the list keys associated with the highest value.
List<String> keys = new ArrayList<>();
for (Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue()==max) {
keys.add(entry.getKey());
}
}
If you like the Java 8 Stream API, try the following:
map.entrySet().stream()
.filter(entry -> entry.getValue() == max)
.map(entry -> entry.getKey())
.collect(Collectors.toList());
The response from Nikolas Charalambidis is quite neat, but it may be faster to do it in just one step (iteration), supposing that the input map was much larger:
public static List<String> getKeysWithMaxValue(Map<String, Integer> map){
final List<String> resultList = new ArrayList<String>();
int currentMaxValue = Integer.MIN_VALUE;
for (Map.Entry<String, Integer> entry : map.entrySet()){
if (entry.getValue() > currentMaxValue){
resultList.clear();
resultList.add(entry.getKey());
currentMaxValue = entry.getValue();
} else if (entry.getValue() == currentMaxValue){
resultList.add(entry.getKey());
}
}
return resultList;
}
Another way to achieve this by only processing the elements once could be to use an appropriate collector:
Entry<Integer, Set<String>> highestKey = map.entrySet().stream()
.collect(groupingBy(Entry::getValue, TreeMap::new, mapping(Entry::getKey, toSet())))
.lastEntry();
Set<String> result = highestKey != null ? highestKey.getValue() : Set.of();
Here we stream() over the entries in the map. By grouping by the value of the entries we can collect into a new map. By stating that it should collect into a TreeMap, it will be sorted according to it's keys. By doing this, we can get the highest value by using lastEntry().
mapping() is used to make sure we get the keys of the original map as the value in the new map, whithout this we would get the entries as values.
Finally, the last null check is needed in case of an empty map, because there won't be any last entry.
A fully working example including imports:
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import static java.util.stream.Collectors.*;
class Sorting {
public static void main(String[] args) {
Map<String, Integer> map = Map.of(
"name1", 3,
"name2", 14,
"name3", 4,
"name4", 14,
"name5", 2,
"name6", 6);
Entry<Integer, Set<String>> highestKey = map.entrySet().stream()
.collect(groupingBy(Entry::getValue, TreeMap::new, mapping(Entry::getKey, toSet())))
.lastEntry();
Set<String> result = highestKey != null ? highestKey.getValue() : Set.of();
}
}
Related
I want to get the second max number or value of a Map by using Java Streams.
If multiple values are present then also I want both key and value.
HashMap<String, Integer> map = new HashMap<String,Integer>();
map.put("Pankaj",1);
map.put("Amit",2);
map.put("Rahul",5);
map.put("Chetan",7);
map.put("Vinod",6);
map.put("Amit",8);
map.put("Rajesh", 7);
Entry<String, Integer> m = map.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
.skip(1)
.findFirst()
.get();
If multiple values are present then also I want both key and value.
Your code works only for a single value. In order to get the multiple values, group the entries on their values and then apply your code on the Stream derived from the resulting Map.
Demo:
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
class Main {
public static void main(String args[]) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("Pankaj", 1);
map.put("Amit", 2);
map.put("Rahul", 5);
map.put("Chetan", 7);
map.put("Vinod", 6);
map.put("Amit", 8);
map.put("Rajesh", 7);
List<Entry<String, Integer>> result = map.entrySet()
.stream()
.collect(Collectors.groupingBy(e -> e.getValue()))
.entrySet()
.stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByKey()))
.skip(1)
.findFirst()
.get()
.getValue();
System.out.println(result);
}
}
Output:
[Rajesh=7, Chetan=7]
I would collect the map into a TreeMap sorted by the keys in reverse order using groupingBy. Then get the 2nd element from it using skip as,
TreeMap<Integer, List<String>> treeMap = map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
() -> new TreeMap<>(Comparator.<Integer>reverseOrder()),
Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
Map.Entry<Integer, List<String>> result = treeMap.entrySet()
.stream()
.skip(1)
.findFirst()
.get();
System.out.println(result);
Outputs,
7=[Rajesh, Chetan]
Note, we are assuming a second-max will always be present as we call get on an Optional.
Be simple! The key word is a second max. It means you should use PriorityQueue:
Queue<Map.Entry<String, Integer>> maxQueue = new PriorityQueue<>((one, two) ->
Integer.compare(two.getValue(), one.getValue()));
maxQueue.addAll(map.entrySet());
Map.Entry<String, Integer> firstMax = maxQueue.remove();
Map.Entry<String, Integer> secondMax = maxQueue.remove();
Here is an efficient way to get all second highest elements without any sorting:
Online Demo
static Map<String, Integer> map = Map.of("Pankaj",1, "Amita",2,
"Rahul",5, "Chetan",7, "Vinod",6, "Amit",8, "Rajesh",7, "Foo",8);
static Integer max2 = Integer.MIN_VALUE;
static List<String> values = new ArrayList<>();
public static void main(String[] args) {
// find max value first
Integer max = map.values()
.stream()
.mapToInt(v -> v)
.max().orElseThrow(NoSuchElementException::new);
// if it is not max then set max2
map.entrySet().forEach(el -> {
if (el.getValue() != max && el.getValue() >= max2) {
if (el.getValue() != max2) {
values.clear();
max2 = el.getValue();
}
values.add(el.getKey());
}
});
System.out.println(max2 + "=" + values);
}
Output:
7=[Rajesh, Chetan]
I have a TreeMap with entries like
["aftab" = 4, "Manoj" = 5, "Rahul" = 5]
I want to get the key with the max value, but if there are two or more max values I want the key that comes first in the map, Manoj in this case. In My application I used Collections.max(map.getKey()) and it is returning Rahul.
Create a Comparator and pass it to Collections.max().
Map<String, Integer> map = new TreeMap<>(
Map.of("Aftab", 4, "Manoj", 5, "Rahul", 5));
Comparator<Entry<String,Integer>> comp = Entry.comparingByValue();
Entry<String,Integer> e = Collections.max(map.entrySet(),comp);
System.out.println(e);
// or
System.out.println(e.getKey());
Make a comparator that sorts the entries by values descending then comparing by keys when two values are equals
Entry.<String, Integer>comparingByValue().reversed()
.thenComparing(Entry.comparingByKey())
Map<String, Integer> map = new TreeMap<>();
map.put("aftab", 4);
map.put("Manoj", 5);
map.put("Rahul", 5);
Entry<String, Integer> result = map.entrySet().stream()
.sorted(Entry.<String, Integer>comparingByValue().reversed().thenComparing(Entry.comparingByKey()))
.findFirst().orElseGet(null);
System.out.println(result);
, output
Manoj=5
Use Collections::max to find the entry with maximum value from map.entrySet() by using Comparator.comparingInt(Map.Entry::getValue).
Demo:
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new TreeMap<>();
map.put("aftab", 4);
map.put("Manoj", 5);
map.put("Rahul", 5);
Entry<String, Integer> entry = Collections.max(map.entrySet(), Comparator.comparingInt(Map.Entry::getValue));
System.out.println(entry);
}
}
Output:
Manoj=5
I am not so familiar with Java 8 (still learning) and looking to see if I could find something equivalent of the below code using streams.
The below code mainly tries to get corresponding double value for each value in String and then sums it up. I could not find much help anywhere on this format. I am not sure if using streams would clean up the code or would make it messier.
// safe assumptions - String/List (Key/Value) cannot be null or empty
// inputMap --> Map<String, List<String>>
Map<String, Double> finalResult = new HashMap<>();
for (Map.Entry<String, List<String>> entry : inputMap.entrySet()) {
Double score = 0.0;
for (String current: entry.getValue()) {
score += computeScore(current);
}
finalResult.put(entry.getKey(), score);
}
private Double computeScore(String a) { .. }
Map<String, Double> finalResult = inputMap.entrySet()
.stream()
.collect(Collectors.toMap(
Entry::getKey,
e -> e.getValue()
.stream()
.mapToDouble(str -> computeScore(str))
.sum()));
Above code iterates over the map and creates a new map with same keys & before putting the values, it first iterates over each value - which is a list, computes score via calling computeScore() over each list element and then sums the scores collected to be put in the value.
You could also use the forEach method along with the stream API to yield the result you're seeking.
Map<String, Double> resultSet = new HashMap<>();
inputMap.forEach((k, v) -> resultSet.put(k, v.stream()
.mapToDouble(s -> computeScore(s)).sum()));
s -> computeScore(s) could be changed to use a method reference i.e. T::computeScore where T is the name of the class containing computeScore.
How about this one:
Map<String, Double> finalResult = inputMap.entrySet()
.stream()
.map(entry -> new AbstractMap.SimpleEntry<String, Double>( // maps each key to a new
// Entry<String, Double>
entry.getKey(), // the same key
entry.getValue().stream()
.mapToDouble(string -> computeScore(string)).sum())) // List<String> mapped to
// List<Double> and summed
.collect(Collectors.toMap(Entry::getKey, Entry::getValue)); // collected by the same
// key and a newly
// calulcated value
The version above could be merged to the single collect(..) method:
Map<String, Double> finalResult = inputMap.entrySet()
.stream()
.collect(Collectors.toMap(
Entry::getKey, // keeps the same key
entry -> entry.getValue()
.stream() // List<String> -> Stream<String>
// then Stream<String> -> Stream<Double>
.mapToDouble(string -> computeScore(string))
.sum())); // and summed
The key parts:
collect(..) performs a reduction on the elements using a certain strategy with a Collector.
Entry::getKey is a shortcut for entry -> entry.getKey. A function for mapping the key.
entry -> entry.getValue().stream() returns the Stream<String>
mapToDouble(..) returns the DoubleStream. This has an aggregating operation sum(..) which sums the elements - together creates a new value for the Map.
Regardless of whether you use the stream-based or the loop-based solution, it would be beneficial and add some clarity and structure to extract the inner loop into a method:
private double computeScore(Collection<String> strings)
{
return strings.stream().mapToDouble(this::computeScore).sum();
}
Of course, this could also be implemented using a loop, but ... that's exactly the point: This method can now be called, either in the outer loop, or on the values of a stream of map entries.
The outer loop or stream could also be pulled into a method. In the example below, I generalized this a bit: The type of the keys of the map does not matter. Neither does whether the values are List or Collection instances.
As an alternative to the currently accepted answer, the stream-based solution here does not fill a new map that is created manually. Instead, it uses a Collector.
(This is similar to other answers, but I think that the extracted computeScore method greatly simplifies the otherwise rather ugly lambdas that are necessary for the nested streams)
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class ToStreamOrNotToStream
{
public static void main(String[] args)
{
ToStreamOrNotToStream t = new ToStreamOrNotToStream();
Map<String, List<String>> inputMap =
new LinkedHashMap<String, List<String>>();
inputMap.put("A", Arrays.asList("1.0", "2.0", "3.0"));
inputMap.put("B", Arrays.asList("2.0", "3.0", "4.0"));
inputMap.put("C", Arrays.asList("3.0", "4.0", "5.0"));
System.out.println("Result A: " + t.computeA(inputMap));
System.out.println("Result B: " + t.computeB(inputMap));
}
private <T> Map<T, Double> computeA(
Map<T, ? extends Collection<String>> inputMap)
{
Map<T, Double> finalResult = new HashMap<>();
for (Entry<T, ? extends Collection<String>> entry : inputMap.entrySet())
{
double score = computeScore(entry.getValue());
finalResult.put(entry.getKey(), score);
}
return finalResult;
}
private <T> Map<T, Double> computeB(
Map<T, ? extends Collection<String>> inputMap)
{
return inputMap.entrySet().stream().collect(
Collectors.toMap(Entry::getKey, e -> computeScore(e.getValue())));
}
private double computeScore(Collection<String> strings)
{
return strings.stream().mapToDouble(this::computeScore).sum();
}
private double computeScore(String a)
{
return Double.parseDouble(a);
}
}
I found it somewhat shorter:
value = startDates.entrySet().stream().mapToDouble(Entry::getValue).sum();
Let's say I have a HashMap with String keys and Integer values:
map = {cat=1, kid=3, girl=3, adult=2, human=5, dog=2, boy=2}
I want to switch the keys and values by putting this information into another HashMap. I know that a HashMap cannot have duplicate keys, so I tried to put the information into a HashMap with the Integer for the keys that would map to a String ArrayList so that I could potentially have one Integer mapping to multiple Strings:
swap = {1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
I tried the following code:
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (String x : map.keySet()) {
for (int i = 0; i <= 5; i++) {
ArrayList<String> list = new ArrayList<String>();
if (i == map.get(x)) {
list.add(x);
swap.put(i, list);
}
}
}
The only difference in my code is that I didn't hard code the number 5 into my index; I have a method that finds the highest integer value in the original HashMap and used that. I know it works correctly because I get the same output even if I hard code the 5 in there, I just didn't include it to save space.
My goal here is to be able to do this 'reversal' with any set of data, otherwise I could just hard code the value. The output I get from the above code is this:
swap = {1=[cat], 2=[boy], 3=[girl], 5=[human]}
As you can see, my problem is that the value ArrayList is only keeping the last String that was put into it, instead of collecting all of them. How can I make the ArrayList store each String, rather than just the last String?
With Java 8, you can do the following:
Map<String, Integer> map = new HashMap<>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
Map<Integer, List<String>> newMap = map.keySet()
.stream()
.collect(Collectors.groupingBy(map::get));
System.out.println(newMap);
The output will be:
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
you are recreating the arrayList for every iteration and i can't figure out a way to do it with that logic, here is a good way though and without the need to check for the max integer:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
List<String> get = swap.get(value);
if (get == null) {
get = new ArrayList<>();
swap.put(value, get);
}
get.add(key);
}
Best way is to iterate over the key set of the original map.
Also you have to asure that the List is present for any key in the target map:
for (Map.Entry<String,Integer> inputEntry : map.entrySet())
swap.computeIfAbsent(inputEntry.getValue(),()->new ArrayList<>()).add(inputEntry.getKey());
This is obviously not the best solution, but approaches the problem the same way you did by interchanging inner and outer loops as shown below.
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (Integer value = 0; value <= 5; value++) {
ArrayList<String> list = new ArrayList<String>();
for (String key : map.keySet()) {
if (map.get(key) == value) {
list.add(key);
}
}
if (map.containsValue(value)) {
swap.put(value, list);
}
}
Output
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
Best way I can think of is using Map.forEach method on existing map and Map.computeIfAbsent method on new map:
Map<Integer, List<String>> swap = new HashMap<>();
map.forEach((k, v) -> swap.computeIfAbsent(v, k -> new ArrayList<>()).add(k));
As a side note, you can use the diamond operator <> to create your new map (there's no need to repeat the type of the key and value when invoking the map's constructor, as the compiler will infer them).
As a second side note, it's good practice to use interface types instead of concrete types, both for generic parameter types and for actual types. This is why I've used List and Map instead of ArrayList and HashMap, respectively.
Using groupingBy like in Jacob's answer but with Map.entrySet for better performance, as suggested by Boris:
// import static java.util.stream.Collectors.*
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(groupingBy(Entry::getValue, mapping(Entry::getKey, toList())));
This uses two more methods of Collectors: mapping and toList.
If it wasn't for these two helper functions, the solution could look like this:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
groupingBy(
Entry::getValue,
Collector.of(
ArrayList::new,
(list, e) -> {
list.add(e.getKey());
},
(left, right) -> { // only needed for parallel streams
left.addAll(right);
return left;
}
)
)
);
Or, using toMap instead of groupingBy:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
toMap(
Entry::getValue,
(e) -> new ArrayList<>(Arrays.asList(e.getKey())),
(left, right) -> {
left.addAll(right);
return left;
}
)
);
It seams you override the values instrad of adding them to the already creared arraylist. Try this:
HashMap<Integer, ArrayList<String>> swapedMap = new HashMap<Integer, ArrayList<String>>();
for (String key : map.keySet()) {
Integer swappedKey = map.get(key);
ArrayList<String> a = swapedMap.get(swappedKey);
if (a == null) {
a = new ArrayList<String>();
swapedMap.put(swappedKey, a)
}
a.add(key);
}
I didn't have time to run it (sorry, don't have Java compiler now), but should be almost ok :)
You could use the new merge method in java-8 from Map:
Map<Integer, List<String>> newMap = new HashMap<>();
map.forEach((key, value) -> {
List<String> values = new ArrayList<>();
values.add(key);
newMap.merge(value, values, (left, right) -> {
left.addAll(right);
return left;
});
});
How can I add a new map to existing map. The maps have the same type Map<String, Integer>. If the key from new map exists in the old map the values should be added.
Map<String, Integer> oldMap = new TreeMap<>();
Map<String, Integer> newMap = new TreeMap<>();
//Data added
//Now what is the best way to iterate these maps to add the values from both?
By add, I assume you want to add the integer values, not create a Map<String, List<Integer>>.
Before java 7, you'll have to iterate as #laune showed (+1 to him). Otherwise with java 8, there is a merge method on Map. So you could do it like this:
Map<String, Integer> oldMap = new TreeMap<>();
Map<String, Integer> newMap = new TreeMap<>();
oldMap.put("1", 10);
oldMap.put("2", 5);
newMap.put("1", 7);
oldMap.forEach((k, v) -> newMap.merge(k, v, (a, b) -> a + b));
System.out.println(newMap); //{1=17, 2=5}
What it does is that for each key-value pair, it merges the key (if it's not yet in newMap, it simply creates a new key-value pair, otherwise it updates the previous value hold by the existing key by adding the two Integers)
Also maybe you should consider using a Map<String, Long> to avoid overflow when adding two integers.
for( Map.Entry<String,Integer> entry: newMap.entrySet() ) {
// get key and value from newMap and insert/add to oldMap
Integer oldVal = oldMap.get( entry.getKey() );
if( oldVal == null ){
oldVal = entry.getValue();
} else {
oldVal += entry.getValue();
}
newMap.put( entry.getKey(), oldVal );
}
Hope that this is what you meant