Java Streams | groupingBy same elements - java

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

Related

How to return a Key from HashMap based on its Value in Java

Consider the following:
for (Boolean i: x.values()) {
if (i == false) {
return // KEY;
}
}
In the above code, I am trying to iterate through the HashMap. And I want to return a Key when the value is false.
How can I do it?
You need to loop through the HashMap's entrySet:
for (Map.Entry<Object, Boolean> entry : x.entrySet()) {
if(entry.getValue() == false){
return entry.getKey();
}
}
In order to find first a key that is mapped to a particular value you need to iterate either over the key set or over the entry set.
The latter option is preferred because at each iteration step you will have a key-value pair on your hands. Therefore, it would be slightly faster than interrogating the map at every iteration (remainder: there could be collisions in a real hashmap, and finding the target node can take more time than accessing the value on a map-entry).
But you definitely can't use a collection of map values returned by a method values() like you're doing in the code-snippet you've posted. There's no fast way of retrieving a key when you have only a value on your hands.
In the example below, ill show how to obtain a key from a map of arbitrary type that matches the condition (a Predicate) provided dynamically at runtime using Stream API.
There's one important thing to consider: target value might not be present in the map. And the code should handle this case. We can provide a default value (either directly or via Supplier), throw an exception (if according to the application logic the given value is always expected to be present), or by utilizing methods ifPresent(), ifPresentOrElse() provided by the Optional class. In the example below, I've chosen to provide a default value.
Take a look at these tutorials for more information on lambda expressions and streams
public static void main(String[] args) {
Map<String, Boolean> sourceMap =
Map.of("A", true, "B", false, "C", true);
String result = getFirstKey(sourceMap, entry -> !entry.getValue(), "");
System.out.println(result);
}
public static <K, V> K getFirstKey(Map<K, V> map,
Predicate<Map.Entry<K, V>> condition,
K defaultKey) {
return map.entrySet().stream()
.filter(condition) // Stream<Map.Entry<K, V>>
.findFirst() // Optional<Map.Entry<K, V>>
.map(Map.Entry::getKey) // Optional<K>
.orElse(defaultKey); // or apply .orElseThrow() depending on your needs
}
Output
B // key assosiated with a value of `false`

Using Streams on a map and finding/replacing value

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.

Java Stream collect - how to deduce type?

I have been given a stream of words, Stream<String> words, and a class Pair<String,Integer> which realizes a simple tuple for (someString, someInt) with getter and setter methods for both elements called getFirst,setFirst,getSecond,setSecond.
I am now supposed to box each word of the stream into a Pair (word, 1), and then use a Collector to somehow make the whole thing tell me how often each word is in the text. Now I've looked up a Collector that should let me do what I want to, and passed it as .collect(...) to the stream.
But the whole thing is looking so complex, and the type inference and deduction and wildcards that are floating around in that topic aren't making it any easier, so that I got now no clue, just what it is I've created.
I've tried deducing it from the API, and tried all the things I could come up with, but none of it seems to match:
words
.map(x -> new Pair<String,Integer>(x,1))
.collect(Collectors.groupingBy(
x -> x.getFirst(),
Collectors.reducing(
(a,b) -> new Pair<String,Integer>(a.getFirst(), a.getSecond() + b.getSecond())
)
));
Try using Collectors.toMap:
Collection<Pair<String, Integer>> values = words.collect(Collectors.toMap(
Function.identity(),
s -> new Pair<>(s, 1),
(a, b) -> {a.setSecond(a.getSecond() + b.getSecond()); return a;}
)).values();
It creates a map from your stream, using provided:
keyMapper - a mapping function to produce keys
valueMapper - a mapping function to produce values
mergeFunction - a merge function, used to resolve collisions between values associated with the same key
So it groups your Pairs by string value to a map, and then you just call .values() to get a collection of Pairs
The easiest (though not necessarily most efficient) solution would be to group to a map and then convert the entries to pairs:
List<Pair<String, Integer>> pairs = words
.collect(Collectors.groupingBy(x -> x, Collectors.summingInt(x -> 1)))
.entrySet()
.stream()
.map(e -> new Pair(e.getKey(), e.getValue()))
.collect(Collectors.toList());
I agree that entering the world of collectors can be a bit frightening at the beginning, particularly if you need to deal with generic type parameters.
There are many ways to solve your problem, both with and without streams.
With streams:
Map<String, Pair<String, Integer>> map = words.stream()
.collect(Collectors.toMap(
word -> word,
word -> new Pair<>(word, 1),
(o, n) -> {
o.setSecond(o.getSecond() + n.getSecond());
return o;
}));
Collection<Pair<String, Integer>> result = map.values();
Collectors.toMap works by transforming each element of the stream into the keys (this is the 1st argument word -> word, which means we leave the word as is, so that it will be the key of the map), and by transforming each element of the stream into the values (this is the 2nd argument word -> new Pair<>(word, 1), which means that we've found the word for the first time, so we're creating a new Pair instance for that word with a count of 1).
The 3rd argument is a merge function that is to be used to merge values when the 1st argument returns a key that already belongs to the map. As maps can't have more than one entry for the same key, we need a way to merge the value that is already in the map for that key, with the new value produced by the 2nd argument. In this case, o stands for the old value and n for the new value. The way I merge values is by summing the counts for the word and setting the new count in the Pair instance that corresponds to the old value. There's no need to create a new instance of Pair with the word and the new count, as it's safe to accumulate the count by mutating the old instance of Pair.
Without streams:
Map<String, Pair<String, Integer>> map = new HashMap<>();
words.forEach(word -> map.merge(
word,
new Pair<>(word, 1),
(o, n) -> {
o.setSecond(o.getSecond() + n.getSecond());
return o;
}));
Collection<Pair<String, Integer>> result = map.values();
This uses Map.merge and has similar semantics as the previous code.

How to get the first key value from map using JAVA 8?

As for now I am doing :
Map<Item, Boolean> processedItem = processedItemMap.get(i);
Map.Entry<Item, Boolean> entrySet = getNextPosition(processedItem);
Item key = entrySet.getKey();
Boolean value = entrySet.getValue();
public static Map.Entry<Item, Boolean> getNextPosition(Map<Item, Boolean> processedItem) {
return processedItem.entrySet().iterator().next();
}
Is there any cleaner way to do this with java8 ?
I see two problems with your method:
it will throw an exception if the map is empty
a HashMap, for example, has no order - so your method is really more of a getAny() than a getNext().
With a stream you could use either:
//if order is important, e.g. with a TreeMap/LinkedHashMap
map.entrySet().stream().findFirst();
//if order is not important or with unordered maps (HashMap...)
map.entrySet().stream().findAny();
which returns an Optional.
Seems like you need findFirst here
Optional<Map.Entry<Item, Boolean>> firstEntry =
processedItem.entrySet().stream().findFirst();
Obviously a HashMap has no order, so findFirst might return a different result on different calls. Probably a more suitable method would be findAny for your case.

How to partition a list by predicate using java8?

I have a list a which i want to split to few small lists.
say all the items that contains with "aaa", all that contains with "bbb" and some more predicates.
How can I do so using java8?
I saw this post but it only splits to 2 lists.
public void partition_list_java8() {
Predicate<String> startWithS = p -> p.toLowerCase().startsWith("s");
Map<Boolean, List<String>> decisionsByS = playerDecisions.stream()
.collect(Collectors.partitioningBy(startWithS));
logger.info(decisionsByS);
assertTrue(decisionsByS.get(Boolean.TRUE).size() == 3);
}
I saw this post, but it was very old, before java 8.
Like it was explained in #RealSkeptic comment Predicate can return only two results: true and false. This means you would be able to split your data only in two groups.
What you need is some kind of Function which will allow you to determine some common result for elements which should be grouped together. In your case such result could be first character in its lowercase (assuming that all strings are not empty - have at least one character).
Now with Collectors.groupingBy(function) you can group all elements in separate Lists and store them in Map where key will be common result used for grouping (like first character).
So your code can look like
Function<String, Character> firstChar = s -> Character.toLowerCase(s.charAt(0));
List<String> a = Arrays.asList("foo", "Abc", "bar", "baz", "aBc");
Map<Character, List<String>> collect = a.stream()
.collect(Collectors.groupingBy(firstChar));
System.out.println(collect);
Output:
{a=[Abc, aBc], b=[bar, baz], f=[foo]}
You can use Collectors.groupingBy to turn your stream of (grouping) -> (list of things in that grouping). If you don't care about the groupings themselves, then call values() on that map to get a Collection<List<String>> of your partitions.

Categories

Resources