Collections and Naturally ordering a Map<Long, Map<String, String>> - java

I'm having trouble with the more intricate map; for a standard Map<Long, String>, I would do something like:
Ordering<Long> valueComparator = Ordering.natural().onResultOf(Functions.forMap(myMap));
Map<Long, String> orderedMap = ImmutableSortedMap.copyOf(myMap, valueComparator);
But I can't seem to get it to like Map<Long, Map<String, String>>, still ordered by Long. Maybe I'm missing something? Below doesn't work...
Ordering<Long> valueComparator = Ordering.natural().onResultOf(Functions.forMap(myOtherMap));
Map<Long, Map<String, String>> orderedMyOtherMap = ImmutableSortedMap.copyOf(myOtherMap,valueComparator);

Your first example isn't doing what you seem to be saying it's doing. It's creating a map that's ordered by the String values corresponding to each Long key. If you wanted to just order by the keys, you'd just do:
ImmutableSortedMap<Long, String> orderedMap = ImmutableSortedMap.copyOf(myMap);
The same thing would work for a Map<Long, Map<String, String>>. The reason what you're trying to do doesn't work is that a Map<String, String> is not Comparable, so there is no natural ordering for it.
As an aside, you may want to consider using a Table<Long, String, String> rather than a Map<Long, Map<String, String>>. There's even a TreeBasedTable that will store the row and column keys in sorted order.

Related

Sort every Map in List by Set of keys

I have List of LinkedHashMap like List<Map<String, String>>. Every Map has the same number of elements and every Map has the same keys.
The second element is LinkedHashSet<String> - set of keys.
Now I would like to order every Map from List by keys. Sort ordering is in LinkedHashSet<String>.
My attempt is iterate by List<Map<String, String>>. For every Map create new Map and iterate by Set. To the new Map put key and value from old Mapwhere key is taken from Set. In code:
private List<Map<String, String>> sort(List<Map<String,String> result, LinkedHashSet<String> keys){
List<Map<String, String>> sortedResult = new LinkedList<>();
result.forEach(map -> {
Map<String, String> sortedMap = new LinkedHashMap<>();
keys.forEach(key -> {
sortedMap.put(key, map.get(key));
});
sortedResult.add(sortedMap);
});
return sortedResult;
}
I think it is a little bit complicated and in my opinion there exsists better way to do that.
You have a LinkedHashMap which tries to maintain only the order of insertion of keys, not the natural-ordering of keys. One thing you can do is to maintain a list of keys outside the map and sort them and re-insert the <key,value> pairs as per the order of the sorted list of keys. So it seems you are already doing this in your code by having an order defined by LinkedHashSet.
The other simple approach is:
If you want an ordered map by keys, you most probably need a TreeMap, insertion into this map maintains the natural ordering of keys and you can construct a treemap from an existing map.
private List<Map<String, String>> sort(List<Map<String,String> result) {
List<Map<String, String>> sortedResult = new LinkedList<>();
for ( Map<String, String> m : result )
sortedResult.add(new TreeMap(m)));
return sortedResult;
}
BTW, Local variables referenced from a lambda expression must be final
There are a couple of things I would change:
The argument name "result" is misleading. People going over the code quickly will think this is the returned result. I would change it to "unsortedMaps" or something similar
Each Map shouldn't affect the other so instead of result.forEach you could use result.parallelStream().forEach to make every map sorted in it's own thread. You will need to make insertion to list itself thread safe (either surronding "sortedResult.add(sortedMap" with synchronized statment or use a thread-safe list implementation. All this doesn't guarantee improvement in performance. It depends on many variants such as the size of the collections and number of cores. Test it to find out.
There are a lot of details in this function. I would extract the part dealing with each map to a seperate function
Here is the result (didn't test the code so can't gurentee correctness. Needless to say unit-tests are always the way to go):
private List<Map<String, String>> sort(List<Map<String,String>> unsortedMaps, LinkedHashSet<String> keys){
List<Map<String, String>> sortedResult = new LinkedList<>();
unsortedMaps.parallelStream().forEach(map -> {
Map<String, String> sortedMap = getSortedMap(keys, map);
synchronized (sortedResult) {
sortedResult.add(sortedMap);
}
});
return sortedResult;
}
private Map<String, String> getSortedMap(LinkedHashSet<String> keys, Map<String, String> map) {
Map<String, String> sortedMap = new LinkedHashMap<>();
keys.forEach(key -> {
sortedMap.put(key, map.get(key));
});
return sortedMap;
}
To complete SomeDude answer, if the natural order isn't enough for your need, you can try to specify a Comparator to the TreeMap :
private List<Map<String, String>> sort(List<Map<String,String>> mapList, Set<String> keys){
List<String> keysList = new ArrayList<>(keys);
return mapList.stream().map(map -> copyAndReOrderMap(map, keysList)).collect(Collectors.toList());
}
private Map<String, String> copyAndReOrderMap(Map<String, String> map, List<String> keysList) {
Map<String, String> orderedMap = new TreeMap<>((key1, key2) -> Integer.compare(keysList.indexOf(key1), keysList.indexOf(key2)));
orderedMap.putAll(map);
return orderedMap;
}
NB: Unless you deal with very large maps, i don't see why you would want to sort each map in a separate Thread.

How can I use guava's MultiMap to split list of values

Map is of type Map<String, List<MyClass>>
I have more than 100 objects of MyClass associated with Key.
I need to split it if it's more than 100.
For e.g.
Input is
Map<String, List<MyClass>> myMap = new HashMap<String, List<MyClass>>();
myMap.put("ABC", [CustomObject1,CustomObject2,CustomObject3....CustomObject100...CustomObject110]);
& Output should be
myMap.put("ABC", [CustomObject1,CustomObject2,CustomObject3....CustomObject100]);
myMap.put("ABC", [CustomObject100,CustomObject101....CustomObject110]);
I thought of getting myMap.containsKey(string) and check size of list and then create new entry or add it in same.
I tried using guava's multimap but it returns Collection> when I try to get elements so not sure how to insert it. Or if there is any better option for this?
Try using Guava Lists.partition
Map<String, List<MyClass>> myMap = new HashMap<String, List<MyClass>>();
myMap.put("ABC", [CustomObject1,CustomObject2,CustomObject3....CustomObject100...CustomObject110]);
Map<String, List<List<MyClass>>> output = myMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Lists.partition(e.getValue(), 100)));

How to convert Guava HashMultmap to java.util.Map

I am trying to convert Guava Multimap<String ,Collection<String>> into Map<String, Collection<String>> but I get a syntax error when using Multimaps.asMap(multimap). Here is a code:
HashMultimap<String, Collection<String>> multimap = HashMultimap.create();
for (UserDTO dto : employees) {
if (dto.getDepartmentNames() != null) {
multimap.put(dto.getUserName().toString().trim(), dto.getDepartmentNames());
}
}
Map<String, Collection<String>> mapOfSets = Multimaps.asMap(multimap);
Here is a screenshot of error:
Can someone point out where I am doing a mistake?
Return type of Multimaps.asMap(multimap) is Map<String, <Set<Collection<String>>.
Multimap can hold multiple values of the same key. Hence, when you want to convert from multimap to a map, you need to keep collection of values for each key, just in case, there is a key which appears twice in the map.
If you want to convert from MultiMap to Map and make set sum on the values, you can do the following:
Multimaps.asMap(multimap).entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e->e.getValue().stream()
.flatMap(Collection::stream).collect(toSet())));
I think what you're doing here is using Multimap wrong. Multimap<String, Collection<String>> is roughly an equivalent to Map<String, Collection<Collection<String>>>, so it results in nested collections when using asMap view (ex. {user1=[[IT, HR]], user2=[[HR]], user3=[[finance]]}).
What you really want is to use Multimap<String, String> (more specifically: SetMultimap<String, String> which corresponds to Map<String, Set<String>>) and use Multimap#putAll(K, Iterable<V>):
SetMultimap<String, String> multimap = HashMultimap.create();
for (UserDTO dto : employees) {
if (dto.getDepartmentNames() != null) {
// note `.putAll` here
multimap.putAll(dto.getUserName().toString().trim(), dto.getDepartmentNames());
}
}
Map<String, Set<String>> mapOfSets = Multimaps.asMap(multimap);
// ex. {user1=[HR, IT], user2=[HR], user3=[finance]}
Using Multimaps#asMap(SetMultimap) instead of SetMultimap#asMap() is necessary due to Java type system limitation (can't override generic type in a subtype when its nested in a generic type):
Note: The returned map's values are guaranteed to be of type Set. To
obtain this map with the more specific generic type Map<K, Set<V>>,
call Multimaps.asMap(SetMultimap) instead.

Create a map from a list of maps

I have a list of maps.
List<Map<Integer, String>>
The values in the list are, for example
<1, String1>
<2, String2>
<1, String3>
<2, String4>
As an end result, I want a Map>, like
<1, <String1, String3>>
<2, <String2, String4>>
How can I achieve this in Java.
CODE :
List<Map<Integer, String>> genericList = new ArrayList<Map<Integer,String>>();
for(TrackActivity activity : activityMajor){
Map<Integer, String> mapIdResponse = activity.getMapIdResponse();
genericList.add(mapIdResponse);
}
Now this genericList is the input and from this list, based on the same ids I want a
Map<Integer, List<String>> mapIdResponseList
Basically, to club the responses which are String based on the ids, grouping the responses with same id in a list and then creating a new map with that id as the key and the list as its value.
You can do it the following with Java 8:
private void init() {
List<Map<Integer, String>> mapList = new ArrayList<>();
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "String1");
mapList.add(map1);
Map<Integer, String> map2 = new HashMap<>();
map2.put(2, "String2");
mapList.add(map2);
Map<Integer, String> map3 = new HashMap<>();
map3.put(1, "String3");
mapList.add(map3);
Map<Integer, String> map4 = new HashMap<>();
map4.put(2, "String4");
mapList.add(map4);
Map<Integer, List<String>> response = mapList.stream()
.flatMap(map -> map.entrySet().stream())
.collect(
Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(
Map.Entry::getValue,
Collectors.toList()
)
)
);
response.forEach((i, l) -> {
System.out.println("Integer: " + i + " / List: " + l);
});
}
This will print:
Integer: 1 / List: [String1, String3]
Integer: 2 / List: [String2, String4]
Explanation (heavily warranted), I am afraid I cannot explain every single detail, you need to understand the basics of the Stream and Collectors API introduced in Java 8 first:
Obtain a Stream<Map<Integer, String>> from the mapList.
Apply the flatMap operator, which roughly maps a stream into an already existing stream.
Here: I convert all Map<Integer, String> to Stream<Map.Entry<Integer, String>> and add them to the existing stream, thus now it is also of type Stream<Map.Entry<Integer, String>>.
I intend to collect the Stream<Map.Entry<Integer, String>> into a Map<Integer, List<String>>.
For this I will use a Collectors.groupingBy, which produces a Map<K, List<V>> based on a grouping function, a Function that maps the Map.Entry<Integer, String> to an Integer in this case.
For this I use a method reference, which exactly does what I want, namely Map.Entry::getKey, it operates on a Map.Entry and returns an Integer.
At this point I would have had a Map<Integer, List<Map.Entry<Integer, String>>> if I had not done any extra processing.
To ensure that I get the correct signature, I must add a downstream to the Collectors.groupingBy, which has to provide a collector.
For this downstream I use a collector that maps my Map.Entry entries to their String values via the reference Map.Entry::getValue.
I also need to specify how they are being collected, which is just a Collectors.toList() here, as I want to add them to a list.
And this is how we get a Map<Integer, List,String>>.
Have a look at guavas MultiMap. Should be exactly what you are looking for:
http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap

Sorting Guava table on values

Is there any possible way to do that? The expecting effect would be that rowMap and columnMap entry values would be sorted by value.
The problem is that I cannot create a comparator without the underlying maps in Table.
Table table = TreeBasedTable.create(?,?);
Map<String, Map<String, String>> rowMap = table.rowMap();
Map<String, String> thisMapShouldBeSortedByValues = rowMap.get(smth);
Map<String, Map<String, String>> columnMap = table.columnMap();
Map<String, String> thisMapShouldBeSortedByValues = columnMap.get(smth);
Now I always have to sort rowMap and columnMap afterwards and allocate new TreeMaps on that.
First of all, I am assuming that by thisMapShouldBeSortedByValues, you mean thisMapShouldBeSortedByKeys
TreeBasedTable extends RowSortedTable, so the table is only sorted by it's rows, not its columns. Calling table.columnMap() gives you an unordered Map<C, Map<R, V>.
Since it is sorted by rows, the table.rowMap returns a SortedMap<R, Map<C, V>>. So the map is sorted on it's row keys, but the Map<C, V> value is an unsorted map, hence why calls to rowMap.get() returns an unordered map.
The SortedMap you are trying to get from calling table.rowMap(), and then rowMap.get(), can be obtained by instead calling table.rowKeySet() and table.row(R rowKey) like so (You build the map you originally wanted to get by calling table.rowMap():
ImmutableSortedMap.Builder<String, SortedMap<String, String>> builder = ImmutableSortedMap.builder();
for (String rowKey : table.rowKeySet()){
builder.put(rowKey, table.row(rowKey)) ;
}
SortedMap<String, <SortedMap<String, String>> thisMapIsSortedByKeys = builder.build();

Categories