Map is defined as:
Map<Integer,String> map = new HashMap<>();
map.put(2,"ram");
map.put(3,"ram");
map.put(4,"gopal");
map.put(5,"madan");
map.put(6,"shyam");
map.put(7,"gopal");
map.put(8,"ram");
My expected output is List which contains only keys for which there is no duplicate values.
5
6
My approach and thought process:
Thought process 1 :
I would take map.entrySet().stream().map(....) and then take another stream inside map and filter the values for which duplicate values are present.
The approach soon got wasted as the first indexed value would be again compared in the nested stream and i would happen to filter out all elements thus.
Thought process 2
I kept values in different List by :
List<String> subList = map.entrySet().stream()
.map((k)->k.getValue())
.collect(Collectors.toList());
and then:
map.entrySet().stream()
.filter(s ->
subList.contains(s.getValue()) )
.map(Map.Entry::getKey)
.collect(Collectors.toList());
But I am getting the output as
2
3
4
5
6
7
8
The output is obvious as the value that i am picking as s from stream i am comparing it in the pool where the value will be always present at-least one time.
I again thought then that if i could have a counter that count count and if the value is present then it would increment, but again all seems very much vague now.
Any ways in which i can iterate by index using stream, so that i can always leave the key value which i am taking and just comparing with rest of values.
will love to get a brief explanation.
You can break the task into two step. first count value repeating by groupingBy() and counting() collectors.
Map<String,Long> valueCount = map.values()
.stream()
.collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));
Its result is:
{madan=1, shyam=1, gopal=2, ram=3}
second step is to find only keys which their values are not duplicate. so to attain this you can use filter() and filtering the map by previous step result.
map.entrySet()
.stream()
.filter(entry -> valueCount.get(entry.getValue())==1).map(Map.Entry::getKey)
.collect(Collectors.toList())
You can filter the values with frequency 1 while creating the subList such as:
Set<String> uniqueSet = map.values().stream()
.collect(Collectors.groupingBy(a -> a, Collectors.counting()))
.entrySet().stream()
.filter(a -> a.getValue() == 1)
.map((Map.Entry::getKey))
.collect(Collectors.toSet());
and then perform the same operation as:
Set<Integer> result = map.entrySet().stream()
.filter(e -> uniqueSet.contains(e.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
Or as Holger pointed out in the comments, instead of counting, you could do away with a Boolean value to filter unique values as:
Set<String> uniqueSet = map.values().stream()
.collect(Collectors.toMap(Function.identity(), v -> true, (a,b) -> false))
.entrySet().stream()
.filter(Map.Entry::getValue)
.map((Map.Entry::getKey))
.collect(Collectors.toSet());
Related
I have a map of the form Map<String, List<Product>>.
I'd like to find the String in this Map that has the largest associated List.
I have tried so, so many things. Most recently:
map.entrySet()
.stream()
.max(Comparator((String entry1, String entry2) -> Integer.compare(q4().get(entry1).size(), q4.get(entry2).size()));
Nothing has worked.
If you're finding the key with the largest list, you can do this:
.max(Comparator.comparingInt(entry -> entry.getValue().size())).map(Entry::getKey)
So your code can be
String result = map.entrySet().stream()
.max(Comparator.comparingInt(entry -> entry.getValue().size()))
.map(Map.Entry::getKey)
.orElse(null); //if map is empty
I have 2 lists as the following
List<String> firstList = Arrays.asList("E","B","A","C");
List<String> secondList = Arrays.asList("Alex","Bob","Chris","Antony","Ram","Shyam");
I want the output in the form of a map having values in the second list mapped to elements in the first list based on first character.
For example I want the output as
Map<String,List<String>> outputMap;
and it has the following content
key -> B, value -> a list having single element Bob
key -> A, value -> a list having elements Alex and Antony
key -> C, value -> a list having single element Chris
I did something like this
firstList.stream()
.map(first->
secondList.stream().filter(second-> second.startsWith(first))
.collect(Collectors.toList())
);
and can see the elements of the second list group by first character. However I am unsure as to how to store the same in a map .
Please note that my question is more from the perspective of using the streaming API to get the job done.
I'm pretty sure that instead of nesting streaming of both lists you should just group the second one by first letter and filter values by testing whether the first letter is in the first list
final Map<String, List<String>> result = secondList.stream()
.filter(s -> firstList.contains(s.substring(0, 1)))
.collect(Collectors.groupingBy(s -> s.substring(0, 1)));
You can also extract s.substring(0, 1) to some
String firstLetter(String string)
method to make code a little bit more readable
Just move that filter to a toMap() collector:
Map<String, List<String>> grouped = firstList.stream()
.collect(Collectors.toMap(first -> first, first -> secondList.stream()
.filter(second -> second.startsWith(first))
.collect(Collectors.toList())));
If you want only keys that have matching names, you can
.filter(first -> secondList.stream().anyMatch(second -> second.startsWith(first)))
Using a stream, how to sort a list of objects by field (in my case ,componentCode) that has the maximum number of duplicates, and then find distinct
I tried something like this, but how to add the size of the duplicates when sorting.
List<String> conflictingComponentsCode = componentWarnings.stream()
.sorted(Comparator.comparing(ComponentErrorDetail::getComponentCode))
.map(ComponentErrorDetail::getComponentCode)
.distinct()
.collect(Collectors.toList());
Very similar to #nafas option:
List<String> conflictingComponentsCode = componentWarnings.stream()
.map(ComponentErrorDetail::getComponentCode)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
You can check this question for another example of grouping by count: Group by counting in Java 8 stream API
the idea is to use Collectors.groupingBy function to make a map (value to count), then sort the map in reverse order then map back to list again.
here a rough implementation:
List<String> conflictingComponentsCode =
componentWarnings.stream()
.map(ComponentErrorDetail::getComponentCode).collect(
Collectors.groupingBy(
Function.identity(), Collectors.counting())
)
//sort map
.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue()
.reversed())
//map to list
.map(entry -> entry.key()).collect(Collectors.toList());
;
I wanted to collect the result to a TreeMap<Integer,Double> by processing a
TreeMap<Integer,ArrayList<String>>.
TreeMap<Integer,Double> result2 = units.entrySet().stream()
.filter(v -> v.getValue().size()>3)
.filter(v -> !v.getValue().get(1).isEmpty() && !v.getValue().get(2).isEmpty())
.mapToDouble(v -> mult( v.getValue().get(1), v.getValue().get(2)))
.collect();
Basically what I am doing is that I get the values from the ArrayList of Strings from the stream, and filter out the ones with no values and get a product of the 2nd and 3rd element by using mult function inside the lambda expression. Now I don't know how to collect to a TreeMap where the key is the key of the processed TreeMap called units and the value should be the product that I calculated in the mapToDouble.
NOTE: units is a TreeMap<Integer,ArrayList<String>>
You can collect the data to a TreeMap by using the overloaded version of toMap(keyMapper, valueMapper, mergeFunction, mapSupplier) method that allows you to specify which Map to create.
TreeMap<Integer,Double> result2 = units.entrySet().stream()
.filter(v -> v.getValue().size()>3)
.filter(v -> !v.getValue().get(1).isEmpty() && !v.getValue().get(2).isEmpty())
.collect(Collectors.toMap(
entry -> entry.getKey(),
entry-> mult( entry.getValue().get(1), entry.getValue().get(2)),
(entry1, entry2) -> entry1, // called if duplicate keys are there, I have return entry1 by default by you can modify it according to you
TreeMap::new
));
you can refer this to look at overloads of toMap method.
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
Map is Map<String, List<User>> and List is List<User>. I want to use
Map<String,List<User>> newMap = oldMap.stream()
.filter(u ->userList.stream()
.filter(ul ->ul.getName().equalsIgnoreCase(u.getKey()).count()>0))
.collect(Collectors.toMap(u.getKey, u.getVaule()));
can't change to new Map. Why?
There are several problems with your code:
Map does not have a stream(): its entry set does, so you need to call entrySet() first.
There are a couple of misplaced parentheses
Collectors.toMap code is incorrect: you need to use the lambda u -> u.getKey() (or the method-reference Map.Entry::getKey) and not just the expression u.getKey(). Also, you mispelled getValue().
This would be a corrected code:
Map<String, List<User>> newMap =
oldMap.entrySet()
.stream()
.filter(u -> userList.stream()
.filter(ul ->ul.getName().equalsIgnoreCase(u.getKey())).count() > 0
).collect(Collectors.toMap(u -> u.getKey(), u -> u.getValue()));
But a couple of notes here:
You are filtering only to see if the count is greater than 0: instead you could just use anyMatch(predicate). This is a short-cuiting terminal operation that returns true if the predicate is true for at least one of the elements in the Stream. This has also the advantage that this operation might not process all the elements in the Stream (when filtering does)
It is inefficient since you are traversing the userList every time you need to filter a Stream element. It would be better to use a Set which has O(1) lookup (so first you would convert your userList into a userSet, transforming the username in lower-case, and then you would search this set for a lower-case value username).
This would be a more performant code:
Set<String> userNameSet = userList.stream().map(u -> u.getName().toLowerCase(Locale.ROOT)).collect(toSet());
Map<String,List<User>> newMap =
oldMap.entrySet()
.stream()
.filter(u -> userNameSet.contains(u.getKey().toLowerCase(Locale.ROOT)))
.collect(Collectors.toMap(u -> u.getKey(), u -> u.getValue()));
Perhaps you intended to create a Stream of the entry Set of the input Map.
Map<String,List<User>> newMap =
oldMap.entrySet().stream()
.filter(u ->userList.stream().filter(ul ->ul.getName().equalsIgnoreCase(u.getKey())).count()>0)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
This would create a Map that retains the entries of the original Map whose keys equal the name of at least one of the members of userList (ignoring case).