web-dev-qa-db-fra.com

Un moyen efficace de comparer les chaînes de version dans Java

Duplicata possible:
Comment comparez-vous deux chaînes de version en Java?

J'ai 2 chaînes qui contiennent des informations de version comme indiqué ci-dessous:

str1 = "1.2"
str2 = "1.1.2"

Maintenant, quelqu'un peut-il me dire le moyen efficace de comparer ces versions à l'intérieur de chaînes dans Java & return 0, si elles sont égales, -1, si str1 <str2 & 1 si str1> str2 .

56
Mike

Nécessite commons-lang3-3.8.1.jar pour les opérations de chaîne.

/**
 * Compares two version strings. 
 * 
 * Use this instead of String.compareTo() for a non-lexicographical 
 * comparison that works for version strings. e.g. "1.10".compareTo("1.6").
 * 
 * @param v1 a string of alpha numerals separated by decimal points. 
 * @param v2 a string of alpha numerals separated by decimal points.
 * @return The result is 1 if v1 is greater than v2. 
 *         The result is 2 if v2 is greater than v1. 
 *         The result is -1 if the version format is unrecognized. 
 *         The result is zero if the strings are equal.
 */

public int VersionCompare(String v1,String v2)
{
    int v1Len=StringUtils.countMatches(v1,".");
    int v2Len=StringUtils.countMatches(v2,".");

    if(v1Len!=v2Len)
    {
        int count=Math.abs(v1Len-v2Len);
        if(v1Len>v2Len)
            for(int i=1;i<=count;i++)
                v2+=".0";
        else
            for(int i=1;i<=count;i++)
                v1+=".0";
    }

    if(v1.equals(v2))
        return 0;

    String[] v1Str=StringUtils.split(v1, ".");
    String[] v2Str=StringUtils.split(v2, ".");
    for(int i=0;i<v1Str.length;i++)
    {
        String str1="",str2="";
        for (char c : v1Str[i].toCharArray()) {
            if(Character.isLetter(c))
            {
                int u=c-'a'+1;
                if(u<10)
                    str1+=String.valueOf("0"+u);
                else
                    str1+=String.valueOf(u);
            }
            else
                str1+=String.valueOf(c);
        }            
        for (char c : v2Str[i].toCharArray()) {
            if(Character.isLetter(c))
            {
                int u=c-'a'+1;
                if(u<10)
                    str2+=String.valueOf("0"+u);
                else
                    str2+=String.valueOf(u);
            }
            else
                str2+=String.valueOf(c);
        }
        v1Str[i]="1"+str1;
        v2Str[i]="1"+str2;

            int num1=Integer.parseInt(v1Str[i]);
            int num2=Integer.parseInt(v2Str[i]);

            if(num1!=num2)
            {
                if(num1>num2)
                    return 1;
                else
                    return 2;
            }
    }
    return -1;
}    
146
Alex Gitelman

Comme d'autres l'ont souligné, String.split () est un moyen très facile de faire la comparaison que vous voulez, et Mike Deck fait l'excellent point qu'avec de telles cordes courtes (probables), cela n'aura probablement pas beaucoup d'importance, mais ce que le Hey! Si vous souhaitez effectuer la comparaison sans analyser manuellement la chaîne et avoir la possibilité de quitter plus tôt, vous pouvez essayer la classe Java.util.Scanner .

public static int versionCompare(String str1, String str2) {
    try ( Scanner s1 = new Scanner(str1);
          Scanner s2 = new Scanner(str2);) {
        s1.useDelimiter("\\.");
        s2.useDelimiter("\\.");

        while (s1.hasNextInt() && s2.hasNextInt()) {
            int v1 = s1.nextInt();
            int v2 = s2.nextInt();
            if (v1 < v2) {
                return -1;
            } else if (v1 > v2) {
                return 1;
            }
        }

        if (s1.hasNextInt() && s1.nextInt() != 0)
            return 1; //str1 has an additional lower-level version number
        if (s2.hasNextInt() && s2.nextInt() != 0)
            return -1; //str2 has an additional lower-level version 

        return 0;
    } // end of try-with-resources
}
14
Eric

Je cherchais à le faire moi-même et je vois trois approches différentes pour le faire, et jusqu'à présent, presque tout le monde fractionne les chaînes de version. Je ne pense pas que cela soit efficace, bien que la taille du code soit bonne et semble bonne.

Approches:

  1. Supposons une limite supérieure au nombre de sections (ordinaux) dans une chaîne de version ainsi qu'une limite à la valeur qui y est représentée. Souvent 4 points maximum et 999 maximum pour n'importe quel ordinal. Vous pouvez voir où cela va, et cela va transformer la version pour qu'elle tienne dans une chaîne comme: "1.0" => "001000000000" avec un format de chaîne ou une autre façon de remplir chaque ordinal. Faites ensuite une comparaison de chaînes.
  2. Divisez les chaînes sur le séparateur ordinal ('.') Et parcourez-les et comparez une version analysée. C'est l'approche bien démontrée par Alex Gitelman.
  3. Comparer les ordinaux au fur et à mesure que vous les analysez des chaînes de version en question. Si toutes les chaînes n'étaient vraiment que des pointeurs vers des tableaux de caractères comme en C, alors ce serait l'approche claire (où vous remplaceriez un '.' Par un terminateur nul comme il est trouvé et déplacez environ 2 ou 4 pointeurs.

Réflexions sur les trois approches:

  1. Il y avait un article de blog lié qui montrait comment aller avec 1. Les limitations sont en longueur de chaîne de version, nombre de sections et valeur maximale de la section. Je ne pense pas que ce soit fou d'avoir une telle chaîne qui casse 10 000 à un moment donné. De plus, la plupart des implémentations finissent toujours par diviser la chaîne.
  2. Fractionner les chaînes à l'avance est clair à lire et à réfléchir, mais nous parcourons chaque chaîne environ deux fois pour ce faire. Je voudrais comparer comment cela se passe avec la prochaine approche.
  3. La comparaison de la chaîne au fur et à mesure de son fractionnement vous offre l'avantage de pouvoir arrêter le fractionnement très tôt dans une comparaison de: "2.1001.100101.9999998" à "1.0.0.0.0.0.1.0.0.0.1". S'il s'agissait de C et non Java les avantages pourraient continuer à limiter la quantité de mémoire allouée aux nouvelles chaînes pour chaque section de chaque version, mais ce n'est pas le cas.

Je n'ai vu personne donner un exemple de cette troisième approche, alors je voudrais l'ajouter ici comme une réponse à l'efficience.

public class VersionHelper {

    /**
     * Compares one version string to another version string by dotted ordinals.
     * eg. "1.0" > "0.09" ; "0.9.5" < "0.10",
     * also "1.0" < "1.0.0" but "1.0" == "01.00"
     *
     * @param left  the left hand version string
     * @param right the right hand version string
     * @return 0 if equal, -1 if thisVersion &lt; comparedVersion and 1 otherwise.
     */
    public static int compare(@NotNull String left, @NotNull String right) {
        if (left.equals(right)) {
            return 0;
        }
        int leftStart = 0, rightStart = 0, result;
        do {
            int leftEnd = left.indexOf('.', leftStart);
            int rightEnd = right.indexOf('.', rightStart);
            Integer leftValue = Integer.parseInt(leftEnd < 0
                    ? left.substring(leftStart)
                    : left.substring(leftStart, leftEnd));
            Integer rightValue = Integer.parseInt(rightEnd < 0
                    ? right.substring(rightStart)
                    : right.substring(rightStart, rightEnd));
            result = leftValue.compareTo(rightValue);
            leftStart = leftEnd + 1;
            rightStart = rightEnd + 1;
        } while (result == 0 && leftStart > 0 && rightStart > 0);
        if (result == 0) {
            if (leftStart > rightStart) {
                return containsNonZeroValue(left, leftStart) ? 1 : 0;
            }
            if (leftStart < rightStart) {
                return containsNonZeroValue(right, rightStart) ? -1 : 0;
            }
        }
        return result;
    }

    private static boolean containsNonZeroValue(String str, int beginIndex) {
        for (int i = beginIndex; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c != '0' && c != '.') {
                return true;
            }
        }
        return false;
    }
}

Test unitaire démontrant la sortie attendue.

public class VersionHelperTest {

    @Test
    public void testCompare() throws Exception {
        assertEquals(1, VersionHelper.compare("1", "0.9"));
        assertEquals(1, VersionHelper.compare("0.0.0.2", "0.0.0.1"));
        assertEquals(1, VersionHelper.compare("1.0", "0.9"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2.0.0"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2.0"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2"));
        assertEquals(1, VersionHelper.compare("0.9.1", "0.9.0"));
        assertEquals(1, VersionHelper.compare("0.9.2", "0.9.1"));
        assertEquals(1, VersionHelper.compare("0.9.11", "0.9.2"));
        assertEquals(1, VersionHelper.compare("0.9.12", "0.9.11"));
        assertEquals(1, VersionHelper.compare("0.10", "0.9"));
        assertEquals(0, VersionHelper.compare("0.10", "0.10"));
        assertEquals(-1, VersionHelper.compare("2.10", "2.10.1"));
        assertEquals(-1, VersionHelper.compare("0.0.0.2", "0.1"));
        assertEquals(1, VersionHelper.compare("1.0", "0.9.2"));
        assertEquals(1, VersionHelper.compare("1.10", "1.6"));
        assertEquals(0, VersionHelper.compare("1.10", "1.10.0.0.0.0"));
        assertEquals(1, VersionHelper.compare("1.10.0.0.0.1", "1.10"));
        assertEquals(0, VersionHelper.compare("1.10.0.0.0.0", "1.10"));
        assertEquals(1, VersionHelper.compare("1.10.0.0.0.1", "1.10"));
    }
}
8
dlamblin

C'est presque certainement pas le le plus moyen efficace de le faire, mais étant donné que les chaînes de numéro de version ne comporteront presque toujours que quelques caractères, je ne pense pas que cela vaille la peine d'être optimisé plus loin:

public static int compareVersions(String v1, String v2) {
    String[] components1 = v1.split("\\.");
    String[] components2 = v2.split("\\.");
    int length = Math.min(components1.length, components2.length);
    for(int i = 0; i < length; i++) {
        int result = new Integer(components1[i]).compareTo(Integer.parseInt(components2[i]));
        if(result != 0) {
            return result;
        }
    }
    return Integer.compare(components1.length, components2.length);
}
6
Mike Deck

Divisez-les en tableaux, puis comparez.

// check if two strings are equal. If they are return 0;
String[] a1;

String[] a2;

int i = 0;

while (true) {
    if (i == a1.length && i < a2.length) return -1;
    else if (i < a1.length && i == a2.length) return 1;

    if (a1[i].equals(a2[i]) {
       i++;
       continue;
    }
     return a1[i].compareTo(a2[i];
}
return 0;
0
fastcodejava

Adapté de la réponse d'Alex Gitelman.

int compareVersions( String str1, String str2 ){

    if( str1.equals(str2) ) return 0; // Short circuit when you shoot for efficiency

    String[] vals1 = str1.split("\\.");
    String[] vals2 = str2.split("\\.");

    int i=0;

    // Most efficient way to skip past equal version subparts
    while( i<vals1.length && i<val2.length && vals[i].equals(vals[i]) ) i++;

    // If we didn't reach the end,

    if( i<vals1.length && i<val2.length )
        // have to use integer comparison to avoid the "10"<"1" problem
        return Integer.valueOf(vals1[i]).compareTo( Integer.valueOf(vals2[i]) );

    if( i<vals1.length ){ // end of str2, check if str1 is all 0's
        boolean allZeros = true;
        for( int j = i; allZeros & (j < vals1.length); j++ )
            allZeros &= ( Integer.parseInt( vals1[j] ) == 0 );
        return allZeros ? 0 : -1;
    }

    if( i<vals2.length ){ // end of str1, check if str2 is all 0's
        boolean allZeros = true;
        for( int j = i; allZeros & (j < vals2.length); j++ )
            allZeros &= ( Integer.parseInt( vals2[j] ) == 0 );
        return allZeros ? 0 : 1;
    }

    return 0; // Should never happen (identical strings.)
}

Donc, comme vous pouvez le voir, pas si trivial. Cela échoue également lorsque vous autorisez les 0 à gauche, mais je n'ai jamais vu de version "1.04.5" ou w/e. Vous devrez utiliser la comparaison d'entiers dans la boucle while pour résoudre ce problème. Cela devient encore plus complexe lorsque vous mélangez des lettres avec des chiffres dans les chaînes de version.

0
trutheality

Step1: Utilisez StringTokenizer dans Java avec un point comme délimiteur)

StringTokenizer(String str, String delimiters) ou

Vous pouvez utiliser String.split() et Pattern.split(), fractionner sur un point, puis convertir chaque chaîne en entier à l'aide de Integer.parseInt(String str)

Étape 2: Comparez l'entier de gauche à droite.

0
Farm

Fractionnez la chaîne sur "." ou quel que soit votre délimiteur, puis analysez chacun de ces jetons à la valeur entière et comparez.

int compareStringIntegerValue(String s1, String s2, String delimeter)  
{  
   String[] s1Tokens = s1.split(delimeter);  
   String[] s2Tokens = s2.split(delimeter);  

   int returnValue = 0;
   if(s1Tokens.length > s2Tokens.length)  
   {  
       for(int i = 0; i<s1Tokens.length; i++)  
       {  
          int s1Value = Integer.parseString(s1Tokens[i]);  
          int s2Value = Integer.parseString(s2Tokens[i]);  
          Integer s1Integer = new Integer(s1Value);  
          Integer s2Integer = new Integer(s2Value);  
          returnValue = s1Integer.compareTo(s2Value);
          if( 0 == isEqual)  
           {  
              continue; 
           }  
           return returnValue;  //end execution
        }
           return returnValue;  //values are equal
 } 

Je laisserai l'autre déclaration if comme exercice.

0
Woot4Moo

La comparaison des chaînes de version peut être un gâchis; vous obtenez des réponses inutiles parce que la seule façon de faire fonctionner cela est d'être très précis sur votre convention de commande. J'ai vu une fonction de comparaison de version relativement courte et complète sur un article de blog , avec le code placé dans le domaine public - il n'est pas dans Java mais il devrait être simple de voir comment l'adapter.

0
Prodicus

Je diviserais le problème en deux, en formulant et en comparant. Si vous pouvez supposer que le format est correct, alors comparer uniquement la version des nombres est très simple:

final int versionA = Integer.parseInt( "01.02.00".replaceAll( "\\.", "" ) );
final int versionB = Integer.parseInt( "01.12.00".replaceAll( "\\.", "" ) );

Ensuite, les deux versions peuvent être comparées sous forme d'entiers. Le "gros problème" est donc le format, mais cela peut avoir de nombreuses règles. Dans mon cas, je viens de compléter au moins deux paires de chiffres, donc le format est toujours "99,99,99", puis je fais la conversion ci-dessus; donc dans mon cas, la logique du programme est dans le formatage, et non dans la comparaison des versions. Maintenant, si vous faites quelque chose de très spécifique et que vous pouvez faire confiance à l'origine de la chaîne de version, peut-être pouvez-vous simplement vérifier la longueur de la chaîne de version et ensuite faire la conversion int ... mais je pense que c'est une meilleure pratique de assurez-vous que le format est comme prévu.

0
Patricio Téllez