【问题标题】:comparing version numbers in c比较c中的版本号
【发布时间】:2013-02-24 21:46:14
【问题描述】:

我在其他语言中看到了很多针对此问题的答案,但我正在尝试找到一种方法来比较以字符串形式给出的 2 个版本号。例如

str1 = "141.1.23"
str2 = "141.1.22"

我试图找到一种方法来比较字符串中的整数值,看看哪个更大。 (在这种情况下 str1 会更大)。我曾考虑过使用 atoi 和 strtok 的组合,但我知道我不能一次标记 2 个字符串。有什么建议吗?

【问题讨论】:

  • 对于这个例子strcmp 会做:-)
  • 嗯,stcmp 可能会将 Linux 2.14 放在 Linux 2.4 之前。
  • @David Grayson:当第一个不匹配的字符在str1中的值大于在str2中的值时,strcmp返回一个大于零的值,并且比较在第一个nul处停止,所以它仍然会工作,即使是 2.1 和 2.14。 2.1 和 2.10 可能有歧义,但无论如何都是这样。在这种情况下,从示例中无法清楚地看出什么构成了有效的版本字符串。只要除最后一个之外的所有数字组长度相同,它就可以工作,因此您最终不会将数字与点进行比较。
  • 对不起,我一输入就意识到,所以删除了评论:)。
  • 这个问题的 C++ 版本供有兴趣的人参考:How to compare version numbers in C++

标签: c string-comparison version-numbering


【解决方案1】:

我真的很奇怪,当 C 语言中有 sscanf 时,为什么人们会努力寻求如此复杂的解决方案。下面是一个非常简单的解决方案,适用于 99% 的所有用例:

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

在这里,试一试: https://ideone.com/bxCjsb

【讨论】:

  • 这个实现很棒。应该得到更多的投票。
【解决方案2】:

我知道我不能同时标记 2 个字符串。

幸运的是,您不需要:创建一个接受字符串的函数,并使用 strtok_r 将其解析为三个整数(使用可重入版本,它更安全)。

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

现在您可以调用parse_ver 两次,获取两个version_t 值,然后并排比较它们。

附:如果您采用约定始终将前导零填充到特定长度的数字,即确保您编写 "141.1.03" 而不是 "141.1.3",则可以将整数比较替换为字典序。

【讨论】:

  • 关于固定字段长度建议,从问题中的示例中不清楚情况并非如此。充其量是指定得很差。
【解决方案3】:

以下例程比较由真实数字组成的版本号字符串。优点是分隔符无关紧要;例如,它将与 141.01.03、141:1:3 甚至 141A1P3 一起使用。它还处理不匹配的尾部,以便 141.1.3 将位于 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;
}

strtouls 替换为strcspns 和strncmp,您可以使用它来比较非数字版本的“数字”——但分隔符必须是点。例如,141.3A.1 排在 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')
            ...

【讨论】:

  • 更彻底的恶意整数输入检查:stackoverflow.com/a/12923949/895245
  • 在你的第二个例子中“。”可以替换为一串有效的分隔符,如“.:-”,以消除分隔符必须是点的限制。
【解决方案4】:

strverscmp glibc 扩展

例子:

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

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

在 Glibc 2.21、Ubuntu 15.10 上测试。

filevercmp 来自 gnulib

又一个 GNU 实现。来源:http://git.savannah.gnu.org/cgit/gnulib.git/tree/libfilevercmp.c?id=71be4c87c8267369f40fbfab7523ab9847154c02#n125

在 Coreutils 8.23 的 sort -V 中使用,其工作方式如下:https://stackoverflow.com/a/4024263/895245

【讨论】:

    【解决方案5】:

    我们可以按照建议使用 strtok。看看这段代码。为了简化它,我在 c++ 中使用向量,请使用其他容器或数据结构,如初始化为两个字符串的最大长度的数组来保存标记化的元素。

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

    【讨论】:

    • 这个答案是 C++ 而不是 C
    • 这就是我在声明中所指出的。我只使用 C++ 中的向量,你可以用 C 中的数组替换它
    • 这个问题有一个 C++ 对应物,这个答案可能更有价值:stackoverflow.com/questions/33135019
    【解决方案6】:

    仅标记第一个不匹配组件的极简 C 版本。使用 strchr() 和 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;
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-15
      • 1970-01-01
      • 2022-10-24
      • 2013-09-06
      • 2023-03-20
      • 2021-09-18
      • 2016-07-23
      相关资源
      最近更新 更多