I am new Java 8 and want to sort a Map based on Key and then sort each list within values.
I tried to look for a Java 8 way to sort Keys and also value.
HashMap> map
map.entrySet().stream().sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue, (e1, e2) -> e2, LinkedHashMap::new));
I am able to sort the Map and I can collect each values within map to sort but is their a way we can do in java 8 and can both be combined.
To sort by key, you could use a TreeMap. To sort each list in the values, you could iterate over the values of the map by using the Map.values().forEach() methods and then sort each list by using List.sort. Putting it all together:
Map<Integer, List<Integer>> sortedByKey = new TreeMap<>(yourMap);
sortedByKey.values().forEach(list -> list.sort(null)); // null: natural order
This sorts each list in-place, meaning that the original lists are mutated.
If instead you want to create not only a new map, but also new lists for each value, you could do it as follows:
Map<Integer, List<Integer>> sortedByKey = new TreeMap<>(yourMap);
sortedByKey.replaceAll((k, originalList) -> {
List<Integer> newList = new ArrayList<>(originalList);
newList.sort(null); // null: natural order
return newList;
});
EDIT:
As suggested in the comments, you might want to change:
sortedByKey.values().forEach(list -> list.sort(null));
By either:
sortedByKey.values().forEach(Collections::sort);
Or:
sortedByKey.values().forEach(list -> list.sort(Comparator.naturalOrder()));
Either one of the two options above is much more expressive and shows the developer's intention in a better way than using null as the comparator argument to the List.sort method.
Same considerations apply for the approach in which the lists are not modified in-place.
Related
I'm trying to convert a list of objects into a list of lists of objects, i.e. List<T> into List<List<T>> and group them by a list of strings used as sorting keys.
The problem is that after the groupingBy(), the return value is a map, and that map a different order of values.
What can I do to preserve the initial order of elements in the list groupedTreesList?
My code:
Map<List<String>,List<QueryTreeProxy>> groupedMap = treesProxyList.stream()
.collect(Collectors.groupingBy(
QueryTreeProxy::getMappingKeys,
Collectors.toList()
));
List<List<QueryTreeProxy>> groupedTreesList = groupedMap.values().stream().toList();
groupingBy(), the return value is a map and that map output a different order of values list
You can use another flavor of groupingBy(classifier, mapFactory, downstream), which allows you to specify the type of the Map. And we can use LinkedHashMap to preserve the initial order.
And there's no need to generate the second stream, you can use parameterized contractor of ArrayList instead.
Map<List<String>, List<QueryTreeProxy>> groupedMap = treesProxyList.stream()
.collect(Collectors.groupingBy(
QueryTreeProxy::getMappingKeys,
LinkedHashMap::new,
Collectors.toList()
));
List<List<QueryTreeProxy>> groupedTreesList = new ArrayList<>(groupedMap.values());
Besides that, using a mutable object as a key isn't a good practice.
I've got a list of type List<A> and with map operation getting a collective list of type List<B> for all A elements merged in one list.
List<A> listofA = [A1, A2, A3, A4, A5, ...]
List<B> listofB = listofA.stream()
.map(a -> repo.getListofB(a))
.flatMap(Collection::stream)
.collect(Collectors.toList());
without flatmap
List<List<B>> listOflistofB = listofA.stream()
.map(a -> repo.getListofB(a))
.collect(Collectors.toList());
I want to collect the results as a map of type Map<A, List<B>> and so far trying with various Collectors.toMap or Collectors.groupingBy options but not able to get the desired result.
You can use the toMap collector with a bounded method reference to get what you need. Also notice that this solution assumes you don't have repeated A instances in your source container. If that precondition holds this solution would give you the desired result. Here's how it looks.
Map<A, Collection<B>> resultMap = listofA.stream()
.collect(Collectors.toMap(Function.identity(), repo::getListofB);
If you have duplicate A elements, then you have to use this merge function in addition to what is given above. The merge function deals with key conflicts if any.
Map<A, Collection<B>> resultMap = listofA.stream()
.collect(Collectors.toMap(Function.identity(), repo::getListofB,
(a, b) -> {
a.addAll(b);
return a;
}));
And here's a much more succinct Java9 approach which uses the flatMapping collector to handle repeated A elements.
Map<A, List<B>> aToBmap = listofA.stream()
.collect(Collectors.groupingBy(Function.identity(),
Collectors.flatMapping(a -> getListofB(a).stream(),
Collectors.toList())));
It would be straight forward,
listofA.stream().collect(toMap(Function.identity(), a -> getListofB(a)));
In this answer, I'm showing what happens if you have repeated A elements in your List<A> listofA list.
Actually, if there were duplicates in listofA, the following code would throw an IllegalStateException:
Map<A, Collection<B>> resultMap = listofA.stream()
.collect(Collectors.toMap(
Function.identity(),
repo::getListofB);
The exception might be thrown because Collectors.toMap doesn't know how to merge values when there is a collision in the keys (i.e. when the key mapper function returns duplicates, as it would be the case for Function.identity() if there were repeated elements in the listofA list).
This is clearly stated in the docs:
If the mapped keys contains duplicates (according to Object.equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys may have duplicates, use toMap(Function, Function, BinaryOperator) instead.
The docs also give us the solution: in case there are repeated elements, we need to provide a way to merge values. Here's one such way:
Map<A, Collection<B>> resultMap = listofA.stream()
.collect(Collectors.toMap(
Function.identity(),
a -> new ArrayList<>(repo.getListofB(a)),
(left, right) -> {
left.addAll(right);
return left;
});
This uses the overloaded version of Collectors.toMap that accepts a merge function as its third argument. Within the merge function, Collection.addAll is being used to add the B elements of each repeated A element into a unqiue list for each A.
In the value mapper function, a new ArrayList is created, so that the original List<B> of each A is not mutated. Also, as we're creating an Arraylist, we know in advance that it can be mutated (i.e. we can add elements to it later, in case there are duplicates in listofA).
To collect a Map where keys are the A objects unchanged, and the values are the list of corresponding B objects, you can replace the toList() collector by the following collector :
toMap(Function.identity(), a -> repo.getListOfB(a))
The first argument defines how to compute the key from the original object : identity() takes the original object of the stream unchanged.
The second argument defines how the value is computed, so here it just consists of a call to your method that transforms a A to a list of B.
Since the repo method takes only one parameter, you can also improve clarity by replacing the lambda with a method reference :
toMap(Function.identity(), repo::getListOfB)
I have a Map and want to sort the values by a Comparator putting the result into a LinkedHashMap.
Map<String, User> sorted = test.getUsers()
.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(SORT_BY_NAME))
.collect(LinkedHashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()),
LinkedHashMap::putAll);
test.setUsers(sorted);
All works, however, I wonder if this can be simplified.
Actually I create a new Map and put that new Map into the setUsers(). Can I change the stream directly without creating a new LinkedHashMap?
With Collections.sort(Comparator), the list is directly sorted. However, Collections.sort does not work with Maps.
Why not use Collectors.toMap()?
.collect(Collectors.toMap(
Entry::getKey,
Entry::getValue,
(a, b) -> { throw new AssertionError("impossible"); },
LinkedHashMap::new));
You cannot perform in-place sorting on a collection that does not support array-style indexing, so for maps it is out of the question (except you use something like selection sort on a LinkedHashMap, but that would be a bad idea). Creating a new map is unavoidable. You can still simplify it though, by following shmosel's answer.
Consider the following Java HashMap.
Map<String, String> unsortMap = new HashMap<String, String>();
unsortMap.put("Z", "z");
unsortMap.put("B", "b");
unsortMap.put("A", "a");
unsortMap.put("C", "c");
Now I wish to sort this Map by Key. One option is for me to use a TreeMap for this purpose.
Map<String, String> treeMap = new TreeMap<String, String>(unsortMap);
Another option is for me to Use Java Streams with Sorted(), as follows.
Map<String, Integer> sortedMap = new HashMap<>();
unsortMap.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue()));
Out of these two, which option is preferred and why (may be in terms of performance)?
Thank you
As pointed out by others dumping the sorted stream of entries into a regular HashMap would do nothing... LinkedHashMap is the logical choice.
However, an alternative to the approaches above is to make full use of the Stream Collectors API.
Collectors has a toMap method that allows you to provide an alternative implementation for the Map. So instead of a HashMap you can ask for a LinkedHashMap like so:
unsortedMap.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> v1, // you will never merge though ask keys are unique.
LinkedHashMap::new
));
Between using a TreeMap vs LinkedHashMap ... The complexity of construction is likely to be the same something like O(n log n)... Obviously the TreeMap solution is a better approach if you plan to keep adding more elements to it... I guess you should had started with a TreeMap in that case. The LinkedHashMap option has the advantage that lookup is going to be O(1) on the Linked or the original unsorted map whereas as TreeMap's is something like O(log n) so if you would need to keep the unsorted map around for efficient lookup whereas in if you build the LinkedHashMap you could toss the original unsorted map (thus saving some memory).
To make things a bit more efficient with LinkedHashMap you should provide an good estimator of the required size at construction so that there is not need for dynamic resizing, so instead of LinkedHashMap::new you say () -> new LinkedHashMap<>(unsortedMap.size()).
I'm my opinion the use of a TreeMap is more neat... as keeps the code smaller so unless there is actual performance issue that could be addressed using the unsorted and sorted linked map approach I would use the Tree.
Your stream code won't even sort the map, because it is performing the operation against a HashMap, which is inherently unsorted. To make your second stream example work, you may use LinkedHashMap, which maintains insertion order:
Map<String, Integer> sortedMap = new LinkedHashMap<>();
unsortMap.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue()));
But now your two examples are not even the same underlying data structure. A TreeMap is backed by a tree (red black if I recall correctly). You would use a TreeMap if you wanted to be able to iterate in a sorted way, or search quickly for a key. A LinkedHashMap is hashmap with a linked list running through it. You would use this if you needed to maintain insertion order, for example when implementing a queue.
The second way does not work, when you call HashMap#put, it does not hold the put order. You might need LinkedHashMap.
TreeMap v.s. Stream(LinkedHashMap):
code style. Using TreeMap is more cleaner since you can achieve it in one line.
space complexity. If the original map is HashMap, with both method you need to create a new Map. If If the original map is LinkedHashMap, then you only need create a new Map with the first approach. You can re-use the LinkedHashMap with the second approach.
time complexity. They should both have O(nln(n)).
I have a HashMap<String, Float> that gets filled with several entries.
I want to save the keys of the HashMap into an ArrayList<String>, but sort this ArrayList according to the corresponding values of the HashMap.
Is there a better solution than sorting the HashMap, then using
ArrayList<String> sortedKeys = new ArrayList<String>(myHashmap.keySet());
By the way I'm not bound to using a HashMap in the first place, but the value sets the order and I don't want swap key and value since the float may change several times.
You can use the stream API:
List<String> sortedKeys = myMap.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.collect(Collectors.toCollection(ArrayList::new));
If I understand correctly, you can create the array list from the keys first, and then sort:
ArrayList<String> list = new ArrayList<>(myHashMap.keySet());
list.sort(Comparator.comparing(myHashMap::get));