Get only key from List of Map object using stream - java

I am trying to get the Get only the key values from the List of Map object using the stream in java 8.
When I stream the List of map object I am getting Stream<List<String>> instead of List<String>.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Hello World");
Map<String, String> a = new HashMap<String, String>();
a.put("1", "Bharathi");
a.put("2", "Test");
a.put("3", "Hello");
List<Map<String, String>> b = new ArrayList<>();
b.add(a);
System.out.println("Hello World" + b);
/*
* b.stream().map(c-> c.entrySet().stream().collect( Collectors.toMap(entry ->
* entry.getKey(), entry -> entry.getValue())));
*/
Stream<List<String>> map2 = b.stream()
.map(c -> c.entrySet().stream().map(map -> map.getKey()).collect(Collectors.toList()));
//List<List<String>> collect = map2.map(v -> v).collect(Collectors.toList());
}
}
How to get the key object from the List of Map object?

you can use flatMap over the keySet of each Map within:
List<String> output = lst.stream()
.flatMap(mp -> mp.keySet().stream())
.collect(Collectors.toList());

You can simply flatMap it:
b.stream().flatMap(m -> m.keySet().stream()).collect(Collectors.toList())

Of course flatMap is stream base solution! however you can do it with non-stream version in simple way.
List<String> map2 = new ArrayList<>();
b.forEach(map -> map2.addAll(map.keySet()));

You also can use a slightly more declarative way:
List<String> collect = b.stream()
.map(Map::keySet) // map maps to key sets
.flatMap(Collection::stream) // union all key sets to the one stream
.collect(Collectors.toList()); // collect the stream to a new list

Related

How to get all entries with second highest value of a Hashmap?

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]

Remove elements from hashset inside hashmap while iterating through java stream [duplicate]

This question already has answers here:
what does java8 stream map do here?
(4 answers)
Closed 7 months ago.
I have a hashmap in Java with a string key and a HashSet value. The hashset may contain many PlacementBundles inside it.
public Map<String, Set<PlacementBundle>> placementByConcept;
I am trying to remove the value from the HashSet while iterating the map which matches a specific condition.
I tried the below code but cannot remove the matching element from the HashSet.
placementByConcept.entrySet()
.stream()
.map(e -> e.getValue()
.removeIf(s -> s.getBeatObjectiveId().equals("non-scored")));
you can use forEach:
placementByConcept.entrySet().forEach(e -> e.getValue().removeIf(s -> s.getBeatObjectiveId().equals("non-scored")));
public class Remove {
public static void main(String[] args)
{
HashMap<Integer, String>
map = new HashMap<>();
map.put(1, "Stack");
map.put(2, "Overflow");
map.put(3, "StackOverflow");
int keyToBeRemoved = 2;
System.out.println("Original HashMap: "
+ map);
map.entrySet()
.removeIf(
entry -> (keyToBeRemoved == entry.getKey()));
System.out.println("New HashMap: "
+ map);
}
}
Output:
Original HashMap: {1=Stack, 2=Overflow, 3=StackOverflow}
New HashMap: {1=Stack, 3=StackOverflow}
In your case Set<PlacementBundle> is an immutable collection. You can't remove an element from it.
Thank you Holger for pointing out the assumption I made which may not be true for the asked question.
If Set is immutable collection and you use foreach as suggested in the accepted answer, you will get UnsupportedOperationException
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
#Slf4j
public class Test {
public static void main(String[] args) {
Map<String, Set<PlacementBundle>> placementByConcept = new HashMap<>();
placementByConcept.put("concept1", Set.of(
PlacementBundle.builder().beatObjectiveId("scored").build(),
PlacementBundle.builder().beatObjectiveId("non-scored").build())
);
placementByConcept.put("concept2", Set.of(
PlacementBundle.builder().beatObjectiveId("scored").build(),
PlacementBundle.builder().beatObjectiveId("non-scored").build())
);
log.info("Original: {}", placementByConcept);
/* This won't give any exception, neither will remove the entries */
placementByConcept.entrySet()
.stream()
.map(e -> e.getValue()
.removeIf(s -> s.getBeatObjectiveId().equals("non-scored")));
log.info("Does not work: {}", placementByConcept);
/* This will give you the exception UnsupportedOperationException */
// placementByConcept.entrySet().forEach(e -> e.getValue().removeIf(s -> s.getBeatObjectiveId().equals("non-scored")));
/* This is one of the correct way */
for (Map.Entry<String, Set<PlacementBundle>> entry : placementByConcept.entrySet()) {
var filtered = entry.getValue().stream()
.filter(placementBundle -> !placementBundle.getBeatObjectiveId().equals("non-scored"))
.collect(Collectors.toUnmodifiableSet());
log.debug("New Value Set: {}", filtered);
entry.setValue(filtered);
}
log.info("After: {}", placementByConcept);
}
}
#Builder
#Data
class PlacementBundle {
private String beatObjectiveId;
}
Output:
Original: {concept2=[PlacementBundle(beatObjectiveId=scored), PlacementBundle(beatObjectiveId=non-scored)], concept1=[PlacementBundle(beatObjectiveId=scored), PlacementBundle(beatObjectiveId=non-scored)]}
Does not work: {concept2=[PlacementBundle(beatObjectiveId=scored), PlacementBundle(beatObjectiveId=non-scored)], concept1=[PlacementBundle(beatObjectiveId=scored), PlacementBundle(beatObjectiveId=non-scored)]}
After: {concept2=[PlacementBundle(beatObjectiveId=scored)], concept1=[PlacementBundle(beatObjectiveId=scored)]}

Why is it not in the order of key when converting key to uppercase even though I use LinkedHashMap

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);

Convert Properties to HashMap Directly Using Streams [duplicate]

This question already has an answer here:
Using Collectors.toMap to return a LinkedHashMap
(1 answer)
Closed 1 year ago.
I am trying to convert a properties object into a HashMap<String, String>. I am able to convert it to a Map, but not able to do it diectly to a HashMap.
So far, I have done this:
dbProperties.entrySet().stream().collect(Collectors.toMap(k -> k.getKey().toString(), v -> v.getValue().toString()));
This gives me a Map<Object, Object>. Is there way to get it to a HashMap<String, String> without casting or anything?
You can do it by providing a BinaryOperator and Supplier.
import java.util.HashMap;
import java.util.Properties;
import java.util.stream.Collectors;
public class Main {
public static void main(String args[]) {
Properties dbProperties = new Properties();
dbProperties.put("One", "1");
dbProperties.put("Two", "2");
HashMap<String, String> map =
dbProperties.entrySet()
.stream()
.collect(Collectors
.toMap(k -> k.getKey().toString(),
v -> v.getValue().toString(),
(v1, v2) -> v1,
HashMap::new
)
);
System.out.println(map);
}
}
Output:
{One=1, Two=2}

Java 8 stream Map<String, List<String>> sum of values for each key

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();

Categories