62

Possible Duplicate:
How do you compare two version Strings in Java?

I've 2 strings which contains version information as shown below:

str1 = "1.2"
str2 = "1.1.2"

Now, can any one tell me the efficient way to compare these versions inside strings in Java & return 0 , if they're equal, -1, if str1 < str2 & 1 if str1>str2.

Community
  • 1
  • 1
Mike
  • 6,732
  • 24
  • 61
  • 81
  • 2
    how does 1.2.0 compare to 1.2 ? – Thilo Jul 15 '11 at 02:01
  • Also, how should 1.12 compare to 1.2 (i.e. is that one point twelve compared to one point two, or should the comparison just look at the .1 substring vs. the .2 substring)? – GreenMatt Jul 15 '11 at 02:06
  • 5
    Please see http://stackoverflow.com/questions/198431/how-do-you-compare-two-version-strings-in-java – g051051 Jul 15 '11 at 02:10
  • Can I use string's compareto method itself? This was also an interview question. What's the expected answer with efficiency? – Mike Jul 15 '11 at 02:19
  • 1
    1.2.0 = 1.2 & 1.12 > 1.2 – Mike Jul 15 '11 at 02:26
  • Check my solution http://stackoverflow.com/questions/198431/how-do-you-compare-two-version-strings-in-java/36987530#36987530 – Prateek Mishra May 02 '16 at 16:51

10 Answers10

151

Requires commons-lang3-3.8.1.jar for string operations.

/**
 * 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;
}    
Kumar
  • 75
  • 13
Alex Gitelman
  • 23,471
  • 7
  • 49
  • 48
  • Don't you need to use `Integer` comparison in the (first) return statement? – trutheality Jul 15 '11 at 02:22
  • @trutheality - nope - the strings are identical if and only if they are the same integer, unless leading zeroes are allowed or something odd like that. – Ed Staub Jul 15 '11 at 02:29
  • @Ed Staub but `"10" – trutheality Jul 15 '11 at 02:45
  • What I want is 1.2.0 = 1.2 & 1.12 > 1.2 The above program does not work for 1.12 > 1.2. Also, this was an interview question. I'm not sure what the interviewer was looking for... – Mike Jul 15 '11 at 02:53
  • Last comparison needs `Integer()`. I corrected the answer. – Alex Gitelman Jul 15 '11 at 03:04
  • Also adjusted to return -1,0 or 1; – Alex Gitelman Jul 15 '11 at 03:08
  • @Alex: It still does not work for 1.3 < 1.3.1. 1.3 is less than 1.3.1, right? Also, 1.2.2.1 is greater than 1.2 but it gives wrong results – Mike Jul 15 '11 at 03:10
  • It should. First loop will stop at 1.3 and then the string that is longer will win. So 1.3.1 is > 1.3. Of course you need to accomodate case 1.3 vs 1.3.0 separately. I'll try it. – Alex Gitelman Jul 15 '11 at 03:14
  • Ok. I tested it and fixed it. My last two statements were incorrect. Now I tried it for quite few cases and it works. – Alex Gitelman Jul 15 '11 at 03:33
  • 6
    It will become more interesting when it comes to 1.3a.1 verus 1.3b :D and 1.3-SNAPSHOT ;D – Angel O'Sphere Jul 15 '11 at 14:30
  • @Angel and we can always use code names for versions, so only human can compare them :-) – Alex Gitelman Jul 15 '11 at 14:47
  • I edited it to use ```Integer.valueOf``` snce we are aiming for performance, and version numbers tend to be low and will hit the cache instead of creating a new object – Johnco Feb 16 '13 at 18:11
  • 1
    Could someone please explain the last part. Why are we comparing vals1 and vals2 lengths? Thanks – whiteElephant Sep 26 '13 at 23:44
  • 1
    @whiteElephant It may be easier to understand if you put last return in an `else` clause. This else is for the case when the strings are the same or when one string is a subset of the other. For example, "1.2" = "1.2" or "1.2" < "1.2.3". In this case, you can compare the lengths based on the assumption that the longer string is a higher version. However, this breaks if you expect "1.2" to be equal to "1.2.0". Otherwise, it's a great and concise solution. – Lucas Apr 10 '14 at 16:31
  • @Lucas You assumption is wrong. Example: "1.3.0" > "1.2.12" – maff91 Jan 27 '16 at 15:18
  • Why does "1.1.1" = "0.0.0"? – behelit Sep 27 '16 at 06:59
  • @behelit They are not equal. See https://ideone.com/jLXxdm – Alex Gitelman Oct 11 '16 at 19:13
  • 3
    Please be aware that Integer.valueOf() might cast a NumberFormatException if the version string is not properly formatted. – Incinerator Mar 16 '17 at 09:54
  • We need to add a `try-catch` block in case of number format exception occurs. – Mustafa Alp Oct 05 '18 at 11:20
15

As others have pointed out, String.split() is a very easy way to do the comparison you want, and Mike Deck makes the excellent point that with such (likely) short strings, it probably won't matter much, but what the hey! If you want to make the comparison without manually parsing the string, and have the option of quitting early, you could try the java.util.Scanner class.

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
}
Eric
  • 500
  • 4
  • 14
10

This is almost certainly not the most efficient way to do it, but given that version number strings will almost always be only a few characters long I don't think it's worth optimizing further:

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);
}
Mike Deck
  • 17,069
  • 15
  • 62
  • 89
8

I was looking to do this myself and I see three different approaches to doing this, and so far pretty much everyone is splitting the version strings. I do not see doing that as being efficient, though code size wise it reads well and looks good.

Approaches:

  1. Assume an upper limit to the number of sections (ordinals) in a version string as well as a limit to the value represented there. Often 4 dots max, and 999 maximum for any ordinal. You can see where this is going, and it's going towards transforming the version to fit into a string like: "1.0" => "001000000000" with string format or some other way to pad each ordinal. Then do a string compare.
  2. Split the strings on the ordinal separator ('.') and iterate over them and compare a parsed version. This is the approach demonstrated well by Alex Gitelman.
  3. Comparing the ordinals as you parse them out of the version strings in question. If all strings were really just pointers to arrays of characters as in C then this would be the clear approach (where you'd replace a '.' with a null terminator as it's found and move some 2 or 4 pointers around.

Thoughts on the three approaches:

  1. There was a blog post linked that showed how to go with 1. The limitations are in version string length, number of sections and maximum value of the section. I don't think it's crazy to have such a string that breaks 10,000 at one point. Additionally most implementations still end up splitting the string.
  2. Splitting the strings in advance is clear to read and think about, but we are going through each string about twice to do this. I'd like to compare how it times with the next approach.
  3. Comparing the string as you split it give you the advantage of being able to stop splitting very early in a comparison of: "2.1001.100101.9999998" to "1.0.0.0.0.0.1.0.0.0.1". If this were C and not Java the advantages could go on to limit the amount of memory allocated for new strings for each section of each version, but it is not.

I didn't see anyone giving an example of this third approach, so I'd like to add it here as an answer going for efficiency.

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

Unit test demonstrating expected output.

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"));
    }
}
Johno Crawford
  • 326
  • 3
  • 10
dlamblin
  • 40,676
  • 19
  • 92
  • 127
0

Split the String on "." or whatever your delimeter will be, then parse each of those tokens to the Integer value and compare.

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
 } 

I will leave the other if statement as an exercise.

Woot4Moo
  • 22,887
  • 13
  • 86
  • 143
0

Comparing version strings can be a mess; you're getting unhelpful answers because the only way to make this work is to be very specific about what your ordering convention is. I've seen one relatively short and complete version comparison function on a blog post, with the code placed in the public domain- it isn't in Java but it should be simple to see how to adapt this.

Prodicus
  • 447
  • 2
  • 7
0

Adapted from Alex Gitelman's answer.

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.)
}

So as you can see, not so trivial. Also this fails when you allow leading 0's, but I've never seen a version "1.04.5" or w/e. You would need to use integer comparison in the while loop to fix that. This gets even more complex when you mix letters with numbers in the version strings.

trutheality
  • 21,548
  • 6
  • 47
  • 65
0

Step1 : Use StringTokenizer in java with dot as delimiter

StringTokenizer(String str, String delimiters) or

You can use String.split() and Pattern.split(), split on dot and then convert each String to Integer using Integer.parseInt(String str)

Step 2: Compare integer from left to right.

Tamil Selvan C
  • 18,342
  • 12
  • 44
  • 63
Farm
  • 3,189
  • 2
  • 27
  • 32
0

Split them into arrays and then compare.

// 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;
fastcodejava
  • 35,219
  • 24
  • 124
  • 181
0

I would divide the problem in two, formating and comparing. If you can assume that the format is correct, then comparing only numbers version is very simple:

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

Then both versions can be compared as integers. So the "big problem" is the format, but that can have many rules. In my case i just complete a minimum of two pair of digits, so the format is "99.99.99" always, and then i do the above conversion; so in my case the program logic is in the formatting, and not in the version comparison. Now, if you are doing something very specific and maybe you can trust the origin of the version string, maybe you just can check the length of the version string and then just do the int conversion... but i think it's a best practice to make sure the format is as expected.

  • If you assume that the format is regular, then a simple lexicographic comparison will do and you don't need to split. In practice, you will want to split though: people invariably start with 1.0 (because nobody is thinking about version number comparison at that time), and then they get to 1.10. Or they start with 1.05 because two digits, man, and then reach 1.99 - or maybe they reach 9.x and want to upgrade to 10. – toolforger Nov 21 '19 at 17:32