2

I have the following Map:

    Map<String, List<String>> map = new HashMap<String, List<String>>();

which is filled with pairs of keys and values.
For example: key = student name and value = family members names.
I want to sort the map by the size of the list of strings. I have tried implementing Comparator with a TreeMap but I got an error so I switched back to HashMap. Any ideas?

Johny
  • 319
  • 2
  • 5
  • 10
  • `TreeMap` with Comparator is the way to go IMO.. What was your error? – calebds Jan 25 '12 at 19:26
  • Just to reinforce the message of other answers, you can't sort a HashMap (or, generally speaking, a Map). You'd have to use specific versions of Map that support sorting. TreeMap seems like the best shot to sort by values. – madth3 Jan 25 '12 at 19:48
  • TreeMap sorts by key, not value. – Andrei LED Jan 25 '12 at 19:51
  • I can't mark it as such, but this is an exact duplicate of http://stackoverflow.com/questions/8896679/map-that-could-be-iterated-in-the-order-of-values – Louis Wasserman Jan 25 '12 at 20:17
  • Yeah, I have seen there are other similar questions but I haven't found one with a List as a value for the Map. And that's a little different. – Johny Jan 25 '12 at 20:30
  • Not very different at all; see my answer below. – Louis Wasserman Jan 25 '12 at 22:04

3 Answers3

3

You should use the HashMap unordered, and then each time you want to order, put all the values of the HashMap into a TreeMap, using a Comparator that has the HashMap as a variable.

Then, for each key you compare, you get the value of the HashMap (the list) and check the list size. So you compare by the list sizes, returning -1, 0 or 1 depending on the case.

Once you finish what you need, you discard that TreeMap.

If you try to use only a TreeMap, then you'll see that you are ordering the keys according to a value that is not a property of such key. In this case, the length of the value (a list). So, there may exist a function that increases the length of the list, and the TreeMap won't even notice.

Some code:

public class ListSizeComparator implements Comparator<String> {

private final Map<String, List<String>> map;

public ListSizeComparator(final Map<String, List<String>> map) {
    this.map = map;
}

@Override
public int compare(String s1, String s2) {
    //Here I assume both keys exist in the map.
    List<String> list1 = this.map.get(s1);
    List<String> list2 = this.map.get(s2);
    Integer length1 = list1.size();
    Integer length2 = list2.size();
    return length1.compareTo(length2);
}

}
Luciano
  • 8,552
  • 5
  • 32
  • 56
  • I'm not sure I understand what you mean. Could you elaborate on how the compare method would look like? – Johny Jan 25 '12 at 20:01
  • Hey, thanks! I tried TreeMap> sorter = new TreeMap(new ListSizeComparator(map)); but it didn't work. When I try to print the map I get nothing. – Johny Jan 25 '12 at 22:26
  • Because you need to fill the treemap after the constructor. sorter.putAll(map); – Luciano Jan 26 '12 at 01:41
  • I just changed the return of the compare method, because if the lengths are the same, one of the two entries won't be inserted into the treemap. Otherwise it works! – Johny Jan 26 '12 at 09:42
  • Warning: the resulting map will not support doing any operations -- get, containsKey, anything else -- with any key that isn't actually in the map. This is super dangerous. – Louis Wasserman Jan 26 '12 at 23:16
3

The solution is more or less identical to https://stackoverflow.com/a/8897384/869736, but all you need to do is write a Comparator that compares lists by their length.

Comparator<List<String>> lengthComparator = new Comparator<List<String>>() {
  public int compare(List<String> a, List<String> b) {
    return a.size() - b.size(); 
    // size() is always nonnegative, so this won't have crazy overflow bugs
  }
};

and then just use the solution outlined there.

Community
  • 1
  • 1
Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • Hey thanks! But that means I have to use Guava libraries, right? – Johny Jan 25 '12 at 22:57
  • Yes, that does follow. Working around it would be much less pleasant, but still doable using LinkedHashMap as your output, Collections.reverseOrder instead of Ordering.reverse(), making an explicit copy and sorting it rather than using Ordering.sortedCopy(), and a bunch of other stuff. – Louis Wasserman Jan 25 '12 at 23:12
  • And it's certainly true that Guava's collections framework is massively useful, and this is only a tiny fraction of its capabilities. You may find the wiki (http://code.google.com/p/guava-libraries/wiki/GuavaExplained) helpful. – Louis Wasserman Jan 25 '12 at 23:22
  • I think the solution of @Luciano with a little change in the compare method worked, so I won't have to use Guava in this case. But I will definitely read that wiki. Thanks anyway! – Johny Jan 26 '12 at 09:47
0

I see three choices here:

  1. Sort the map contents every time you need - if it's not too often that it's OK.
  2. In addition to the map store other auxiliary structure with desired order, for example TreeMap<Integer, List<String>> (key - number of family members, value - list of students).
  3. May be you don't need at all your map as you described it and following map will be sufficient: TreeMap<Integer, Map<String, List<String>>> (key - number of family members, value - part of your original map containing students with number of family members equal $key).
Andrei LED
  • 2,560
  • 17
  • 21
  • I just tried that but it doesn't work because if two students have equal number of family members, only one of them will be inserted. I guess keys are ment to be unique in a Map. – Johny Jan 25 '12 at 20:48
  • Yes, keys are unique. That's why values of the map are lists in the case 2 and maps in the case 3, which allows to store multiple stidents under the same key in the top level map. – Andrei LED Jan 26 '12 at 05:41