comparing version numbers in c

后端 未结 6 804
误落风尘
误落风尘 2020-12-18 05:15

I am seeing a lot of answers for this problem in other languages but I am trying to find out a way to compare 2 version numbers given as strings. For example



        
相关标签:
6条回答
  • 2020-12-18 05:52

    I really wonder why people strive for such complicated solutions when there is sscanf in C. Here is a very simple solution to that problem that will work for 99% of all use cases:

    int compVersions ( const char * version1, const char * version2 ) {
        unsigned major1 = 0, minor1 = 0, bugfix1 = 0;
        unsigned major2 = 0, minor2 = 0, bugfix2 = 0;
        sscanf(version1, "%u.%u.%u", &major1, &minor1, &bugfix1);
        sscanf(version2, "%u.%u.%u", &major2, &minor2, &bugfix2);
        if (major1 < major2) return -1;
        if (major1 > major2) return 1;
        if (minor1 < minor2) return -1;
        if (minor1 > minor2) return 1;
        if (bugfix1 < bugfix2) return -1;
        if (bugfix1 > bugfix2) return 1;
        return 0;
    }
    

    Here, give it a try: https://ideone.com/bxCjsb

    0 讨论(0)
  • 2020-12-18 05:54

    We can use strtok as suggested. Take a look at this code. To ease it out, im using vector in c++, please use other containers or data structures like array initialized to max of length of the two strings to hold the tokenized elements.

    vector<char*> tokenize(char *s)
    {
        vector<char*> svec;
    
        char *stp = strtok(s,".");
        while(stp != NULL)
        {
                svec.push_back(stp);
                stp = strtok(NULL,".");
        }
        cout << endl;
        return svec;
    
    }
    
    int version_compare(char *s1, char *s2)
    {
        vector<char*> tokens_s1 = tokenize(s1);
        vector<char*> tokens_s2 = tokenize(s2);
    
        int st1, st2, flag, maxf,result;
        st1 = tokens_s1.size();
        st2 = tokens_s2.size();
        flag = st1 < st2 ? st1 : st2;
    
    
        for(int i=0; i < flag ;i++)
        {
    
                int one = *(tokens_s1[i]);
                int two = *(tokens_s2[i]);
                if(one > two)
                         return 1;
                else if(one < two)
                        return 2;
                else
                        result = 0;
    
        }
    }
    
        if((st1 == st2) && (result == 0)) return 0;
        return (st1 > st2 ? 1 : 2);
    
    
    
    }
    
    
    int main()
    {
        char s1[] = "1.2.3.4";
        char s2[] = "2.2.3.3.3";
        int st;
        st = version_compare(s1,s2);
        cout<<st<<endl;
    
    }
    
    0 讨论(0)
  • 2020-12-18 05:57

    The following routine compares version-number strings that are made up of genuine numbers. The advantage is that the delimiter does not matter; it will work with, for example, 141.01.03, 141:1:3, or even 141A1P3. It also handles mismatched tails so that 141.1.3 will come before 141.1.3.1.

    #include <assert.h>
    #include <stdlib.h>
    
    int versionCmp( char *pc1, char *pc2)
    {
        int result = 0;
        /* loop through each level of the version string */
        while (result == 0) {
            /* extract leading version numbers */
            char* tail1;
            char* tail2;
            unsigned long ver1 = strtoul( pc1, &tail1, 10 );
            unsigned long ver2 = strtoul( pc2, &tail2, 10 );
            /* if numbers differ, then set the result */
            if (ver1 < ver2)
                result = -1;
            else if (ver1 > ver2)
                result = +1;
            else {
                /* if numbers are the same, go to next level */
                pc1 = tail1;
                pc2 = tail2;
                /* if we reach the end of both, then they are identical */
                if (*pc1 == '\0' && *pc2 == '\0')
                    break;
                /* if we reach the end of one only, it is the smaller */
                else if (*pc1 == '\0')
                    result = -1;
                else if (*pc2 == '\0')
                    result = +1;
                /*  not at end ... so far they match so keep going */
                else {
                    pc1++;
                    pc2++;
                }
            }
        }
        return result;
    }
    
    int main( void )
    {
        assert(versionCmp("1.2.3" , "1.2.3" ) == 0);
        assert(versionCmp("1.2.3" , "1.2.4" )  < 0);
        assert(versionCmp("1.2.4" , "1.2.3" )  > 0);
        assert(versionCmp("10.2.4", "9.2.3" )  > 0);
        assert(versionCmp("9.2.4",  "10.2.3")  < 0);
        /* Trailing 0 ignored. */
        assert(versionCmp("01", "1") == 0);
        /* Any single space delimiter is OK. */
        assert(versionCmp("1a2", "1b2") == 0);
        return EXIT_SUCCESS;
    }
    

    Replace the strtouls with strcspns and a strncmp, and you can use it to compare non-numeric version "numbers" -- but the delimiter must be a dot. For example, 141.3A.1 sorts before 141.3B.

    ...
    while (result == 0) {
        /* ignore leading zeroes */
        pc1 += strspn( pc1, "0" );
        pc2 += strspn( pc2, "0" );
        /* extract leading version strings */
        int len1 = strcspn( pc1, "." );
        int len2 = strcspn( pc2, "." );
        /* if one is shorter than the other, it is the smaller version */
        result = len1 - len2;
        /* if the same length then compare as strings */
        if (result == 0)
            result = strncmp( pc1, pc2, len1 );
        if (result == 0) {
            pc1 += len1;
            pc2 += len2;
            if (*pc1 == '\0' && *pc == '\0')
                ...
    
    0 讨论(0)
  • 2020-12-18 05:59

    strverscmp glibc extension

    • man strverscmp
    • http://www.gnu.org/software/libc/manual/html_node/String_002fArray-Comparison.html

    Example:

    #define _GNU_SOURCE
    #include <assert.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void) {
        assert(strverscmp("1.2.3" , "1.2.3" ) == 0);
        assert(strverscmp("1.2.3" , "1.2.4" )  < 0);
        assert(strverscmp("1.2.3" , "1.2.2" )  > 0);
        assert(strverscmp("9.2.3" , "10.2.3")  < 0);
        assert(strverscmp("10.2.3", "9.2.3" )  > 0);
    
        /* Delimiers are also compared. */
        assert(strverscmp("1a2", "1b2" ) < 0);
        assert(strverscmp("1b2", "1a2" ) > 0);
    
        /* Leading 0s: number gets treated as 0.X, e.g. 01 means 0.1.
         * Maybe not perfect for version strings, but sane version strings
         * should not have leading 0s. 
         */
        assert(strverscmp("01", "9" ) < 0);
        assert(strverscmp("01", "09") < 0);
        assert(strverscmp("01", "09") < 0);
        assert(strverscmp("09",  "1") < 0);
    
        return EXIT_SUCCESS;
    }
    

    Source: https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strverscmp.c;h=96d4227cd50090f3a7c45e7241d817d34e42f5ce;hb=cbc06bc486635347ee0da51d04a82eedf51602d5#l42

    Tested on Glibc 2.21, Ubuntu 15.10.

    filevercmp from gnulib

    Yet another GNU implementation. Source: http://git.savannah.gnu.org/cgit/gnulib.git/tree/libfilevercmp.c?id=71be4c87c8267369f40fbfab7523ab9847154c02#n125

    It is used in sort -V of Coreutils 8.23, which works like this: https://stackoverflow.com/a/4024263/895245

    0 讨论(0)
  • 2020-12-18 06:07

    A minimalist C version that only tokenizes the first non-matching component. Uses strchr() and strtoul().

    int version_compare(char *s1, char *s2)
    {
        char *delim = ".:-";
        while(1) {
            if (*s1 == *s2)  {
                if (!*s1)
                    return 0;
                s1++; s2++;
            } else if (strchr(delim, *s1) || !*s1) {
                return -1;
            } else if (strchr(delim, *s2) || !*s2) {
                return 1;
            } else {
                int diff;
                char *end1, *end2;
                diff = strtoul(c1, &end1, 10) - strtoul(c2, &end2, 10);
                if (!diff) {
                    c1 += (end1 - c1);
                    c2 += (end2 - c2);
                } else {
                    return diff;
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-18 06:12

    I know I wont be able to tokenize 2 strings at once.

    Fortunately, you do not need to: make a function that takes a string, and parses it for three integer numbers using strtok_r (use a reentrant version, it's a lot safer).

    strunct version_t {
        int major;
        int minor;
        int build;
    };
    
    version_t parse_ver(const char* version_str) {
        version_t res;
        // Use strtok_r to split the string, and atoi to convert tokens to ints
        return res;
    }
    

    Now you can call parse_ver twice, get two version_t values, and compare them side-by-side.

    P.S. If you adopt a convention to always pad the numbers with leading zeros to a specific length, i.e. make sure that you write "141.1.03" and not "141.1.3", you could substitute integer comparison with lexicographic one.

    0 讨论(0)
提交回复
热议问题