I have the following Item
class:
@AllArgsConstructor
@Getter
public static class Item {
public enum Type { Meat, Fish }
private String name;
private int price;
private Type type;
}
And there's a List
of items like shown below:
List<Item> menu = Arrays.asList(
new Item("pork" 800, Item.Type.Meat),
new Item("beef" 700, Item.Type.Meat),
new Item("chicken", 200, Item.Type.meat),
new Item("prawns" 300, Item.Type.Fish),
new Item("salmon", 450, Item.Type.Fish)
);
I need to group Item
by Type
and store into a map.
And then I need to sort map entries by Value (which in this case is a List
of String
s) in ascending order and store into a LinkedHashMap
.
How do I sort a map entries based on a List<String>
value?
My code:
// grouping items by type
Map<Item.Type, List<String>> map = menu.stream()
.collect(Collectors.groupingBy(
Item:: getType,
Collectors.mapping(Item::getName, Collectors.toList())
));
// a LinkedHashMap to store sorted entries
Map<Item.Type, List<String>> hmap = new LinkedHashMap<>();
// attempting to sort map entries by value and put them
// into the resulting map
map.entrySet().stream()
.sorted(Map.Entry.<Item.Type, List<String>> comparingByValue())
.forEachOrdered(e -> hmap.put(e.getKey(), e.getValue()));
But I'm getting a compilation error:
Type parameter 'java.util.List' is not within its bound;
should extend 'java.lang.Comparable<? super java.util.List>'
I think the reason is because I have a list of String in a map which I want to sort.
When I have just a String
as a value (Not List<String>
). The code works perfectly well.
CodePudding user response:
None of the Collections implement Comparable
interface. There's no point in it, because they are containers of data, if you need to sort the data you're sorting it within the collection that is capable of maintaining the order. When you need to sort collections - that's a weird task.
I said weird because collections like List
have no knowledge of how to compare their elements, meaning it's not mandatory for the elements to be comparable, and you can't provide a comparator while instantiating a list. As a consequence of this, it's not possible to establish the natural order for a List
, in other words a list doesn't "know" how it can be compared with another list.
You need to define a custom Comparator
for that purpose.
When I have just a
String
as a value (NotList<String>
). The code works perfectly well.
Assuming that you want your lists of string to be compared as if each of them is single is a single string (let's call it comparison key), then it would be wise to generate such strings in advance and associate every Type
of item with a comparison key (if you have in mind different comparison strategy - then tweak the code accordingly).
Comparison keys can be generated right on the spot (in the stream), but it would lead to recreating the same strings multiple times. In this case it's not a big issue since Item.Type
is an enum therefore the number of string comparison keys that correspond to each enum-constant is limited, but it wouldn't hurt to have them ready to use in advance.
List<Item> menu = Arrays.asList(
new Item("pork", 800, Item.Type.Meat),
new Item("beef", 700, Item.Type.Meat),
new Item("chicken", 200, Item.Type.Meat),
new Item("prawns", 300, Item.Type.Fish),
new Item("salmon", 450, Item.Type.Fish)
);
Map<Item.Type, List<String>> itemNamesByType = menu.stream()
.collect(Collectors.groupingBy(
Item:: getType,
Collectors.mapping(Item::getName, Collectors.toList())
));
// generating Comparison Keys for every Type
Map<Item.Type, String> comparisonKeyByType = itemNamesByType.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> String.join("", entry.getValue())
));
Map<Item.Type, List<String>> itemNamesByTypeSorted = itemNamesByType.entrySet().stream()
.sorted(Map.Entry.comparingByKey(
Comparator.comparing(comparisonKeyByType::get)
))
.collect(Collectors.toMap(
Map.Entry::getKey, // keyMapper
Map.Entry::getValue, // valueMapper
(left, right) -> { throw new AssertionError("duplicates not expected"); }, // since the source is an entry set - there shouldn't be duplicate keys (but we need to provide this argument in order to be able to use version of toMap() which allows to provide a mapFactory
LinkedHashMap::new // mapFactory
));
itemNamesByTypeSorted.forEach((k, v) -> System.out.println(k " -> " v));
Output:
Meat -> [pork, beef, chicken]
Fish -> [prawns, salmon]
CodePudding user response:
Just implement available method of list
- Compare Strings
public static void main(String[] args) {
Map<String, List<String>> hmap = new LinkedHashMap<>();
List<String> values = new ArrayList<>();
values.add("d");
values.add("a");
values.add("g");
hmap.put("1", values);
hmap.put("2", values);
System.out.println(hmap); // --> {1=[d, a, g], 2=[d, a, g]}
for(Map.Entry<String,List<String>> entry: hmap.entrySet()){
entry.getValue().sort(Comparator.naturalOrder());
}
System.out.println(hmap); // --> {1=[a, d, g], 2=[a, d, g]}
}
- Compare by attribute list object
public static void main(String[] args) {
final List menu = Arrays.asList(
new Dish("pork", 800, "Meat"),
new Dish("beef", 700, "Meat"),
new Dish("chicken", 200, "Meat"),
new Dish("prawns", 300, "Fish"),
new Dish("salmon", 450, "Fish"));
Map<String, List<Dish>> hmap = new LinkedHashMap<>();
hmap.put("1", menu);
hmap.put("2", menu);
System.out.println(hmap); // --> {1=[Dish{name='pork', price=800, type='Meat'}, Dish{name='beef', price=700, type='Meat'}, Dish{name='chicken', price=200, type='Meat'}, Dish{name='prawns', price=300, type='Fish'}, Dish{name='salmon', price=450, type='Fish'}], 2=[Dish{name='pork', price=800, type='Meat'}, Dish{name='beef', price=700, type='Meat'}, Dish{name='chicken', price=200, type='Meat'}, Dish{name='prawns', price=300, type='Fish'}, Dish{name='salmon', price=450, type='Fish'}]}
for (Map.Entry<String, List<Dish>> entry : hmap.entrySet()) {
entry.getValue().sort(Comparator.comparing(Dish::getName));
}
System.out.println(hmap); // --> {1=[Dish{name='beef', price=700, type='Meat'}, Dish{name='chicken', price=200, type='Meat'}, Dish{name='pork', price=800, type='Meat'}, Dish{name='prawns', price=300, type='Fish'}, Dish{name='salmon', price=450, type='Fish'}], 2=[Dish{name='beef', price=700, type='Meat'}, Dish{name='chicken', price=200, type='Meat'}, Dish{name='pork', price=800, type='Meat'}, Dish{name='prawns', price=300, type='Fish'}, Dish{name='salmon', price=450, type='Fish'}]}
}