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

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)]}

Related

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}

Counting the values of a map

The following map contains data of the form:
1946-01-12;13:00:00;0.3;G
1946-01-12;18:00:00;-2.8;Y
1946-01-13;07:00:00;-6.2;G
1946-01-13;13:00:00;-4.7;G
The dates are the keys.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
public class WeatherDataHandler {
private NavigableMap<LocalDate, List<Weather>> weatherData =
new TreeMap<>();
public void loadData(String filePath) throws IOException {
List<String> fileData =
Files.readAllLines(Paths.get(filePath));
for (String str : fileData) {
List<String> parsed = parseData(str);
LocalDate date = LocalDate.parse(parsed.get(0));
LocalTime time = LocalTime.parse(parsed.get(1));
double temperature = Double.parseDouble(parsed.get(2));
String quality = parsed.get(3);
Weather weather =
new Weather(date, time, temperature, quality);
List<Weather> entries;
entries = new ArrayList<Weather>();
if (weatherData.get(date) == null) {
entries.add(weather);
weatherData.put(date, entries);
} else {
entries = weatherData.get(date);
entries.add(weather);
}
}
}
private List<String> parseData(String str) {
return Arrays.asList(str.split(";"));
}
}
Now, I want to implement a method that counts the number of entries of every key, or in other words, the number of times every date occur in the list. It shall return all dates between two dates (that is user input) and the number of values of every key. I started with the following code
/**
* Search for missing values between the two dates (inclusive) assuming there
* should be 24 measurement values for each day (once every hour). Result is
* sorted by date.
*/
public Map<LocalDate, Integer> missingValues(LocalDate dateFrom, LocalDate dateTo) {
Map<LocalDate, Integer> counts = weatherData.subMap(dateFrom , dateTo)
.values().stream()
.flatMap(List::stream)
.filter(p -> p.getValue()
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
}
}
but I am having troubles with the filter and collectors method. How can I finish this?
Use Collectors.groupingBy to group by date of Weather class and Collectors.counting() to count the size of every group.
Map<LocalDate, Long> counts = weatherData.subMap(dateFrom ,dateTo)
.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.groupingBy(p -> p.getDate(), Collectors.counting()));
What you effectively need is to group by the date as the key element of the map. In the value, you want how many times each key has occurred.
For this, you will have to use Collectors.groupingBy while collecting the map. and for the value, it would be simply Collectors.counting(). Please check the code snippet below for more detail:
Map<LocalDate, Long> countMap = weatherData
.subMap(dateFrom ,dateTo)
.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.groupingBy(p -> p.getDate(), Collectors.counting()));
Since you are using streams to simplify your code you may also want to do the following.
Instead of doing
List<Weather> entries;
entries = new ArrayList<Weather>();
if (weatherData.get(date) == null) {
entries.add(weather);
weatherData.put(date, entries);
} else {
entries = weatherData.get(date);
entries.add(weather);
}
You can just do
weatherData.computeIfAbsent(date, k->new ArrayList<>()).add(weather);
It says that if the key date is not there, put in a new ArrayList as the value. In any event, return the value. Since the value is the list that was either just entered or already there, you can then simply add the weather instance to the list.
The compiler already knows the type of list since it derived that from your NavigableMap instance.
If you want to check it out first, try this simple example.
Map<Integer,List<Integer>> map = new HashMap<>();
System.out.println(map);
map.computeIfAbsent(10, k-> new ArrayList<>()).add(20);
System.out.println(map);
map.computeIfAbsent(10, k-> new ArrayList<>()).add(30);
System.out.println(map);
computeIfAbsent has been available since Java 1.8

Get only key from List of Map object using stream

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

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

Java 8 extract first key from matching value in a Map

Suppose I have a map of given name, surname pairs and I want to find the given name of the first entry in that map that has the surname matching a certain value.
How would we do this in a java 8 fashion.
In my test case example below I put two ways that would do it.
However the first one (looking for the given name of the first person with a surname of "Donkey") will throw java.util.NoSuchElementException: No value present so it is not safe.
The second one works but it is not only harder to read but it it is a bit not quite functional.
Just wondering if someone here would suggest me an easier clearer way of achieving this using either stream() or forEach() or both.
#Test
public void shouldBeAbleToReturnTheKeyOfTheFirstMatchingValue() throws Exception {
Map<String, String> names = new LinkedHashMap<>();
names.put("John", "Doe");
names.put("Fred", "Flintstone");
names.put("Jane", "Doe");
String keyOfTheFirst = names.entrySet().stream().filter(e -> e.getValue().equals("Doe")).findFirst().get().getKey();
assertEquals("John", keyOfTheFirst);
try {
names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst().get();
} catch (NoSuchElementException e){
// Expected
}
Optional<Map.Entry<String, String>> optionalEntry = names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst();
keyOfTheFirst = optionalEntry.isPresent() ? optionalEntry.get().getKey() : null;
assertNull(keyOfTheFirst);
}
Thank you in advance.
To return a default value if there is no match, use Optional#orElse
names.entrySet().stream()
.filter(e -> e.getValue().equals("Donkey"))
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
From a similar question:
public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
return map.entrySet()
.stream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
Then you can select the first, if you want to. Remember that the key is unique, the value is not.
Edit:
The whole code (thanks #Peter Lawrey)
package test;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Map<String, String> names = new LinkedHashMap<>();
names.put("John", "Doe");
names.put("Fred", "Flintstone");
names.put("Jane", "Doe");
Optional<String> firstKey = names.entrySet().stream()
.filter(entry -> Objects.equals(entry.getValue(), "Doe"))
.map(Map.Entry::getKey).findFirst();
if (firstKey.isPresent()) {
System.out.println(firstKey.get());
}
}
}
The solution provided by #Misha is the best one if you don't want to use the third-party code. My library has the special shortcut method ofKeys for such cases as I discovered that it's quite common task:
StreamEx.ofKeys(names, "Donkey"::equals).findFirst().orElse(null);
I like old fashioned:
static <K, V> K findFirstKeyByValue(Map<K, V> map, String value) {
for (Entry<K, V> e : map.entrySet())
if (e.getValue().equals(value))
return e.getKey();
return null;
}
Below is my code snippet to get key from map,
Map<String,String> pageDetails = new HashMap<String,String>();
public String getAssociatedKey(){
pageDetails.entrySet().stream().filter( e -> e.getValue().contains("John").findFirst().get().getKey();
}
In order to avoid null pointer exception if map entry exist with null value:
private String getValueByKey(Map<String, String> map, String key)
{
return map.entrySet().stream().filter(e ->
StringUtils.equalsIgnoreCase(e.getKey(), key)).findFirst().get()
.getValue();
}

Categories