Home > Back-end >  How do sort Entries of the Map<Type,List<String>> by Value and store into a LinkedHashMa
How do sort Entries of the Map<Type,List<String>> by Value and store into a LinkedHashMa

Time:09-20

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 Strings) 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 (Not List<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'}]}

    }
  • Related