73 votes

Comparaison naturelle de chaînes de caractères par ordre de tri en Java - y en a-t-il une intégrée ?

J'aimerais une sorte de fonction de comparaison de chaînes qui préserve l'ordre naturel de tri. 1 . Y a-t-il quelque chose de ce genre intégré à Java ? Je ne trouve rien dans le Classe de chaînes y el Classe de comparateur ne connaît que deux implémentations.

Je peux créer mon propre système (ce n'est pas un problème très difficile), mais je préfère ne pas réinventer la roue si je n'y suis pas obligé.

Dans mon cas spécifique, j'ai des chaînes de version de logiciel que je veux trier. Ainsi, je veux que "1.2.10.5" soit considéré comme supérieur à "1.2.9.1".


1 Par ordre de tri "naturel", j'entends qu'il compare les chaînes de caractères comme le ferait un humain, par opposition à l'ordre de tri "ascii-bétique" qui n'a de sens que pour les programmeurs. En d'autres termes, "image9.jpg" est plus petit que "image10.jpg", et "album1set2page9photo1.jpg" est plus petit que "album1set2page10photo5.jpg", et "1.2.9.1" est plus petit que "1.2.10.5".

0 votes

Amusant, tout le monde - relisez la question et supprimez les réponses postées ! !! .. :) Je suppose que c'est le pouvoir du DOWNVOTE ! !! :) ;)

2 votes

BTW. Le numérique n'est pas l'ordre naturel lorsqu'on parle de chaînes de caractères, la question est donc un peu trompeuse.

2 votes

Rien de tel intégré à ma connaissance. Quand coder prend moins de temps que de demander sur SO, je cours généralement pour ma propre roue... :)

55voto

OscarRyz Points 82553

En Java, la signification de l'ordre "naturel" est l'ordre "lexicographique", il n'y a donc pas d'implémentation dans le noyau comme celle que vous recherchez.

Il existe des implémentations open source.

En voici une :

NaturalOrderComparator.java

Assurez-vous de lire le :

Licence Open Source Cougaar

J'espère que cela vous aidera !

0 votes

Merci. J'ai révisé la question pour clarifier ce que j'entendais par "naturel".

2 votes

J'adore SO - je fouille au hasard, et voici une solution rapide à un problème que j'ai repoussé depuis une semaine. C'est un lien très utile - merci ! (Et la licence est compatible avec notre projet)

5 votes

Lorsque vous choisissez une mise en œuvre open source comme celles-ci, vous devez vous assurer qu'elles font exactement ce que vous attendez. Beaucoup se concentrent sur la fourniture d'un ordre qui semble intuitif et joli dans une interface utilisateur. Parfois, elles acceptent et sautent les espaces, les zéros de tête et, surtout, placent les chaînes plus courtes avant les chaînes plus longues lorsqu'elles sont équivalentes. La chaîne 1.020 sera donc placée après 1.20. Si vous l'utilisez pour déterminer si deux versions sont égales, vous pouvez obtenir un faux négatif dans ce cas. C'est-à-dire en vérifiant que compareTo() renvoie 0.

12voto

Mikhail Points 3393

J'ai testé trois implémentations Java mentionnées ici par d'autres personnes et j'ai constaté qu'elles fonctionnent de manière légèrement différente, mais pas comme je m'y attendais.

Les deux sites AlphaNumericStringComparator y AlphanumComparator n'ignorent pas les espaces, de sorte que pic2 est placé avant pic 1 .

D'autre part NaturalOrderComparator ignore non seulement les espaces mais aussi tous les zéros de tête, de sorte que sig[1] précède sig[0] .

En ce qui concerne les performances AlphaNumericStringComparator est ~x10 plus lent que les deux autres.

0 votes

Cela doit être comme ça puisque AlphaNumericStringComparator utilise le regex (ce qui est une mauvaise idée de toute façon)

11voto

Yishai Points 42417

String implémente Comparable, et c'est ce qu'est l'ordre naturel en Java (comparaison à l'aide de l'interface comparable). Vous pouvez placer les chaînes dans un TreeSet ou les trier à l'aide des classes Collections ou Arrays.

Cependant, dans votre cas, vous ne voulez pas d'un "classement naturel", mais plutôt d'un comparateur personnalisé, que vous pouvez ensuite utiliser dans la méthode Collections.sort ou la méthode Arrays.sort qui prend un comparateur.

En ce qui concerne la logique spécifique que vous cherchez à mettre en œuvre dans le comparateur (chiffres séparés par des points), je ne connais pas d'implémentation standard existante pour cela, mais comme vous l'avez dit, ce n'est pas un problème difficile.

EDIT : Dans votre commentaire, votre lien vous amène à aquí qui fait un bon travail si vous n'êtes pas gêné par le fait qu'il est sensible à la casse. Voici ce code modifié pour vous permettre de passer dans le fichier 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);
            }
        }
    }

9 votes

Le "tri naturel" est le terme largement utilisé pour trier "image9.jpg" comme étant inférieur à "image10.jpg". Il est "naturel" parce que c'est la façon dont un être humain les trierait, par opposition au tri "ascii-bétique" non naturel que les ordinateurs effectuent par défaut. codinghorror.com/blog/archives/001018.html

0 votes

J'ai mis à jour la question pour qu'elle soit plus claire à cet égard.

1 votes

Dans le lien codinghorror que vous postez, il y a un lien qui mène à ceci, qui a une implémentation Java : davekoelle.com/alphanum.html

2voto

bennidi Points 734

Que diriez-vous d'utiliser la méthode split() de String, d'analyser la chaîne numérique unique et de la comparer une par une ?

 @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;
}

Je n'ai pas vérifié les cas particuliers mais cela devrait fonctionner et c'est assez compact.

0 votes

Est plus limitée : elle ne traite que les chaînes de caractères qui sont des listes d'entiers délimitées par des points. Je pourrais vouloir comparer "user1album12photo4.jpg" à "user1album13photo4.jpg".

0 votes

C'est vrai... je ne me suis concentré que sur les chaînes de la version du logiciel. désolé, mais malgré tout, j'aime personnellement la solution.

1voto

xtf Points 21

Il concasse les chiffres, puis les compare. Et si c'est sans objet, il continue.

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;

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X