Сравнение строк порядка сортировки в Java - один встроенный?

Мне нужна функция сравнения строк, которая сохраняет естественный порядок сортировки 1. Есть ли что-то подобное в Java? Я не могу найти ничего в String class, а Класс компаратора знает только о двух реализациях.

Я могу опрокинуть свои собственные (это не очень сложная проблема), но я бы предпочел не изобретать колесо, если мне это не нужно.

В моем конкретном случае у меня есть строки версии программного обеспечения, которые я хочу сортировать. Поэтому я хочу, чтобы "1.2.10.5" считалось больше, чем "1.2.9.1".


1 В соответствии с "естественным" порядком сортировки я имею в виду, что он сравнивает строки так, как человек сравнивал бы их, в отличие от "ascii-betical" сортировки, что имеет смысл только для программистов. Другими словами, "image9.jpg" меньше, чем "image10.jpg", а "album1set2page9photo1.jpg" меньше, чем "album1set2page10photo5.jpg", а "1.2.9.1" меньше "1.2.10.5"

Ответ 1

В java "естественный" порядок означает "лексикографический" порядок, поэтому в ядре нет такой реализации, как тот, который вы ищете.

Существуют версии с открытым исходным кодом.

Здесь один:

NaturalOrderComparator.java

Убедитесь, что вы прочитали:

Лицензия с открытым исходным кодом Cougaar

Надеюсь, это поможет!

Ответ 2

String реализует Comparable, и именно это естественное упорядочение в Java (сравнение с использованием сопоставимого интерфейса). Вы можете поместить строки в TreeSet или отсортировать, используя классы Collections или Arrays.

Однако в вашем случае вы не хотите, чтобы "естественный порядок" вам действительно нужен пользовательский компаратор, который затем можно использовать в методе Collections.sort или в методе Arrays.sort, который принимает компаратор.

В терминах конкретной логики, которую вы ищете для реализации в компараторе (числа, разделенные точками), я не знаю о существующих стандартных реализациях этого, но, как вы сказали, это не сложная проблема.

EDIT: В вашем комментарии ваша ссылка дает вам здесь, что делает достойную работу, если вы не возражаете, что это деликатный случай. Вот код, измененный, чтобы вы могли пройти в String.CASE_INSENSITIVE_ORDER:

    /*
     * The Alphanum Algorithm is an improved sorting algorithm for strings
     * containing numbers.  Instead of sorting numbers in ASCII order like
     * a standard sort, this algorithm sorts numbers in numeric order.
     *
     * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
     *
     *
     * This library is free software; you can redistribute it and/or
     * modify it under the terms of the GNU Lesser General Public
     * License as published by the Free Software Foundation; either
     * version 2.1 of the License, or any later version.
     *
     * This library is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     * Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public
     * License along with this library; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     *
     */

    import java.util.Comparator;

    /**
     * This is an updated version with enhancements made by Daniel Migowski,
     * Andre Bogus, and David Koelle
     *
     * To convert to use Templates (Java 1.5+):
     *   - Change "implements Comparator" to "implements Comparator<String>"
     *   - Change "compare(Object o1, Object o2)" to "compare(String s1, String s2)"
     *   - Remove the type checking and casting in compare().
     *
     * To use this class:
     *   Use the static "sort" method from the java.util.Collections class:
     *   Collections.sort(your list, new AlphanumComparator());
     */
    public class AlphanumComparator implements Comparator<String>
    {
        private Comparator<String> comparator = new NaturalComparator();

        public AlphanumComparator(Comparator<String> comparator) {
            this.comparator = comparator;
        }

        public AlphanumComparator() {

        }

        private final boolean isDigit(char ch)
        {
            return ch >= 48 && ch <= 57;
        }

        /** Length of string is passed in for improved efficiency (only need to calculate it once) **/
        private final String getChunk(String s, int slength, int marker)
        {
            StringBuilder chunk = new StringBuilder();
            char c = s.charAt(marker);
            chunk.append(c);
            marker++;
            if (isDigit(c))
            {
                while (marker < slength)
                {
                    c = s.charAt(marker);
                    if (!isDigit(c))
                        break;
                    chunk.append(c);
                    marker++;
                }
            } else
            {
                while (marker < slength)
                {
                    c = s.charAt(marker);
                    if (isDigit(c))
                        break;
                    chunk.append(c);
                    marker++;
                }
            }
            return chunk.toString();
        }

        public int compare(String s1, String s2)
        {

            int thisMarker = 0;
            int thatMarker = 0;
            int s1Length = s1.length();
            int s2Length = s2.length();

            while (thisMarker < s1Length && thatMarker < s2Length)
            {
                String thisChunk = getChunk(s1, s1Length, thisMarker);
                thisMarker += thisChunk.length();

                String thatChunk = getChunk(s2, s2Length, thatMarker);
                thatMarker += thatChunk.length();

                // If both chunks contain numeric characters, sort them numerically
                int result = 0;
                if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0)))
                {
                    // Simple chunk comparison by length.
                    int thisChunkLength = thisChunk.length();
                    result = thisChunkLength - thatChunk.length();
                    // If equal, the first different number counts
                    if (result == 0)
                    {
                        for (int i = 0; i < thisChunkLength; i++)
                        {
                            result = thisChunk.charAt(i) - thatChunk.charAt(i);
                            if (result != 0)
                            {
                                return result;
                            }
                        }
                    }
                } else
                {
                    result = comparator.compare(thisChunk, thatChunk);
                }

                if (result != 0)
                    return result;
            }

            return s1Length - s2Length;
        }

        private static class NaturalComparator implements Comparator<String> {
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        }
    }

Ответ 3

Я тестировал три реализации Java, упомянутых здесь другими, и обнаружил, что их работа несколько по-другому, но не такая, как я ожидал.

Оба AlphaNumericStringComparator и AlphanumComparator не игнорируют пробелы, поэтому что pic2 помещается перед pic 1.

С другой стороны NaturalOrderComparator игнорирует не только пробелы, но и все ведущие нули, так что sig[1] предшествует sig[0].

Относительно производительности AlphaNumericStringComparator ~ x10 медленнее, чем два других.

Ответ 4

Взгляните на эту реализацию. Он должен быть как можно быстрее, без каких-либо регулярных выражений или манипуляций с массивами или вызовов методов, всего несколько флагов и много случаев.

Это должно сортировать любую комбинацию чисел внутри строк и правильно поддерживать числа, которые равны и перемещаются.

public static int naturalCompare(String a, String b, boolean ignoreCase) {
    if (ignoreCase) {
        a = a.toLowerCase();
        b = b.toLowerCase();
    }
    int aLength = a.length();
    int bLength = b.length();
    int minSize = Math.min(aLength, bLength);
    char aChar, bChar;
    boolean aNumber, bNumber;
    boolean asNumeric = false;
    int lastNumericCompare = 0;
    for (int i = 0; i < minSize; i++) {
        aChar = a.charAt(i);
        bChar = b.charAt(i);
        aNumber = aChar >= '0' && aChar <= '9';
        bNumber = bChar >= '0' && bChar <= '9';
        if (asNumeric)
            if (aNumber && bNumber) {
                if (lastNumericCompare == 0)
                    lastNumericCompare = aChar - bChar;
            } else if (aNumber)
                return 1;
            else if (bNumber)
                return -1;
            else if (lastNumericCompare == 0) {
                if (aChar != bChar)
                    return aChar - bChar;
                asNumeric = false;
            } else
                return lastNumericCompare;
        else if (aNumber && bNumber) {
            asNumeric = true;
            if (lastNumericCompare == 0)
                lastNumericCompare = aChar - bChar;
        } else if (aChar != bChar)
            return aChar - bChar;
    }
    if (asNumeric)
        if (aLength > bLength && a.charAt(bLength) >= '0' && a.charAt(bLength) <= '9') // as number
            return 1;  // a has bigger size, thus b is smaller
        else if (bLength > aLength && b.charAt(aLength) >= '0' && b.charAt(aLength) <= '9') // as number
            return -1;  // b has bigger size, thus a is smaller
        else if (lastNumericCompare == 0)
          return aLength - bLength;
        else
            return lastNumericCompare;
    else
        return aLength - bLength;
}

Ответ 5

Как насчет использования метода split() из String, проанализируйте одиночную числовую строку и затем сравните их по одному?

 @Test
public void test(){
    System.out.print(compare("1.12.4".split("\\."), "1.13.4".split("\\."),0));
}


public static int compare(String[] arr1, String[] arr2, int index){
    // if arrays do not have equal size then and comparison reached the upper bound of one of them
    // then the longer array is considered the bigger ( --> 2.2.0 is bigger then 2.2)
    if(arr1.length <= index || arr2.length <= index) return arr1.length - arr2.length;
    int result = Integer.parseInt(arr1[index]) - Integer.parseInt(arr2[index]);
    return result == 0 ?  compare(arr1, arr2, ++index) : result;
}

Я не проверял угловые случаи, но это должно работать, и оно довольно компактно

Ответ 6

Он объединяет цифры, затем сравнивает их. И если он не применим, он продолжается.

public int compare(String o1, String o2) {
if(o1 == null||o2 == null)
    return 0;
for(int i = 0; i<o1.length()&&i<o2.length();i++){
    if(Character.isDigit(o1.charAt(i)) || Character.isDigit(o2.charAt(i)))
    {
    String dig1 = "",dig2 = "";     
    for(int x = i; x<o1.length() && Character.isDigit(o1.charAt(i)); x++){                              
        dig1+=o1.charAt(x);
    }
    for(int x = i; x<o2.length() && Character.isDigit(o2.charAt(i)); x++){
        dig2+=o2.charAt(x);
    }
    if(Integer.valueOf(dig1) < Integer.valueOf(dig2))
        return -1;
    if(Integer.valueOf(dig1) > Integer.valueOf(dig2))
        return 1;
    }       
if(o1.charAt(i)<o2.charAt(i))
    return -1;
if(o1.charAt(i)>o2.charAt(i))
    return 1;
}
return 0;

}

Ответ 7

Может быть, поздний ответ. Но мой ответ может помочь кому-то другому, кому нужен такой компаратор.

Я также проверил пару других компараторов. Но моя кажется немного эффективной, чем другие, которые я сравнивал. Также попробовал тот, который отправил Ишай. Mine берет только половину времени, как упомянутый, для данных буквенно-цифрового набора данных из 100 записей.

/**
 * Sorter that compares the given Alpha-numeric strings. This iterates through each characters to
 * decide the sort order. There are 3 possible cases while iterating,
 * 
 * <li>If both have same non-digit characters then the consecutive characters will be considered for
 * comparison.</li>
 * 
 * <li>If both have numbers at the same position (with/without non-digit characters) the consecutive
 * digit characters will be considered to form the valid integer representation of the characters
 * will be taken and compared.</li>
 * 
 * <li>At any point if the comparison gives the order(either > or <) then the consecutive characters
 * will not be considered.</li>
 * 
 * For ex., this will be the ordered O/P of the given list of Strings.(The bold characters decides
 * its order) <i><b>2</b>b,<b>100</b>b,a<b>1</b>,A<b>2</b>y,a<b>100</b>,</i>
 * 
 * @author kannan_r
 * 
 */
class AlphaNumericSorter implements Comparator<String>
{
    /**
     * Does the Alphanumeric sort of the given two string
     */
    public int compare(String theStr1, String theStr2)
    {
        char[] theCharArr1 = theStr1.toCharArray();
        char[] theCharArr2 = theStr2.toCharArray();
        int aPosition = 0;
        if (Character.isDigit(theCharArr1[aPosition]) && Character.isDigit(theCharArr2[aPosition]))
        {
            return sortAsNumber(theCharArr1, theCharArr2, aPosition++ );
        }
        return sortAsString(theCharArr1, theCharArr2, 0);
    }

    /**
     * Sort the given Arrays as string starting from the given position. This will be a simple case
     * insensitive sort of each characters. But at any given position if there are digits in both
     * arrays then the method sortAsNumber will be invoked for the given position.
     * 
     * @param theArray1 The first character array.
     * @param theArray2 The second character array.
     * @param thePosition The position starting from which the calculation will be done.
     * @return positive number when the Array1 is greater than Array2<br/>
     *         negative number when the Array2 is greater than Array1<br/>
     *         zero when the Array1 is equal to Array2
     */
    private int sortAsString(char[] theArray1, char[] theArray2, int thePosition)
    {
        int aResult = 0;
        if (thePosition < theArray1.length && thePosition < theArray2.length)
        {
            aResult = (int)theArray1[thePosition] - (int)theArray2[thePosition];
            if (aResult == 0)
            {
                ++thePosition;
                if (thePosition < theArray1.length && thePosition < theArray2.length)
                {
                    if (Character.isDigit(theArray1[thePosition]) && Character.isDigit(theArray2[thePosition]))
                    {
                        aResult = sortAsNumber(theArray1, theArray2, thePosition);
                    }
                    else
                    {
                        aResult = sortAsString(theArray1, theArray2, thePosition);
                    }
                }
            }
        }
        else
        {
            aResult = theArray1.length - theArray2.length;
        }
        return aResult;
    }

    /**
     * Sorts the characters in the given array as number starting from the given position. When
     * sorted as numbers the consecutive characters starting from the given position upto the first
     * non-digit character will be considered.
     * 
     * @param theArray1 The first character array.
     * @param theArray2 The second character array.
     * @param thePosition The position starting from which the calculation will be done.
     * @return positive number when the Array1 is greater than Array2<br/>
     *         negative number when the Array2 is greater than Array1<br/>
     *         zero when the Array1 is equal to Array2
     */
    private int sortAsNumber(char[] theArray1, char[] theArray2, int thePosition)
    {
        int aResult = 0;
        int aNumberInStr1;
        int aNumberInStr2;
        if (thePosition < theArray1.length && thePosition < theArray2.length)
        {
            if (Character.isDigit(theArray1[thePosition]) && Character.isDigit(theArray1[thePosition]))
            {
                aNumberInStr1 = getNumberInStr(theArray1, thePosition);
                aNumberInStr2 = getNumberInStr(theArray2, thePosition);

                aResult = aNumberInStr1 - aNumberInStr2;

                if (aResult == 0)
                {
                    thePosition = getNonDigitPosition(theArray1, thePosition);
                    if (thePosition != -1)
                    {
                        aResult = sortAsString(theArray1, theArray2, thePosition);
                    }
                }
            }
            else
            {
                aResult = sortAsString(theArray1, theArray2, ++thePosition);
            }
        }
        else
        {
            aResult = theArray1.length - theArray2.length;
        }
        return aResult;
    }

    /**
     * Gets the position of the non digit character in the given array starting from the given
     * position.
     * 
     * @param theCharArr /the character array.
     * @param thePosition The position after which the array need to be checked for non-digit
     *        character.
     * @return The position of the first non-digit character in the array.
     */
    private int getNonDigitPosition(char[] theCharArr, int thePosition)
    {
        for (int i = thePosition; i < theCharArr.length; i++ )
        {
            if ( !Character.isDigit(theCharArr[i]))
            {
                return i;
            }
        }
        return -1;
    }

    /**
     * Gets the integer value of the number starting from the given position of the given array.
     * 
     * @param theCharArray The character array.
     * @param thePosition The position form which the number need to be calculated.
     * @return The integer value of the number.
     */
    private int getNumberInStr(char[] theCharArray, int thePosition)
    {
        int aNumber = 0;
        for (int i = thePosition; i < theCharArray.length; i++ )
        {
            if(!Character.isDigit(theCharArray[i]))
            {
               return aNumber;
            }
            aNumber += aNumber * 10 + (theCharArray[i] - 48);
        }
        return aNumber;
    }
}

Ответ 8

Использование опции RuleBasedCollator также может быть опцией. Хотя вам придется добавить все правила порядка сортировки заранее, чтобы это не было хорошим решением, если вы хотите также учитывать большие числа.

Добавление определенных настроек, таких как 2 < 10, довольно просто и может быть полезно для сортировки специальных идентификаторов версий, таких как Trusty < Precise < Xenial < Yakkety.

RuleBasedCollator localRules = (RuleBasedCollator) Collator.getInstance();

String extraRules = IntStream.range(0, 100).mapToObj(String::valueOf).collect(joining(" < "));
RuleBasedCollator c = new RuleBasedCollator(localRules.getRules() + " & " + extraRules);

List<String> a = asList("1-2", "1-02", "1-20", "10-20", "fred", "jane", "pic01", "pic02", "pic02a", "pic 5", "pic05", "pic   7", "pic100", "pic100a", "pic120", "pic121");
shuffle(a);

a.sort(c);
System.out.println(a);

Ответ 9

Возможно, эта реализация будет работать лучше для вас.

(btw Мне нужно скопировать/вставить его здесь?)