16

I have a problem with sorting strings which include integers. If I use the below code I get sorting like: 1some, 2some, 20some, 21some, 3some, some

However I want it sorted like: 1some, 2some, 3some, 20some, 21some, some

How can I do this?

Thanks!

Collections.sort(selectedNodes,
    new Comparator<DefaultMutableTreeNode>() {
    @Override
    public int compare(DefaultMutableTreeNode o1,
        DefaultMutableTreeNode o2) {
        return o1.getUserObject().toString()
            .compareTo(o2.getUserObject().toString());
    }
    });
5
  • you should split the strings in two first; the other part being the integer part, and the other the string part. then first compare the integers - if the integers are not equal, the string that should appear first is the one with the smaller integer part. If they are equal, the string that should appear first is the one with alphabetically smaller string part. Commented Nov 27, 2014 at 11:13
  • Parse the integer from the string and compare it before comparing the rest of the string. If it always starts with unique integers, you can even skip the rest of the string. Commented Nov 27, 2014 at 11:13
  • The string can be in any format - e.g: other 1, other 2, 1 some 2 other 3, ... I therefor think it would be difficult to split the string and compare only the integer part. Commented Nov 27, 2014 at 11:16
  • May more than one number appear on the string?; is "1 some 2" a valid element? Commented Nov 27, 2014 at 11:18
  • For info, the code in your question does sort your objects in alphanumeric order Commented Sep 9, 2021 at 8:28

12 Answers 12

13

Here is a self-contained example on how to do this (not particularly optimized):

final Pattern p = Pattern.compile("^\\d+");
String[] examples = { 
   "1some", "2some", "20some", "21some", "3some", "some", "1abc", "abc"
};
Comparator<String> c = new Comparator<String>() {
    @Override
    public int compare(String object1, String object2) {
        Matcher m = p.matcher(object1);
        Integer number1 = null;
        if (!m.find()) {
            return object1.compareTo(object2);
        }
        else {
            Integer number2 = null;
            number1 = Integer.parseInt(m.group());
            m = p.matcher(object2);
            if (!m.find()) {
                return object1.compareTo(object2);
            }
            else {
                number2 = Integer.parseInt(m.group());
                int comparison = number1.compareTo(number2);
                if (comparison != 0) {
                    return comparison;
                }
                else {
                    return object1.compareTo(object2);
                }
            }
        }
    }
};
List<String> examplesList = new ArrayList<String>(Arrays.asList(examples));
Collections.sort(examplesList, c);
System.out.println(examplesList);

Output

[1abc, 1some, 2some, 3some, 20some, 21some, abc, some]

Explanation

  • The example uses a constant Pattern to infer whether a number is in the String's starting position.
  • If not present in the first String, it compares it as is to the second.
  • If present indeed in the first, it checks the second.
  • If not present in the second, it compares the two Strings as is, again
  • If present in both, it compares the Integers instead of the whole Strings, hence resulting in a numerical comparison rather than a lexicographical one
  • If the number compare identical, it goes back to lexicographic comparison of the whole Strings (thanks MihaiC for spotting this one)
Sign up to request clarification or add additional context in comments.

15 Comments

good solution but it sorts by numbers first then by text. so what if the initial array has content like "1some", "2some", "20some", "21some", "3some", "some" ,"abc". It would put abc at the end
@MihaiC just figured out your point. Actually it will put "abc" before "some". Although both at the end, since lexicographical comparison prioritizes digits over alphabetical characters.
i know, i'm just wondering for myself. i would think the correct representation should be "abc",then followed by "1some" "2some" etc. of course numbers get sorted first in any comparator but still interesting question
For me it worked by doing myList.sort(c); Instead of Collections.sort(examplesList, c); But thank you!
@Kikadass List.sort is available since Java 8 only, while my answer was tailored for previous Java versions. If you're using Java 8, you might as well leverage lambdas, method references, etc. for this case.
|
9

Your solution lies in The Alphanum Algorithm and you can implement like this

Comments

9

First make an alphanumerical comparator splitting the string in String or Integer parts.

public class AlphaNumericalComparator implements Comparator<String> {
    @Override
    public int compare(String o1, String o2) {
        List<Object> parts1 = partsOf(o1);
        List<Object> parts2 = partsOf(o2);
        while (!parts1.isEmpty() && !parts2.isEmpty()) {
            Object part1 = parts1.remove(0);
            Object part2 = parts2.remove(0);
            int cmp = 0;
            if (part1 instanceof Integer && part2 instanceof Integer) {
                cmp = Integer.compare((Integer)part1, (Integer)part2);
            } else if (part1 instanceof String && part2 instanceof String) {
                cmp = ((String) part1).compareTo((String) part2);
            } else {
                cmp = part1 instanceof String ? 1 : -1; // XXXa > XXX1
            }
            if (cmp != 0) {
                return cmp;
            }
        }
        if (parts1.isEmpty() && parts2.isEmpty()) {
            return 0;
        }
        return parts1.isEmpty() ? -1 : 1;
    }

    private List<Object> partsOf(String s) {
        List<Object> parts = new LinkedList<>();
        int pos0 = 0;
        int pos = 0;
        boolean wasDigit = false;
        while (true) {
            if (pos >= s.length()
                    || Character.isDigit(s.charAt(pos)) != wasDigit) {
                if (pos > pos0) {
                    String part = s.substring(pos0, pos);
                    parts.add(wasDigit? Integer.valueOf(part) : part);
                    pos0 = pos;
                }
                if (pos >= s.length()) {
                    break;
                }
                wasDigit = !wasDigit;
            }
            ++pos;
        }
        return parts;
    }
};

Then use this comparator in your own one, in Java 8 you may simply use Comparator's static methods.

2 Comments

Thanks gentleman your code helps me. I have to sort Alphanumeric Strings like ABC2, ABC3, ABC1 output is as expected. ABC1, ABC2, ABC3
@Aditchoudhary yes, and ABC9 < ABC10. Good luck.
3

If you know that pattern is always NUMALPHA or ALPHANUM and alpha always same:

if(str1.length() != str2.length()){
   return str1.length() - str2.length();
}

return str1.compareTo(str2);

Comments

1

If you have alphanumeric string array you can directly sort it by using

Arrays.sort(Array_name)

and then to print:

for(String a : Array_name)
    System.out.print(a);

Comments

1

How to Sort String,AlphaNumeric and Numeric value in Java using Comparator

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AlphaNumericSorting {
    public static void main(String[] args) {
        final Pattern p = Pattern.compile("^\\d+");
        String[] examples = { "CD", "DE", "0A", "0B", "0C", "12", "0K", "TA", "0D", "01", "02", "11", "AB", "MN" };
        Comparator<String> c = new Comparator<String>() {
            @Override
            public int compare(String object1, String object2) {
                Matcher m = p.matcher(object1);
                Integer number1 = null;
                if (!m.find()) {
                    Matcher m1 = p.matcher(object2);
                    if (m1.find()) {
                        return object2.compareTo(object1);
                    } else {
                        return object1.compareTo(object2);
                    }
                } else {
                    Integer number2 = null;
                    number1 = Integer.parseInt(m.group());
                    m = p.matcher(object2);
                    if (!m.find()) {
                        // return object1.compareTo(object2);
                        Matcher m1 = p.matcher(object1);
                        if (m1.find()) {
                            return object2.compareTo(object1);
                        } else {
                            return object1.compareTo(object2);
                        }
                    } else {
                        number2 = Integer.parseInt(m.group());
                        int comparison = number1.compareTo(number2);
                        if (comparison != 0) {
                            return comparison;
                        } else {
                            return object1.compareTo(object2);
                        }
                    }
                }
            }
        };
        List<String> examplesList = new ArrayList<String>(Arrays.asList(examples));
        Collections.sort(examplesList, c);
        System.out.println(examplesList);
    }
}

OUTPUT:-

[AB, CD, DE, MN, TA, 0A, 0B, 0C, 0D, 0K, 01, 02, 11, 12]

Comments

0

You need to implement your own Comparator to do this kind of custom sorting. The default String.compareTo() method seems to sort numbers before characters. When 0 in 20some gets compared to s in 3some the 0 has a higher sort priority and therefore the whole word gets sorted in first.
What you would need to do is this: try to split you strings into the number and the character part. That's a hard task since those Strings can consist of many of those parts (or don't they?). You may use algorithms like Alphanum, which Murtaza already showed to you.
If you want to implement it yourself, you could check, where the number part ends. Then parse it to an int with Integer.parse(). Compare int parts if they exist in both Strings, then compare the rest. Well that may not be the most professional solution, but as a beginner you may want craft those things yourself to learn it.

Comments

0

You can't use the default String compareTo() instead need compare the Strings following the below algorithm.

  1. Loop through the first and second String character by character and get a chunk of all strings or numbers
  2. Check if the chunks are numbers or strings
  3. If numbers sort numerically else use String compareTo()

Repeat the steps.

1 Comment

Why can't you use the default String compareTo()?
0

You can do the core of it in one line using regex to extract the numeric part:

Collections.sort(selectedNodes, new Comparator<DefaultMutableTreeNode>() {
    @Override
    public int compare(DefaultMutableTreeNode o1,
        DefaultMutableTreeNode o2) {
        return Integer.parseInt(o1.getUserObject().toString().replaceAll("\\D", "")) -
            Integer.parseInt(o2.getUserObject().toString().replaceAll("\\D", ""));
    }
});

1 Comment

There is a typo in your code. It should be Integer.parseInt(... Also, the example here requires the String to always have a number in it. Sometimes it might have just letters and this will throw an exception.
0

This is a working solution is Java. If you have any suggestions on the code, please let me know on my Gist.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class FB {

    public static int comparator(String s1, String s2) {

        String[] pt1 = s1.split("((?<=[a-z])(?=[0-9]))|((?<=[0-9])(?=[a-z]))"); 
        String[] pt2 = s2.split("((?<=[a-z])(?=[0-9]))|((?<=[0-9])(?=[a-z]))"); 
//pt1 and pt2 arrays will have the string split in alphabets and numbers

        int i=0;
        if(Arrays.equals(pt1, pt2))
            return 0;
        else{
            for(i=0;i<Math.min(pt1.length, pt2.length);i++)
                if(!pt1[i].equals(pt2[i])) {
                    if(!isNumber(pt1[i],pt2[i])) {
                        if(pt1[i].compareTo(pt2[i])>0)
                            return 1;
                        else
                            return -1;
                    }
                    else {
                        int nu1 = Integer.parseInt(pt1[i]);
                        int nu2 = Integer.parseInt(pt2[i]);
                        if(nu1>nu2)
                            return 1;
                        else
                            return -1;
                    }
                }
        }

        if(pt1.length>i)
            return 1;
        else
            return -1;
    }

    private static Boolean isNumber(String n1, String n2) {
        // TODO Auto-generated method stub
        try {
            int nu1 = Integer.parseInt(n1);
            int nu2 = Integer.parseInt(n2);
            return true;
        }
        catch(Exception x) {
            return false;
        }

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        String[] examples = {"1some", "2some", "20some", "21some", "3some", "some", "1abc", "abc"};
        List<String> values = new ArrayList<String>(Arrays.asList(examples));

        System.out.println(values);
        Comparator<String> com = (o1,o2) -> {return comparator(o1,o2);}; //lambda expression

        Collections.sort(values,com);
        System.out.println(values);
    }
}

Output:

[1some, 2some, 20some, 21some, 3some, some, 1abc, abc]
[1abc, 1some, 2some, 3some, 20some, 21some, abc, some]

Comments

0

I use this comparator for order first numeric and then alphabet

public class NumericAlphaComparator implements Comparator<String> {

@Override
public int compare(String o1, String o2) {
    int pos1 = 0;
    int pos2 = 0;

    while (pos1 < o1.length() && pos2 < o2.length()) {
        char c1 = o1.charAt(pos1);
        char c2 = o2.charAt(pos2);

        if (Character.isDigit(c1) && Character.isDigit(c2)) {
            // Both characters are digits, compare numerically
            int num1 = extractNumber(o1, pos1);
            int num2 = extractNumber(o2, pos2);

            if (num1 != num2) {
                return Integer.compare(num1, num2);
            }
        } else {
            // One or both characters are non-digits, compare lexicographically
            int cmp = Character.compare(c1, c2);
            if (cmp != 0) {
                return cmp;
            }
        }

        pos1++;
        pos2++;
    }

    // Handle the case where one string is a prefix of the other
    if (pos1 == o1.length() && pos2 == o2.length()) {
        return 0;
    } else {
        return pos1 == o1.length() ? -1 : 1;
    }
}

private int extractNumber(String s, int startPos) {
    int num = 0;
    while (startPos < s.length() && Character.isDigit(s.charAt(startPos))) {
        num = num * 10 + (s.charAt(startPos) - '0');
        startPos++;
    }
    return num;
}

}

1 Comment

Add an explanation to your answer.
-5
    String [] str = new String[]{"1some", "2some", "20some", "21some", "3some", "some"};
    List<String> list = Arrays.asList(str);

    Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
    System.out.println(list);

1 Comment

This doesn't do what the question asks for. See ideone.com/mWhPIT. The question asks for the order 1some, 2some, 3some, 20some, 21some, some.

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.