I'm trying to get a (sorted by value) list of map entries from a Map. I tried this:
List<Pair<String, AtomicInteger>> expectedList = expectedMap.entrySet().stream()
.sorted(Comparator.comparing(e -> -e.getValue().get()))
.map(e -> new Pair<>(e.getKey(), e.getValue())).collect(Collectors.toList());
but if I try to substitute Pair with Map.Entry, it tells me that Map.Entry is abstract and cannot be instantiated. Is there a way to adapt this construct to get a list of entries instead of a list of pairs?
See the Javadoc for Map.Entry:
Interface Map.Entry
All Known Implementing Classes:
AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry
Pick one implementing class that suits your needs, for example
List<Entry<String, AtomicInteger>> expectedList = expectedMap.entrySet().stream()
.sorted(Comparator.comparingInt(e -> -e.getValue().get()))
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), e.getValue()))
.collect(Collectors.toList());
But you could also just remove the map and use the entries that entrySet() returns:
List<Entry<String, AtomicInteger>> expectedList = expectedMap.entrySet().stream()
.sorted(Comparator.comparingInt(e -> -e.getValue().get()))
.collect(Collectors.toList());
PS.: If you are comparing primitive data types, you should use the right method like comparingInt. This avoids unnecessary boxing.
I don't see why you need to map Map.Entry to something else if you need Map.Entry.
Here's my take. I've opted to first create a list of entries and then sort it. Also used mor of the built-in API to construct the comparator.
List<Map.Entry<String, AtomicInteger>> expectedList =
new ArrayList<>(expectedMap.entrySet());
Collections.sort(expectedList,
Map.Entry.comparingByValue(
Comparator.comparingInt(AtomicInteger::get).reversed());
Related
How can we order the content of a map by comparing its values instead of the value of its keys? For example, how can we implement a method sortByAbsoluteValue that orders the map entries from the one with highest absolute value to lowest absolute value:
HashMap<String,Integer> hashmap=new HashMap<String,Integer>();
hashmap.put("product5",100);
hashmap.put("product6",-20);
hashmap.put("product3",10);
hashmap.put("product4",5);
hashmap.put("product1",15);
hashmap.put("product2",-40);
hashmap.put("product9",0);
hashmap.put("product7",70);
hashmap.put("product8",30);
Map<String, Integer> map = sortByAbsoluteValue(hashmap);
Where map.toString() would output
{product5=100, product7=70, product2=-40, product8=30, product6=-20, product1=15, product3=10, product4=5, product9=0}
While I agree with some of the comments hinting that a list is better suited when looking for sorted results, Java does allow some maps like the TreeMap to be sorted using Comparator<> functions.
Unfortunately, Maps are always sorted by key, but luckily, Java also provides LinkedHashMap which maintain their insertion order.
So the trick here is to first sort using the Comparator in a TreeMap using the value as a key, and then insert the sorted result to a LinkedHashMap:
private LinkedHashMap<String, Integer> sortByAbsoluteValue(Map<String, Integer> unsorted)
{
// assemble the comparator function
Comparator<Integer> sortedByAbsValue = Comparator.comparingInt(Math::abs);
Comparator<Integer> sortedByAbsValueReversed = sortedByAbsValue.reversed();
// group by value, sorting using the custom comparator
TreeMap<Integer, List<Map.Entry<String, Integer>>> sorted = new TreeMap<>(sortedByAbsValueReversed);
sorted.putAll(unsorted.entrySet().stream().collect(Collectors.groupingBy(Map.Entry::getValue)));
// assemble back into a map that maintains insertion order
return sorted.entrySet()
.stream()
.flatMap(kv -> kv.getValue().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, LinkedHashMap::new));
}
I would like to flatten a Map which associates an Integer key to a list of String, without losing the key mapping.
I am curious as though it is possible and useful to do so with stream and lambda.
We start with something like this:
Map<Integer, List<String>> mapFrom = new HashMap<>();
Let's assume that mapFrom is populated somewhere, and looks like:
1: a,b,c
2: d,e,f
etc.
Let's also assume that the values in the lists are unique.
Now, I want to "unfold" it to get a second map like:
a: 1
b: 1
c: 1
d: 2
e: 2
f: 2
etc.
I could do it like this (or very similarly, using foreach):
Map<String, Integer> mapTo = new HashMap<>();
for (Map.Entry<Integer, List<String>> entry: mapFrom.entrySet()) {
for (String s: entry.getValue()) {
mapTo.put(s, entry.getKey());
}
}
Now let's assume that I want to use lambda instead of nested for loops. I would probably do something like this:
Map<String, Integer> mapTo = mapFrom.entrySet().stream().map(e -> {
e.getValue().stream().?
// Here I can iterate on each List,
// but my best try would only give me a flat map for each key,
// that I wouldn't know how to flatten.
}).collect(Collectors.toMap(/*A String value*/,/*An Integer key*/))
I also gave a try to flatMap, but I don't think that it is the right way to go, because although it helps me get rid of the dimensionality issue, I lose the key in the process.
In a nutshell, my two questions are :
Is it possible to use streams and lambda to achieve this?
Is is useful (performance, readability) to do so?
You need to use flatMap to flatten the values into a new stream, but since you still need the original keys for collecting into a Map, you have to map to a temporary object holding key and value, e.g.
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
.flatMap(e->e.getValue().stream()
.map(v->new AbstractMap.SimpleImmutableEntry<>(e.getKey(), v)))
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
The Map.Entry is a stand-in for the nonexistent tuple type, any other type capable of holding two objects of different type is sufficient.
An alternative not requiring these temporary objects, is a custom collector:
Map<String, Integer> mapTo = mapFrom.entrySet().stream().collect(
HashMap::new, (m,e)->e.getValue().forEach(v->m.put(v, e.getKey())), Map::putAll);
This differs from toMap in overwriting duplicate keys silently, whereas toMap without a merger function will throw an exception, if there is a duplicate key. Basically, this custom collector is a parallel capable variant of
Map<String, Integer> mapTo = new HashMap<>();
mapFrom.forEach((k, l) -> l.forEach(v -> mapTo.put(v, k)));
But note that this task wouldn’t benefit from parallel processing, even with a very large input map. Only if there were additional computational intense task within the stream pipeline that could benefit from SMP, there was a chance of getting a benefit from parallel streams. So perhaps, the concise, sequential Collection API solution is preferable.
You should use flatMap as follows:
entrySet.stream()
.flatMap(e -> e.getValue().stream()
.map(s -> new SimpleImmutableEntry(e.getKey(), s)));
SimpleImmutableEntry is a nested class in AbstractMap.
Hope this would do it in simplest way. :))
mapFrom.forEach((key, values) -> values.forEach(value -> mapTo.put(value, key)));
This should work. Please notice that you lost some keys from List.
Map<Integer, List<String>> mapFrom = new HashMap<>();
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
.flatMap(integerListEntry -> integerListEntry.getValue()
.stream()
.map(listItem -> new AbstractMap.SimpleEntry<>(listItem, integerListEntry.getKey())))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
Same as the previous answers with Java 9:
Map<String, Integer> mapTo = mapFrom.entrySet()
.stream()
.flatMap(entry -> entry.getValue()
.stream()
.map(s -> Map.entry(s, entry.getKey())))
.collect(toMap(Entry::getKey, Entry::getValue));
I need to get the previous element of a LinkedHashMap.
I tried using the ListIterator because it has the previous() method. But the problem is ListIterator needs a List not a set.
ListIterator<Entry<String, Integer>> it = (ListIterator<Entry<String, Integer>>) unitsItems.entrySet().iterator();
I have to transform my entrySet into a list. So I tried this :
List entryList= new ArrayList (unitsItems.entrySet());
ListIterator<Entry<String, Integer>> it = (ListIterator<Entry<String, Integer>>) entryList.iterator();
I got this error:
java.util.ArrayList$Itr cannot be cast to java.util.ListIterator
Can anyone tell me the correct way to transform the set to a list and then use it in ListIterator?
Thank you.
As per request: Since you already have the list entryList you just need to call listIterator() on it to get what you want.
Btw, I'd add the generic type to the list as well: List<Entry<String, Integer>> = new ArrayList<>(unitsItems.entrySet());
If you're using Java 8, you can do this:
#Test
public void obtainListIterator() {
LinkedHashMap<String, String> test = new LinkedHashMap<>();
test.put("1", "a");
test.put("2", "a");
test.put("3", "a");
ListIterator<Map.Entry<String, String>> listIterator =
test
.entrySet()
.stream()
.collect(Collectors.toList())
.listIterator();
assertThat(listIterator.next().getKey()).isEqualTo("1");
assertThat(listIterator.previous().getKey()).isEqualTo("1");
}
However, the answer given by #Thomas is better than mine:
ListIterator<Map.Entry<String, String>> listIterator = new LinkedList(test.entrySet()).listIterator();
Using entrySet() is wrong since it's a method of HashMap and thus it does not contain any notion of list (thus your problems).
As you can see from the sources the linked list is implemented directly at the LinkedHashMap level.
That means that to do what you need (since the API do not supply a way) you'll need to subclass the LinkedHashMap and do it yourself.
How to convert a Map<String, Double> to List<Pair<String, Double>> in Java 8?
I wrote this implementation, but it is not efficient
Map<String, Double> implicitDataSum = new ConcurrentHashMap<>();
//....
List<Pair<String, Double>> mostRelevantTitles = new ArrayList<>();
implicitDataSum.entrySet()
.stream()
.sorted(Comparator.comparing(e -> -e.getValue()))
.forEachOrdered(e -> mostRelevantTitles.add(new Pair<>(e.getKey(), e.getValue())));
return mostRelevantTitles;
I know that it should works using .collect(Collectors.someMethod()). But I don't understand how to do that.
Well, you want to collect Pair elements into a List. That means that you need to map your Stream<Map.Entry<String, Double>> into a Stream<Pair<String, Double>>.
This is done with the map operation:
Returns a stream consisting of the results of applying the given function to the elements of this stream.
In this case, the function will be a function converting a Map.Entry<String, Double> into a Pair<String, Double>.
Finally, you want to collect that into a List, so we can use the built-in toList() collector.
List<Pair<String, Double>> mostRelevantTitles =
implicitDataSum.entrySet()
.stream()
.sorted(Comparator.comparing(e -> -e.getValue()))
.map(e -> new Pair<>(e.getKey(), e.getValue()))
.collect(Collectors.toList());
Note that you could replace the comparator Comparator.comparing(e -> -e.getValue()) by Map.Entry.comparingByValue(Comparator.reverseOrder()).
Note that if you want efficient implementation, you should consider this:
List<Pair<String, Double>> mostRelevantTitles =
implicitDataSum.entrySet()
.stream()
.map(e -> new Pair<>(e.getKey(), e.getValue()))
.collect(Collectors.toList());
mostRelevantTitles.sort(Comparators.comparing(Pair::getSecond, Comparator.reverseOrder()));
I assume that your Pair class have getSecond getter.
Using the sorted() stream pipeline step you create intermediate buffer, store everything to that buffer, convert it into array, sort that array, then store the result into the ArrayList. My approach, though less functional, stores data directly into the target ArrayList, then sorts it in-place without any additional copying. So my solution would take less time and intermediate memory.
public List<TeamResult> process(final Map<String, Team> aggregatedMap) {
return aggregatedMap.entrySet()
.stream()
.map(e -> new TeamResult(e.getKey(),e.getValue()))
.collect(Collectors.toList());
}
Sort the Map based on values in reverse order and collect the keys in list and also limit only first 2 results in the list
List<String> list = map.keySet().stream()
.sorted((k1, k2)->map.get(k2)- map.get(k1))
.limit(2)
.collect(Collectors.toList())
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).