1

I have this kind of values inside a Map:

Map<Integer, String> map = new HashMap<Integer, String>();                  
map.put(1,"mark");
map.put(2,"1.1");
map.put(3,"google");
map.put(4,"12");
map.put(5,"2.2);

I need to order this Map, ordering both numbers and strings.

What I get now is this (as you can see numbers are not "ordered" since are String)

1.1
12
2.2
google
mark

What I should get is:

1.1
2.2
12
google
mark

How can I do it? I'm a little confused.

MDP
  • 4,177
  • 21
  • 63
  • 119
  • 1
    What do you mean by ordering a map? (A `HashMap` is not ordered). Can you show your code? – Thiyagu May 28 '19 at 15:13
  • 2
    make a simple mapper returning Float.parse if numeric string, `Float.MAX_VALUE` otherwise, and use a comparator based on it – Alex Salauyou May 28 '19 at 15:13

2 Answers2

5

You can't order a Map by entry value. SortedMap e.g. TreeMap allows to order the entries by key. It seems you might be using a wrong data structure.

In your example you can define a function that will leniently parse a double and return Double.NaN instead of an exception:

private double lenientParseDouble(String s) {
    try {
        return Double.parseDouble(s);
    } catch (NumberFormatException ex) {
        return Double.NaN;
    }
}

As per Double.compare() docs:

Double.NaN is considered by this method to be equal to itself and greater than all other double values (including Double.POSITIVE_INFINITY).

and then use it as part of the Comparator chain

map.values().stream()
        .sorted(Comparator.comparingDouble(this::lenientParseDouble).thenComparing(Function.identity()))
        .forEach(System.out::println);
Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
  • Thank you man, it works. I've never seen this synthax. I have to get the sorted value (to add them to a List) instead of printing them with System.out? Thank you – MDP May 28 '19 at 18:33
1

If you really need a map you can use a LinkedHashMap which is sorted by insertion order. To compare the values you can create your own comparator:

Comparator<String> stringAndNumberComparator = (s1, s2) -> {
    try {
        return Double.valueOf(s1).compareTo(Double.valueOf(s2));
    } catch (NumberFormatException e) {
        return s1.compareTo(s2);
    }
};

Now you can use a java stream to sort the entry set of your map and collect it back to a LinkedHashMap:

Map<Integer, String> sorted = map.entrySet().stream()
        .sorted(Map.Entry.comparingByValue(stringAndNumberComparator))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (s1, s2) -> s1, LinkedHashMap::new));

Alternatively you can use the comparator provided in the answer from Karol Dowbecki with Map.Entry.comparingByValue().

The result will be {2=1.1, 5=2.2, 4=12, 3=google, 1=mark};

If you just need a list of your values you can just use this:

List<String> sorted = map.values().stream()
        .sorted(stringAndNumberComparator)
        .collect(Collectors.toList());

The result in this case will be [1.1, 2.2, 12, google, mark].

Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
  • Thank you maaaaaaaaaan. I tried it and it works. :) When I can't use .compareTo() , because s1 or s2 is null, what value should I return in order not to "ruin" the sorted map? – MDP May 28 '19 at 19:19
  • 1
    @MDP You can add null checks before. e.g. `if (s1 == null) return -1; if (s2 == null) return 1;` but you will get an `Exception` using `Collectors.toMap()`. See https://stackoverflow.com/q/24630963/9662601 for more info on that. Another option would be to filter non null values before or replace them by default non null values. – Samuel Philipp May 28 '19 at 19:23