[Solved] Sorting TreeMap alphabetically


Eric Berry wrote a handy class that compares Strings by human values instead of traditional machine values. Below is a modified version of it along with Object comparator (what I think you are looking for) and its testing class.

An example on how to use the string comparator:

Map<String,String> humanSortedMap = new TreeMap<>(new AlphaNumericStringComparator());

An example on how to use the object comparator, but this time using a List instead of a TreeMap:

Collections.sort(humanSortedList, new AlphaNumericObjectComparator<QuartzJobWrapper>()
            {
                @Override
                public int compare(QuartzJobWrapper t1, QuartzJobWrapper t2)
                {
                    return compareStrings(t1.getName(), t2.getName());
                }
            });

AlphaNumericStringComparator Source:

  /*
 * Copyright (c) 2007 Eric Berry <[email protected]>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

import java.text.DecimalFormatSymbols;
import java.util.Comparator;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;

/**
 * Compares Strings by human values instead of traditional machine values.
 * 
 * @author elberry
 * @modified Tristan Everitt
 */
public class AlphaNumericStringComparator implements Comparator<String>
{

    private Pattern alphaNumChunkPattern;

    public AlphaNumericStringComparator()
    {
        this(Locale.getDefault());
    }

    public AlphaNumericStringComparator(Locale locale)
    {
        DecimalFormatSymbols dfs = new DecimalFormatSymbols(locale);
        char localeDecimalSeparator = dfs.getDecimalSeparator();
        // alphaNumChunkPatter initialized here to get correct decimal separator for locale.
        alphaNumChunkPattern = Pattern.compile("(\\d+\\" + localeDecimalSeparator + "\\d+)|(\\d+)|(\\D+)");
    }

    @Override
    public int compare(String s1, String s2)
    {
        int compareValue = 0;
        Matcher s1ChunkMatcher = alphaNumChunkPattern.matcher(s1);
        Matcher s2ChunkMatcher = alphaNumChunkPattern.matcher(s2);
        String s1ChunkValue = null;
        String s2ChunkValue = null;

        while (s1ChunkMatcher.find() && s2ChunkMatcher.find() && compareValue == 0)
        {
            s1ChunkValue = s1ChunkMatcher.group();
            s2ChunkValue = s2ChunkMatcher.group();

            // teveritt - Remove white space and make lower case to neutralise it
            s1ChunkValue = s1ChunkValue.replaceAll("\\s+", "");
            s2ChunkValue = s2ChunkValue.replaceAll("\\s+", "");
            s1ChunkValue = StringUtils.lowerCase(s1ChunkValue);
            s2ChunkValue = StringUtils.lowerCase(s2ChunkValue);

            try
            {
                // compare double values - ints get converted to doubles. Eg. 100 = 100.0
                Double s1Double = Double.valueOf(s1ChunkValue);
                Double s2Double = Double.valueOf(s2ChunkValue);
                compareValue = s1Double.compareTo(s2Double);
            }
            catch (NumberFormatException e)
            {
                // not a number, use string comparison.
                compareValue = s1ChunkValue.compareTo(s2ChunkValue);
            }
            // if they are equal thus far, but one has more left, it should come after the one that doesn't.
            if (compareValue == 0)
            {
                if (s1ChunkMatcher.hitEnd() && !s2ChunkMatcher.hitEnd())
                {
                    compareValue = -1;
                }
                else if (!s1ChunkMatcher.hitEnd() && s2ChunkMatcher.hitEnd())
                {
                    compareValue = 1;
                }
            }
        }
        return compareValue;
    }
}

AlphaNumericObjectComparator Source:

/**
 * Compares Objects by human values instead of traditional machine values.
 * 
 * @modified Tristan Everitt
 */
public class AlphaNumericObjectComparator<T> implements Comparator<T>
{

    private AlphaNumericStringComparator stringComparator;

    public AlphaNumericObjectComparator()
    {
        this(Locale.getDefault());
    }

    public AlphaNumericObjectComparator(Locale locale)
    {
        this.stringComparator = new AlphaNumericStringComparator(locale);
    }

    @Override
    public int compare(T t1, T t2)
    {
        return compareStrings(t1.toString(), t2.toString());
    }

    protected int compareStrings(String s1, String s2)
    {
        return stringComparator.compare(s1, s2);
    }
}

AlphaNumericStringComparatorTester Source:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import org.junit.Test;

/**
 *
 * @author Tristan Everitt
 */
public class AlphaNumericStringComparatorTester
{
    @Test
    public void testHumanNaturalSort1()
    {
        List<String> randomList = Arrays.asList("z1.doc", "z10.doc", "z100.doc", "z101.doc", "z102.doc", "z11.doc", "z12.doc", "z13.doc", "z14.doc", "z15.doc", "z16.doc", "z17.doc", "z18.doc",
                "z19.doc", "z2.doc", "z20.doc", "z3.doc", "z4.doc", "z5.doc", "z6.doc", "z7.doc", "z8.doc", "z9.doc", "z1.2.doc", "z1.3.doc");
        Collections.shuffle(randomList, new Random());

        List<String> expected = Arrays.asList("z1.doc", "z1.2.doc", "z1.3.doc", "z2.doc", "z3.doc", "z4.doc", "z5.doc", "z6.doc", "z7.doc", "z8.doc", "z9.doc", "z10.doc", "z11.doc", "z12.doc",
                "z13.doc", "z14.doc", "z15.doc", "z16.doc", "z17.doc", "z18.doc", "z19.doc", "z20.doc", "z100.doc", "z101.doc", "z102.doc");

        assertNotEquals(expected, randomList);
        Collections.sort(randomList, new AlphaNumericStringComparator());
        assertEquals(expected, randomList);
    }

    @Test
    public void testHumanNaturalSort2()
    {
        List<String> randomList = Arrays.asList("z1.doc", "z10.doc", "z100.doc", "z101.doc", "z102.doc", "z11.doc", "z12.doc", "z13.doc", "z14.doc", "z15.doc", "z16.doc", "z17.doc", "z18.doc",
                "z19.doc", "z2.doc", "z20.doc", "z3.doc", "z4.doc", "z5.doc", "z6.doc", "z7.doc", "z8.doc", "z9.doc", "z1.2.doc", "z1.3.doc");
        Collections.shuffle(randomList, new Random());

        List<String> expected = Arrays.asList("z1.doc", "z1.2.doc", "z1.3.doc", "z2.doc", "z3.doc", "z4.doc", "z5.doc", "z6.doc", "z7.doc", "z8.doc", "z9.doc", "z10.doc", "z11.doc", "z12.doc",
                "z13.doc", "z14.doc", "z15.doc", "z16.doc", "z17.doc", "z18.doc", "z19.doc", "z20.doc", "z100.doc", "z101.doc", "z102.doc");

        assertNotEquals(expected, randomList);
        Collections.sort(randomList, new AlphaNumericStringComparator());
        assertEquals(expected, randomList);
    }

    @Test
    public void testHumanNaturalSort3()
    {
        List<String> randomList = Arrays.asList("yr1", "yr10", "yr11", "yr12", "yr13", "yr2", "yr 3", "yr 3.4", "yr 4", "yr5", "yr6", "yr7", "yr8", "yr 9");
        Collections.shuffle(randomList, new Random());

        List<String> expected = Arrays.asList("yr1", "yr2", "yr 3", "yr 3.4", "yr 4", "yr5", "yr6", "yr7", "yr8", "yr 9", "yr10", "yr11", "yr12", "yr13");

        assertNotEquals(expected, randomList);
        Collections.sort(randomList, new AlphaNumericStringComparator());
        assertEquals(expected, randomList);
    }

    @Test
    public void testHumanNaturalSort4()
    {
        List<String> randomList = Arrays.asList("1-2", "1-02", "1-20", "10-20", "fred", "jane", "pic01", "pic2", "pic02", "pic02a", "pic3", "pic4", "pic 4 else", "pic 5", "pic05", "pic 5",
                "pic 5 something", "pic 6", "pic   7", "pic100", "pic100a", "pic120", "pic121", "pic02000", "tom", "x2-g8", "x2-y7", "x2-y08", "x8-y8");
        Collections.shuffle(randomList, new Random());

        List<String> expected = Arrays.asList("1-2", "1-02", "1-20", "10-20", "fred", "jane", "pic01", "pic02", "pic2", "pic02a", "pic3", "pic4", "pic 4 else", "pic 5", "pic05", "pic 5",
                "pic 5 something", "pic 6", "pic   7", "pic100", "pic100a", "pic120", "pic121", "pic02000", "tom", "x2-g8", "x2-y7", "x2-y08", "x8-y8");

        assertNotEquals(expected, randomList);
        Collections.sort(randomList, new AlphaNumericStringComparator());
        assertEquals(expected, randomList);
    }

}

solved Sorting TreeMap alphabetically