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.

2 Answers 2

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
}
Sign up to request clarification or add additional context in comments.

4 Comments

+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.
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 stackoverflow.com/questions/237159/… for instance -- but the fact that this is a pain is a well-known API wart.)
Actually i think this benchmark shows that using Regex is not a bad idea: stackoverflow.com/a/7324087/82609
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.
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).

3 Comments

100 will come before 2 in lexicographic order.
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.
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.