Эффективный способ сравнения строк версии в Java

Возможный дубликат:
Как вы сравниваете две строки версии в Java?

У меня есть 2 строки, которые содержат информацию о версии, как показано ниже:

str1 = "1.2"
str2 = "1.1.2"

Теперь, может ли кто-нибудь сказать мне эффективный способ сравнить эти версии внутри строк в Java и вернуть 0, если они равны, -1, если str1 < str2 и 1, если str1 > str2.

Ответ 1

/**
 * 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").
 * 
 * @note It does not work if "1.10" is supposed to be equal to "1.10.0".
 * 
 * @param str1 a string of ordinal numbers separated by decimal points. 
 * @param str2 a string of ordinal numbers separated by decimal points.
 * @return The result is a negative integer if str1 is _numerically_ less than str2. 
 *         The result is a positive integer if str1 is _numerically_ greater than str2. 
 *         The result is zero if the strings are _numerically_ equal.
 */
public static int versionCompare(String str1, String str2) {
    String[] vals1 = str1.split("\\.");
    String[] vals2 = str2.split("\\.");
    int i = 0;
    // set index to first non-equal ordinal or length of shortest version string
    while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) {
      i++;
    }
    // compare first non-equal ordinal number
    if (i < vals1.length && i < vals2.length) {
        int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i]));
        return Integer.signum(diff);
    }
    // the strings are equal or one string is a substring of the other
    // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4"
    return Integer.signum(vals1.length - vals2.length);
}

Ответ 2

Как указывали другие, String.split() - это очень простой способ выполнить нужное вам сравнение, и Майк Пак делает замечательную мысль, что с такими (вероятными) короткими строками это, вероятно, не имеет большого значения, но что эй! Если вы хотите провести сравнение без ручного разбора строки и иметь возможность отказаться от раннего, вы можете попробовать java.util.Scanner класс.

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()) return 1; //str1 has an additional lower-level version number
return 0;

Ответ 3

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

Подходы:

  • Предположим, что верхний предел числа секций (ординалов) в строке версии, а также предел представленного там значения. Часто 4 точки макс и максимум 999 для любого ординала. Вы можете видеть, куда это происходит, и он собирается преобразовать версию, чтобы она вписывалась в строку типа "1.0" = > "001000000000" со строковым форматом или каким-либо другим способом для заполнения каждого ординала. Затем сравните строку.
  • Разделите строки на порядковый разделитель ('.') и перейдем к ним и сравните разборную версию. Этот подход хорошо продемонстрировал Алекс Гительман.
  • Сравнение ординалов при их анализе из строк версии, о которых идет речь. Если все строки были просто указателями на массивы символов, как в C, тогда это был бы четкий подход (где вы заменили бы "." Нулевым терминатором, как он нашел, и переместили бы около 2 или 4 указателя.

Мысли о трех подходах:

  • Был сообщение в блоге, в котором показано, как перейти с 1. Ограничения указаны в строке строки строки, количестве разделов и максимальном значение раздела. Я не считаю сумасшедшим иметь такую ​​строку, которая разбивает 10 000 в одной точке. Кроме того, большинство реализаций по-прежнему разбивают строку.
  • Разделение строк заранее ясно, чтобы читать и думать, но мы каждую секунду перебираем каждую строку, чтобы сделать это. Я хотел бы сравнить, как это происходит со следующим подходом.
  • Сравнение строки по мере ее разделения дает вам преимущество в том, что вы можете прекратить расщепление в начале сравнения: "2.1001.100101.9999998" до "1.0.0.0.0.0.1.0.0.0.1". Если бы это были C, а не Java, преимущества могли бы быть ограничены объемом памяти, выделенной для новых строк для каждого раздела каждой версии, но это не так.

Я не видел, чтобы кто-нибудь показывал пример этого третьего подхода, поэтому я хотел бы добавить его здесь, чтобы получить ответ.

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, демонстрирующий ожидаемый результат.

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

Ответ 4

Это почти наверняка не самый эффективный способ сделать это, но учитывая, что строки с номерами версий почти всегда будут содержать только несколько символов, я не думаю, что это стоит оптимизировать дальше:

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

Ответ 5

Разделите строку на "." или независимо от того, какой будет ваш делиметр, затем проанализируйте каждый из этих токенов до значения Integer и сравните.

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
 } 

Я оставлю другое выражение if в качестве упражнения.

Ответ 6

Сравнение строк версии может быть беспорядочным; вы получаете бесполезные ответы, потому что единственный способ сделать эту работу - быть очень конкретным в отношении вашего соглашения о заказе. Я видел одну относительно короткую и полную функцию сравнения версий в сообщении в блоге с кодом, размещенным в общественном достоянии, - он не находится в Java, но должно быть просто понять, как это сделать.

Ответ 7

Адаптировано из ответа Алекса Гительмана.

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

Итак, как вы можете видеть, не так тривиально. Также это терпит неудачу, когда вы разрешаете вести 0, но я никогда не видел версию "1.04.5" или w/e. Вам нужно будет использовать целочисленное сравнение в цикле while, чтобы исправить это. Это становится еще более сложным, когда вы смешиваете буквы с номерами в строках версии.

Ответ 8

Шаг1: Используйте StringTokenizer в java с точкой в ​​качестве разделителя

StringTokenizer(String str, String delimiters) или

Вы можете использовать String.split() и Pattern.split(), разбить на точку и затем преобразовать каждую строку в целое, используя Integer.parseInt(String str)

Шаг 2: Сравните целое число слева направо.

Ответ 9

Разделите их на массивы, а затем сравните.

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

Ответ 10

Я бы разделил проблему на две части, составив и сравнив. Если вы можете предположить, что формат правильный, то сравнение версий только чисел очень просто:

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

Затем обе версии можно сравнить как целые. Таким образом, "большая проблема" - это формат, но это может иметь много правил. В моем случае я просто заполняю минимум две пары цифр, поэтому формат "99.99.99" всегда, а затем я делаю вышеуказанное преобразование; поэтому в моем случае логика программы находится в форматировании, а не в сравнении версий. Теперь, если вы делаете что-то очень конкретное и, возможно, можете доверять началу строки версии, возможно, вы просто можете проверить длину строки версии, а затем просто сделать преобразование int... но я считаю, что это лучшая практика для убедитесь, что формат соответствует ожидаемому.