сравнение номеров версий в c

Я вижу много ответов на эту проблему на других языках, но я пытаюсь найти способ сравнить 2 номера версии, указанные в виде строк. Например

str1 = "141.1.23"
str2 = "141.1.22"

Я пытаюсь найти способ сравнить целочисленные значения в строках, чтобы увидеть, какое из них больше. (В этом случае str1 будет больше). Я думал об использовании комбинации с atoi и strtok, но я знаю, что не смогу токенизировать 2 строки одновременно. Любой совет?


person bardockyo    schedule 24.02.2013    source источник
comment
Для этого примера подойдет strcmp :-)   -  person cnicutar    schedule 25.02.2013
comment
Ммм, stcmp, вероятно, поставит Linux 2.14 раньше Linux 2.4.   -  person David Grayson    schedule 25.02.2013
comment
@ Дэвид Грейсон: strcmp возвращает значение больше нуля, когда первый несоответствующий символ имеет большее значение в str1, чем в str2, и сравнение останавливается на первом нуле, поэтому оно все равно будет работать, даже для 2.1 и 2.14. 2.1 и 2.10 могут быть двусмысленными, но тогда это все равно. Из примеров ни в коем случае не ясно, что представляет собой действительную строку версии в этом случае. Он будет работать до тех пор, пока все группы цифр, кроме последней, имеют одинаковую длину, поэтому вам не придется сравнивать цифру с точкой.   -  person Clifford    schedule 25.02.2013
comment
Извините, я понял, что как только набрал, удалил комментарий :).   -  person bardockyo    schedule 25.02.2013
comment
Версия этого вопроса на C ++ для заинтересованных: Как сравнить номера версий в C ++   -  person hippietrail    schedule 19.01.2016


Ответы (6)


Я знаю, что не смогу разметить сразу две строки.

К счастью, вам не нужно: создавать функцию, которая принимает строку и анализирует ее на три целых числа, используя 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 значения и сравнить их бок о бок.

P.S. Если вы примете соглашение, согласно которому числа всегда дополняются ведущими нулями до определенной длины, т.е. убедитесь, что вы пишете "141.1.03", а не "141.1.3", вы можете заменить целочисленное сравнение на лексикографическое.

person Sergey Kalinichenko    schedule 24.02.2013
comment
Что касается предложения о фиксированной длине поля, из примера в вопросе не ясно, что это не так. В лучшем случае это плохо определено. - person Clifford; 25.02.2013

Мне действительно интересно, почему люди стремятся к таким сложным решениям, когда в 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

person Mecki    schedule 21.05.2019
comment
Эта реализация великолепна. Должно быть больше голосов. - person Oscar Hierro; 29.11.2019

Следующая процедура сравнивает строки номеров версий, состоящие из подлинных номеров. Преимущество в том, что разделитель не имеет значения; он будет работать, например, с 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')
            ...
person Peter Raynham    schedule 25.02.2013
comment
Для более тщательной проверки ввода целочисленных вредоносных программ: stackoverflow.com/a/12923949/895245 - person Ciro Santilli 新疆再教育营六四事件ۍ 04.05.2016
comment
В вашем втором примере. может быть заменен строкой допустимых разделителей, например.: -, чтобы устранить ограничение, согласно которому разделитель должен быть точкой. - person Viktor S; 13.03.2018

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=cbc06bc486635347e0e2d5e06e06e06e4e5e4e5e5e5e5e5e5e5e5e5

Проверено на Glibc 2.21, Ubuntu 15.10.

filevercmp от гнулиба

Еще одна реализация GNU. Источник: http://git.savannah.gnu.org/cgit/gnulib.git/tree/libfilevercmp.c?id=71be4c87c8267369f40fbfab7523ab9847154c02#n125

Он используется в sort -V Coreutils 8.23, который работает следующим образом: https://stackoverflow.com/a/4024263/895245

person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 03.05.2016

Мы можем использовать strtok, как было предложено. Взгляните на этот код. Чтобы упростить это, я использую вектор в С ++, пожалуйста, используйте другие контейнеры или структуры данных, такие как массив, инициализированный максимальной длиной двух строк, для хранения токенизированных элементов.

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;

}
person Vignesh Goutham    schedule 11.08.2016
comment
Это C ++, а не C. - person Michi; 11.08.2016
comment
Это то, что я указал в заявлении. Я использую только векторы из C ++, вы можете заменить их массивами в C - person Vignesh Goutham; 11.08.2016
comment
Существует аналог этого вопроса на C ++, где этот ответ может быть более ценным: stackoverflow.com/questions/33135019 - person hippietrail; 03.11.2017

Минималистичная версия 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;
            }
        }
    }
}
person shad    schedule 22.12.2016