0

I need to write a class that handles a Map<String, String[]>, processing its keys according to their numeric order. To add insult to injury, some keys are not valid integers, and they should be processed at the end, in acceding lexicographic order.

For example, if the keys are:

["10", "2", "100", "duck", "black"]

They should by iterated at this order -

["2", "10", "100", "black", "duck"]

What's the most elegant way to do it in Java, other than iterating and try-catching NumberFormatException? Obviously, I can not control the format of the given map.

APerson
  • 8,140
  • 8
  • 35
  • 49
Adam Matan
  • 128,757
  • 147
  • 397
  • 562

2 Answers2

6

Since you need to iterate in a particular order that isn't the input map's natural order, you'll need to dump it into another map (or a list if you don't need the associated values for each key). Use a TreeMap with a custom comparator:

class NumbersThenWordsComparator implements Comparator<String> {
    private static Integer intValue(String s) {
        try {
            return Integer.valueOf(s);
        } catch (NumberFormatException e) {
            return null;
        }
    }

    @Override
    public int compare(String s1, String s2) {
        Integer i1 = intValue(s1);
        Integer i2 = intValue(s2);
        if (i1 == null && i2 == null) {
            return s1.compareTo(s2);
        } else if (i1 == null) {
            return -1;
        } else if (i2 == null) {
            return 1;
        } else {
            return i1.compareTo(i2);
        }
    }       
}

public void myMethod(Map<String, String[]> originalMap) {
    TreeMap<String, String[]> t =
        new TreeMap<String, String[]>(new NumbersThenWordsComparator());
    t.putAll(originalMap);
    // now iterate over t, which will produce entries in the desired order
}
jacobm
  • 13,790
  • 1
  • 25
  • 27
  • +1 except you shouldn't call Integer.valueOf directly because having non number strings is not an exceptionnal situation that should be handled. Exceptions are not supposed to be used this way. – Sebastien Lorber Mar 04 '12 at 16:18
  • Agreed. Unfortunately, the standard Java API offers no suitable alternative. (If you really care, you can implement a method yourself that does the check -- see http://stackoverflow.com/questions/237159/whats-the-best-way-to-check-to-see-if-a-string-represents-an-integer-in-java for instance -- but the fact that this is a pain is a well-known API wart.) – jacobm Mar 04 '12 at 16:24
  • Actually i think this benchmark shows that using Regex is not a bad idea: http://stackoverflow.com/a/7324087/82609 – Sebastien Lorber Mar 04 '12 at 17:31
  • If performance is paramount. Otherwise: catching the exception is gross but obviously works and creates no maintenance burden. If you write a custom parser you incur a maintenance cost and need to convince yourself and your readers that your method works. As with everything it's a trade off. – jacobm Mar 04 '12 at 17:45
0

Use a SortedMap, which sorts keys based on their natural ordering. For Strings, it's lexicographic. I'm not sure if numbers come before letters, but either way sorting will be well defined and consistent, so you can handle it properly to get numbers first or last (i.e. do something like ArrayList<String> keys = yourMap.keySet() and then modify it if the number strings aren't first).

smessing
  • 4,330
  • 1
  • 22
  • 19
  • 2
    100 will come before 2 in lexicographic order. – Adam Matan Mar 04 '12 at 15:28
  • If you know numbers are going to be a certain size, you can pad them with zeros using DecimalFormat. I.e. `DecimalFormat format = new DecimalFormat("00000");` This would produce `00100` and `00002` which would be properly sorted lexicographically. Granted, this requires an additional assumption about your data. – smessing Mar 04 '12 at 15:31
  • 1
    Also, if you don't want to do that, a simple solution would be to write your own custom comparator, and use that to sort keys in your `SortedMap`. – smessing Mar 04 '12 at 15:32