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]
Related
When I convert to lowercase, it's in the right order.
When I try to convert to uppercase,it's wrong.
I try to use LinkedHashMap to keeping the insertion order.
I think stream changed the order of keys,But I don't know how to sort after changing the key.
Thanks in advance.
Here is my code
import java.util.*;
import java.util.stream.Collectors;
public class Test {
static List<Map<String,Object>> convertKeyCase (List<Map<String,Object>> list,int...s) {
return list.stream().map(m -> m.entrySet().stream().sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(p -> s.length == 0 || s[0]==1 ? p.getKey().toUpperCase():p.getKey().toLowerCase(), Map.Entry::getValue)))
.collect(Collectors.toList());
}
public static void main(String[] args) {
List<Map<String,Object>> list1 = new ArrayList<>();
Map<String,Object> map1 = new LinkedHashMap<>();
Map<String,Object> map2 = new LinkedHashMap<>();
map1.put("date","2021-06-03");
map1.put("weather","1");
map1.put("wind","2-3");
map1.put("temp","17-29°C");
list1.add(map1);
map2.put("date","2021-06-04");
map2.put("weather","1");
map2.put("wind","3-4");
map2.put("temp","17-30°C");
list1.add(map2);
List<Map<String,Object>> list = convertKeyCase(list1);
List<Map<String,Object>> list2 = convertKeyCase(list1,0);
System.out.println(list);
System.out.println(list2);
}
}
The fact that the input List contains LinkedHashMaps doesn't mean that the output will contain LinkedHashMaps.
If you want LinkedHashMaps, you must request them explicitly in the toMap() call:
static List<Map<String,Object>> convertKeyCase (List<Map<String,Object>> list,int...s) {
return list.stream()
.map(m -> m.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(p -> s.length == 0 || s[0]==1 ? p.getKey().toUpperCase():p.getKey().toLowerCase(),
Map.Entry::getValue,
(v1,v2)->v1,
LinkedHashMap::new)))
.collect(Collectors.toList());
}
This changes the output to:
[{DATE=2021-06-03, TEMP=17-29°C, WEATHER=1, WIND=2-3}, {DATE=2021-06-04, TEMP=17-30°C, WEATHER=1, WIND=3-4}]
[{date=2021-06-03, temp=17-29°C, weather=1, wind=2-3}, {date=2021-06-04, temp=17-30°C, weather=1, wind=3-4}]
After below two lines again you will get a List and natural sorting will be applied. You have to change your implementation to return LinkedHashMap there.
List<Map<String,Object>> list = convertKeyCase(list1);
List<Map<String,Object>> list2 = convertKeyCase(list1,0);
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();
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();
}
}
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;
});
});
So this program gives the occcurences of each word. I need to make a list with this data sorting these words in ascending order.
import java.util.*;
public class A1E5{
public static void main(String[] args) {
// Set text in a string
String text = "Good morning. Have a good class. " +
"Have a good visit. Have fun!";
// Create a TreeMap to hold words as key and count as value
Map<String, Integer> map = new TreeMap<>();
String[] words = text.split("[\\s+\\p{P}]");
for (int i = 0; i < words.length; i++) {
String key = words[i].toLowerCase();
if (key.length() > 0) {
if (!map.containsKey(key)) {
map.put(key, 1);
}
else {
int value = map.get(key);
value++;
map.put(key, value);
}
}
}
// Display key and value for each entry
map.forEach((k, v) -> System.out.println(k + "\t" + v));
}
}
Implement Comparator interface and override its compare method in java.
Obtain map.entrySet() in set, convert it into list (we have converted set to list because Collections’s sort method can accept only list type as parameter).
Call Collections.sort and pass list [i.e. listOfentrySet] as parameter.
Collections.sort internally calls Arrays.sort, Arrays.Sort() internally calls Merge Sort.Merge sort calls overridden compare method of Comparator interface for comparison of keys.Ultimately listOfentrySet will contain entry (key-value) pairs sorted on basis of keys in java.
Its a simple google search but here you are:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
class SortByKeyAscending implements Comparator<Map.Entry<Integer, Integer>>{
#Override
public int compare( Map.Entry<Integer,Integer> entry1, Map.Entry<Integer,Integer> entry2){
return (entry1.getKey()).compareTo( entry2.getKey() );
}
}
public class SortMapByKeyAscendingExample {
public static void main(String...a){
Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>();
map.put(4, 1);
map.put(2, 1);
map.put(3, 1);
map.put(5, 1);
Set<Entry<Integer, Integer>> entrySet = map.entrySet();
List<Entry<Integer, Integer>> listOfentrySet = new ArrayList<Entry<Integer, Integer>>(entrySet);
System.out.print("Before sorting by key : ");
for(Map.Entry<Integer, Integer> entry:listOfentrySet){
System.out.print(entry.getKey()+"="+entry.getValue()+" ");
}
Collections.sort(listOfentrySet, new SortByKeyAscending());
System.out.print("\nAfter sorting by key(ascending): ");
for(Map.Entry<Integer, Integer> entry:listOfentrySet)
System.out.print(entry.getKey()+"="+entry.getValue()+" ");
}
}
The output would be (key value)
Before sorting : 4=1 2=1 3=1 5=1
After sorting : 2=1 3=1 4=1 5=1
If I understood your requirement correctly, since you can't have a TreeMap itself sort on the values you want something like this:
List<String> wordsList = new ArrayList<String>();
map.forEach((k, v) -> wordsList.add(k));
System.out.println(Arrays.toString(wordsList.toArray()));
Output:
[a, class, fun, good, have, morning, visit]
Or this:
List<String> wordsList = new ArrayList<String>();
for(Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
for(int i = 0 ; i <= entry.getValue(); i++) {
wordsList.add(key);
}
}
System.out.println(Arrays.toString(wordsList.toArray()));
Output:
[a, a, a, class, class, fun, fun, good, good, good, good, have, have, have, have, morning, morning, visit, visit]
If you're trying to print out the results ordered by count, you can do this:
map.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.forEach(e -> System.out.println(e.getKey() + "\t" + e.getValue()));
You can also use streams to produce your frequency map in the first place:
Pattern.compile("[\\s+\\p{P}]")
.splitAsStream(text)
.map(String::toLowerCase)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.forEach(e -> System.out.println(e.getKey() + "\t" + e.getValue()));