Эффективный способ сравнения строк версии в Java [дубликат]


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

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

str1 = "1.2"
str2 = "1.1.2"

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

10 51

10 ответов:

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

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

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
}

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

подходы:

  1. предположим верхний предел количества разделов (ординалов) в строке версии, а также ограничение на значение, представленное там. Часто 4 точки максимум, и 999 максимум для любого порядкового номера. Вы можно увидеть, где это происходит, и это идет к преобразованию версии, чтобы вписаться в строку, такую как: "1.0" => "001000000000" со строковым форматом или каким-либо другим способом заполнить каждый порядковый номер. Затем выполните сравнение строк.
  2. разделить строки на порядковый разделитель ('.') и перебирать их и сравнивать разобранную версию. Именно такой подход хорошо продемонстрировал Алексей Гительман.
  3. сравнение ординалов, когда вы разбираете их из рассматриваемых строк версии. Если все строки были действительно просто указатели на массивы символов, как в C, тогда это был бы четкий подход (где вы бы заменили a '.'с нулевым Терминатором, как он найден и переместить некоторые 2 или 4 указателя вокруг.

мысли о трех подходов:

  1. было сообщение в блоге связано это показало, как идти с 1. Ограничения заключаются в длине строки версии, количестве разделов и максимальном значении раздела. Я не думаю, что это ненормально иметь такие строка, которая ломает 10,000 в одной точке. Кроме того, большинство реализаций по-прежнему заканчиваются разделением строки.
  2. разделение строк заранее ясно читать и думать, но мы проходим через каждую строку примерно в два раза для этого. Я хотел бы сравнить, как это происходит со следующим подходом.
  3. сравнение строки при ее разделении дает вам преимущество в том, что вы можете остановить разделение очень рано при сравнении: "2.1001.100101.9999998" to "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;
    }
}

модульный тест, демонстрирующий ожидаемый результат.

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

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

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

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

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 в качестве упражнения.

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

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

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, чтобы исправить это. Это становится еще более сложным, когда вы смешиваете буквы с цифрами в строках версии.

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

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

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

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

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

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

StringTokenizer(String str, String delimiters) или

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

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