I'm new to streams and I am trying to filter through this map for the first true value in a key/value pair, then I want to return the string Key, and replace the Value of true with false.
I have a map of strings/booleans:
Map<String, Boolean> stringMap = new HashMap<>();
//... added values to the map
String firstString = stringMap.stream()
.map(e -> entrySet())
.filter(v -> v.getValue() == true)
.findFirst()
//after find first i'd like to return
//the first string Key associated with a true Value
//and I want to replace the boolean Value with false.
That is where I am stuck--I might be doing the first part wrong too, but I'm not sure how to both return the string value and replace the boolean value in the same stream? I was going to try to use collect here to deal with the return value, but I think if I did that it would maybe return a Set rather than the string alone.
I could work with that but I would prefer to try to just return the string. I also wondered if I should use Optional here instead of the String firstString local variable. I've been reviewing similar questions but I can't get this to work and I'm a bit lost.
Here are some of the similar questions I've checked by I can't apply them here:
Sort map by value using lambdas and streams
Modify a map using stream
Map doesn't have a stream() method, also your .map() doesn't really make sense. What is entrySet() in that context? And at last, findFirst() returns an Optional so you'd either change the variable, or unwrap the Optional.
Your code could look something like this:
String first = stringMap.entrySet().stream()
.filter(Map.Entry::getValue) // similar to: e -> e.getValue()
.map(Map.Entry::getKey) // similar to: e -> e.getKey()
.findFirst()
.orElseThrow(); // throws an exception when stringMap is empty / no element could be found with value == true
Please also note that the "first" element doesn't really make sense in the context of maps. Because a normal map (like HashMap) has no defined order (unless you use SortedMap like TreeMap).
At last, you shouldn't modify the input map while streaming over it. Find the "first" value. And then simply do:
stringMap.put(first, false);
Optional<String> firstString = stringMap.entrySet().stream()
.filter( v-> v.getValue() == true )
.map( e -> e.getKey())
.findFirst();
Your ordering of the operations seems to be off.
stringMap.entrySet().stream()
On a map you could stream the key set, or the entry set, or the value collection. So make sure you stream the entry set, because you'll need access to both the key for returning and the value for filtering.
.filter( v-> v.getValue() == true )
Next filter the stream of entries so that only entries with a true value remain.
.map( e -> e.getKey())
Now map the stream of entries to just the String value of their key.
.findFirst();
Find the first key whose value is true. Note that the entries in a hash map are in no particular order. The result of the find first operation is as you already mentioned an optional value.
Related
I'm fairly new to Java and trying to learn how to use streams for easier code writing. If I can code like this:
Map<String, SomeConfig> temp = new HashMap<>();
resultStorage.forEach((key, value) -> key.getUsers().forEach(user -> {
if (!temp.containsKey(user.getMeta())) {
SomeConfig emailConfiguration = key
.withCheck1(masterAccountId)
.withCheck2(getClientTimezone())
.withCheck3(user.getMeta());
temp.put(user.getMeta(), emailConfiguration);
}
temp.get(user. getMeta()).getStreams().add(value);
}));
return new ArrayList<>(temp.values());
resultStorage declaration:
private Map< SomeConfig, byte[]> resultStorage = new ConcurrentHashMap<>();
getStreams is a getter on SomeConfig that returns a List<byte[]> as here:
private List<byte[]> attachmentStreams = new ArrayList<>();
public List<byte[]> getAttachmentStreams() {
return attachmentStreams;
}
My first attempt was something similar to this:
resultStorage.entrySet().stream()
.forEach(entry -> entry.getKey().getUsers().forEach(user -> {
}));
Are we able to use a forEach within one of the streams terminating operation, forEach? How would a stream benefit in this case as I saw documentation that it can significantly improve readability and performance of older pre-Java8 code?
Edit:
resultStorage holds a ConcurrentHashMap. It will contain Map<SomeConfig, byte[]> for email and attachments. Using another HashMap temp that is initially empty - we analyze resultStorage , see if temp contains a specific email key, and then put or add based on the existence of a user's email
The terminal operation of entrySet().stream().forEach(…) is entirely unrelated to the getUsers().forEach(…) call within the Consumer. So there’s no problem of “multiple terminal operations” here.
However, replacing the Map operation forEach((key, value) -> … with an entrySet() .stream() .forEach(entry -> …) rarely adds a benefit. So far, you’re not only made the code longer, you introduced the necessity to deal with a Map.Entry instead of just using key and value.
But you can simplify your operation by using a single computeIfAbsent instead of containsKey, put, and get:
resultStorage.forEach((key, value) -> key.getUsers().forEach(user ->
temp.computeIfAbsent(user.getMeta(), meta ->
key.withCheck1(masterAccountId).withCheck2(getClientTimezone()).withCheck3(meta))
.getStreams().add(value)));
Notes after the code.
Map<String, SomeConfig> temp = resultStorage.keySet()
.stream()
.flatMap(key -> key.getUsers()
.stream()
.map(user -> new AbstractMap.SimpleEntry(user, key)))
.collect(Collectors.toMap(e -> e.getKey().getMeta(),
e -> e.getValue()
.withCheck1(masterAccountId)
.withCheck2(getClientTimezone())
.withCheck3(e.getKey().getMeta())
resultStorage.keySet()
This returns Set<SomeConfig>.
stream()
This returns a stream where every element in the stream is an instance of SomeConfig.
.flatMap(key -> key.getUsers()
.stream()
.map(user -> new AbstractMap.SimpleEntry(user, key)))
Method flatMap() must return a Stream. The above code returns a Stream where every element is an instance of AbstractMap.SimpleEntry. The "entry" key is the user and the entry value is the key from resultStorage.
Finally I create a Map<String, SomeConfig> via [static] method toMap of class Collectors.
The first argument to method toMap is the key mapper, i.e. a method that extracts the [map] key from the AbstractMap.SimpleEntry. In your case this is the value returned by method getMeta() of the user – which is the key from AbstractMap.SimpleEntry, i.e. e.getKey() returns a user object.
The second argument to toMap is the value mapper. e.getValue() returns a SomeConfig object and the rest is your code, i.e. the withChecks.
There is no way I can test the above code because not only did you not post a minimal, reproducible example, you also did not post any sample data. Hence the above may be way off what you actually require.
Also note that the above code simply creates your Map<String, SomeConfig> temp. I could not understand the code in your question that processes that Map so I did not try to implement that part at all.
I have a map as below
Map<String, String> myMap = new HashMap<>();
myMap.put("a", "Something");
myMap.put("b", null);
myMap.put("c", "more");
and a list,
List<String> myList = Arrays.asList("a","b");
I want to check, whether all the values in myMap with keys in myList are null
I have created a method as follows and it works fine. I wanted to check whether we can achieve the same in one line of code using stream
myMap.values().removeIf(Objects::isNull);
Map<String, String> resultMap = myList.stream().filter(myMap::containsKey).collect(Collectors.toMap(Function.identity(), myMap::get));
if(!resultMap.isEmpty()){
// Atleast one not null value is present in myMap with key in myList
}
Sure, simply check if all elements in the list match a non-null value from the map:
myList.stream().allMatch(x -> myMap.containsKey(x) && myMap.get(x) == null);
// or (more overhead, but you might prefer its expressivness):
myList.stream()
.filter(myMap::containsKey)
.map(myMap::get)
.allMatch(Objects::isNull);
Or, if you consider "missing keys" to be equivalent to "having null":
myList.stream().map(myMap::get).allMatch(Objects:isNull);
Map.get specifies that keys that aren't present return null. So you can filter out keys mapped to null or not mapped at all with just one null check.
Map<String, String> resultMap = myList.stream()
.filter(key -> myMap.get(key) != null)
.collect(Collectors.toMap(Function.identity(), myMap::get));
If you don't need a resultMap it's even shorter with anyMatch
myList.stream().allMatch(key -> myMap.get(key) != null)
Unlike myMap.values().removeIf(Objects::isNull) this will not modify the original map.
So, you've removed the entries having null values with this line:
myMap.values().removeIf(Objects::isNull);
Fine, since keeping null-references in the collections an antipattern because these elements can't provide any useful information. So I consider this was your intention unrelated to checking if all the string in myList were associated with null (or not present).
Now to check whether myMap contain any of the elements from myList (which would automatically imply that the value mapped to such element is non-null) you can create a stream over the contents of myList and check each element against the key-set of myMap:
boolean hasNonNullValue = myList.stream().anyMatch(myMap.keySet()::contains);
I suspect you might to perform some actions with such keys (if any). If so, then instead of performing a check provided above would make sense to generate a list of these keys:
List<String> keysToExamine = myList.stream()
.filter(myMap.keySet()::contains)
.toList(); // for JDK versions earlier then 16 use .collect(Collectors.toList()) instead of toList()
Note: check elements of the list against the key-set, not the opposite, otherwise you might cause performance degradation.
Stream#findAny and Optional#ifPresent
It seems you want to perform some action if at least one non-null value is present in myMap with the corresponding key in myList. If yes, this combination meets your requirement perfectly.
myMap.keySet()
.stream()
.filter(k -> myMap.get(k) != null && myList.contains(k))
.findAny()
.ifPresent(
// Atleast one not null value is present in myMap with key in myList
System.out::println // A sample action
);
Demo
I have the following HashMap in my Java app:
final Map<UUID, Boolean> map = demoRepo.demoService.stream()
.collect(Collectors.toMap(
ProductDTO::getMenuUuid,
ProductDTO::getStatus));
However, as the result contains multiple menuUuid value, I need to group them as the key does not contain the same value. So, how should I do this using stream?
Update: I also tried groupingBy as shown below, but I think the usage is not correct:
final Map<UUID, Boolean> map = sdemoRepo.demoService.stream()
.collect(Collectors.groupingBy(
ProductDTO::getMenuUuid, LinkedHashMap::new,
Collectors.mapping(ProductDTO::getStatus)));
Suppose that I have the following stream:
MenuUuid | Status |
-----------------------
1 true
2 false
1 true
1 true
3 true
2 false
Then I need a map result like; 1:true, 2:false, 3:true
If all the user ids have the same boolean then just do the following:
final Map<UUID, Boolean> map = demoRepo.demoService.stream()
.collect(Collectors.toMap(
ProductDTO::getMenuUuid,
ProductDTO::getStatus,
(existingValue, newValue)->existingValue));
the last lambda, is a merge function. it is used to make a decision on how to merge duplicate keys. In this case, it just keeps the first that's already there. You could also use the new one since you aren't really altering the boolean and they are all the same value.
If your ProductDTO class uses UUID to determine equality via equals() you could also do the following:
final Map<UUID, Boolean> map = demoRepo.demoService.stream()
.distinct()
.collect(Collectors.toMap(
ProductDTO::getMenuUuid,
ProductDTO::getStatus));
This works because you won't have any duplicate UUID's
The Collectors.toMap(Function keyMapper,
Function valueMapper,
BinaryOperator mergeFunction) allows the caller to define a BinaryOperator mergeFunction that returns the correct value if the stream provides a key-value pair for a key that already exists.
The following example uses the Boolean.logicalOr method to combine the existing value with the new one.
Equal UUID keys are not grouped,since the result is a single key-single value map. But
final Map<UUID, Boolean> map = demoRepo.demoService.stream()
.collect(Collectors.toMap(
ProductDTO::getMenuUuid,
ProductDTO::getStatus,
Boolean::logicalOr));
In particular Boolean::logicalOr is a function that is called if the map already contains a value for the key. In this case, the Boolean::logicalOr takes as argument the existing map value and the new value from the Stream` and returns the Logical Or of these two, as the result. This results is then entered in the map.
You will have to merge the values. See if it helps
final Map<UUID, Boolean> map =
demoRepo.stream().filter(Objects::nonNull)
.collect(
Collectors.toMap(
ProductDTO::getMenuUuid,
ProductDTO::getStatus,
(menuUuid, status) -> {
menuUuid.addAll(status);
return menuUuid;
}));
I am trying to rewrite the method below using streams but I am not sure what the best approach is? If I use flatMap on the values of the entrySet(), I lose the reference to the current key.
private List<String> asList(final Map<String, List<String>> map) {
final List<String> result = new ArrayList<>();
for (final Entry<String, List<String>> entry : map.entrySet()) {
final List<String> values = entry.getValue();
values.forEach(value -> result.add(String.format("%s-%s", entry.getKey(), value)));
}
return result;
}
The best I managed to do is the following:
return map.keySet().stream()
.flatMap(key -> map.get(key).stream()
.map(value -> new AbstractMap.SimpleEntry<>(key, value)))
.map(e -> String.format("%s-%s", e.getKey(), e.getValue()))
.collect(Collectors.toList());
Is there a simpler way without resorting to creating new Entry objects?
A stream is a sequence of values (possibly unordered / parallel). map() is what you use when you want to map a single value in the sequence to some single other value. Say, map "alturkovic" to "ALTURKOVIC". flatMap() is what you use when you want to map a single value in the sequence to 0, 1, or many other values. Hence why a flatMap lambda needs to turn a value into a stream of values. flatMap can thus be used to take, say, a list of lists of string, and turn that into a stream of just strings.
Here, you want to map a single entry from your map (a single key/value pair) into a single element (a string describing it). 1 value to 1 value. That means flatMap is not appropriate. You're looking for just map.
Furthermore, you need both key and value to perform your mapping op, so, keySet() is also not appropriate. You're looking for entrySet(), which gives you a set of all k/v pairs, juts what we need.
That gets us to:
map.entrySet().stream()
.map(e -> String.format("%s-%s", e.getKey(), e.getValue()))
.collect(Collectors.toList());
Your original code makes no effort to treat a single value from a map (which is a List<String>) as separate values; you just call .toString() on the entire ordeal, and be done with it. This means the produced string looks like, say, [Hello, World] given a map value of List.of("Hello", "World"). If you don't want this, you still don't want flatmap, because streams are also homogenous - the values in a stream are all of the same kind, and thus a stream of 'key1 value1 value2 key2 valueA valueB' is not what you'd want:
map.entrySet().stream()
.map(e -> String.format("%s-%s", e.getKey(), myPrint(e.getValue())))
.collect(Collectors.toList());
public static String myPrint(List<String> in) {
// write your own algorithm here
}
Stream API just isn't the right tool to replace that myPrint method.
A third alternative is that you want to smear out the map; you want each string in a mapvalue's List<String> to first be matched with the key (so that's re-stating that key rather a lot), and then do something to that. NOW flatMap IS appropriate - you want a stream of k/v pairs first, and then do something to that, and each element is now of the same kind. You want to turn the map:
key1 = [value1, value2]
key2 = [value3, value4]
first into a stream:
key1:value1
key1:value2
key2:value3
key2:value4
and take it from there. This explodes a single k/v entry in your map into more than one, thus, flatmapping needed:
return map.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(v -> String.format("%s-%s", e.getKey(), v))
.collect(Collectors.toList());
Going inside-out, it maps a single entry within a list that belongs to a single k/v pair into the string Key-SingleItemFromItsList.
Adding my two cents to excellent answer by #rzwitserloot. Already flatmap and map is explained in his answer.
List<String> resultLists = myMap.entrySet().stream()
.flatMap(mapEntry -> printEntries(mapEntry.getKey(),mapEntry.getValue())).collect(Collectors.toList());
System.out.println(resultLists);
Splitting this to a separate method gives good readability IMO,
private static Stream<String> printEntries(String key, List<String> values) {
return values.stream().map(val -> String.format("%s-%s",key,val));
}
I have a stream of words and I would like to sort them according to the occurrence of same elements (=words).
e.g.: {hello, world, hello}
to
Map<String, List<String>>
hello, {hello, hello}
world, {world}
What i have so far:
Map<Object, List<String>> list = streamofWords.collect(Collectors.groupingBy(???));
Problem 1: The stream seems to lose the information that he is processing Strings, therefore the compiler forces me to change the type to Object, List
Problem 2: I don't know what to put inside the parentesis to group it by the same occurrence. I know that I am able to process single elements within th lambda-expression but I have no idea how to reach "outside" each element to check for equality.
Thank You
To get a Map<String, List<String>>, you just need to tell to the groupingBy collector that you want to group the values by identity, so the function x -> x.
Map<String, List<String>> occurrences =
streamOfWords.collect(groupingBy(str -> str));
However this a bit useless, as you see you have the same type of informations two times. You should look into a Map<String, Long>, where's the value indicates the occurrences of the String in the Stream.
Map<String, Long> occurrences =
streamOfWords.collect(groupingBy(str -> str, counting()));
Basically instead of having a groupingBy that return values as List, you use the downstream collector counting() to tell that you want to count the number of times this value appears.
Your sort requirement should imply that you should have a Map<Long, List<String>> (what if different Strings appear the same number of times?), and as the default toMap collector returns an HashMap, it has no notions of ordering, but you could store the elements in a TreeMap instead.
I've tried to summarize a bit what I've said in the comments.
You seems to have troubles with how str -> str can tell whether "hello" or "world" are different.
First of all str -> str is a function, that is, for an input x yields a value f(x). For example, f(x) = x + 2 is a function that for any value x returns x + 2.
Here we are using the identity function, that is f(x) = x. When you collect the elements from the pipeline in the Map, this function will be called before to obtain the key from the value. So in your example, you have 3 elements for which the identity function yields:
f("hello") = "hello"
f("world") = "world"
So far so good.
Now when collect() is called, for every value in the stream you'll apply the function on it and evaluate the result (which will be the key in the Map). If a key already exists, we take the currently mapped value and we merge in a List the value we wanted to put (i.e the value from which you just applied the function on) with this previous mapped value. That's why you get a Map<String, List<String>> at the end.
Let's take another example. Now the stream contains the values "hello", "world" and "hey" and the function that we want to apply to group the elements is str -> str.substring(0, 2), that is, the function that takes the first two characters of the String.
Similarly, we have:
f("hello") = "he"
f("world") = "wo"
f("hey") = "he"
Here you see that both "hello" and "hey" yields the same key when applying the function and hence they will be grouped in the same List when collecting them, so that the final result is:
"he" -> ["hello", "hey"]
"wo" -> ["world"]
To have an analogy with mathematics, you could have take any non-bijective function, such as x2. For x = -2 and x = 2 we have that f(x) = 4. So if we grouped integers by this function, -2 and 2 would have been in the same "bag".
Looking at the source code won't help you to understand what's going on at first. It's useful if you want to know how it's implemented under the hood. But try first to think of the concept with a higher level of abstraction and then maybe things will become clearer.
Hope it helps! :)
The KeyExtractor you are searching for is the identity function:
Map<String, List<String>> list = streamofWords.collect(Collectors.groupingBy(Function.identity()));
EDIT added explanation:
Function.identity() retuns a 'Function' with one method that does nothing more than returning the argument it gets.
Collectors.groupingBy(Function<S, K> keyExtractor) provides a collector, which collects all elements of the stream to a Map<K, List<S>>. It is using the keyExtractor implementation it gets to inspect the stream's objects of type S and deduce a key of type K from them. This key is the map's key used to get (or create) the list in the result map the stream element is added to.
If you want to group by some fields of an object, not a whole object and you don't want to change your equals and hashCode methods I'd create a class holding a set of keys for grouping purposes:
import java.util.Arrays;
#Getter
public class MultiKey {
public MultiKey(Object... keys) {
this.keys = keys;
}
private Object[] keys;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MultiKey multiKey = (MultiKey) o;
return Arrays.equals(keys, multiKey.keys);
}
#Override
public int hashCode() {
return Arrays.hashCode(keys);
}
}
And the groupingBy itself:
Map<MultiKey, List<VhfEventView>> groupedList = list
.stream()
.collect(Collectors.groupingBy(
e -> new MultiKey(e.getGroupingKey1(), e.getGroupingKey2())));