Эффективный способ сравнения строк версии в Java [дубликат]
Возможные Дубликаты:
как вы сравниваете две строки версии в Java?
у меня есть 2 строки, которые содержат информацию о версии, как показано ниже:
str1 = "1.2"
str2 = "1.1.2"
теперь, может ли кто-нибудь сказать мне эффективный способ сравнить эти версии внутри строк в Java & return 0 , если они равны, -1, если str1 str2.
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 }
Я хотел сделать это сам, и я вижу три разных подхода к этому, и до сих пор почти все разделяют строки версии. Я не вижу, что это эффективно, хотя размер кода хорошо читается и выглядит хорошо.
подходы:
- предположим верхний предел количества разделов (ординалов) в строке версии, а также ограничение на значение, представленное там. Часто 4 точки максимум, и 999 максимум для любого порядкового номера. Вы можно увидеть, где это происходит, и это идет к преобразованию версии, чтобы вписаться в строку, такую как: "1.0" => "001000000000" со строковым форматом или каким-либо другим способом заполнить каждый порядковый номер. Затем выполните сравнение строк.
- разделить строки на порядковый разделитель ('.') и перебирать их и сравнивать разобранную версию. Именно такой подход хорошо продемонстрировал Алексей Гительман.
- сравнение ординалов, когда вы разбираете их из рассматриваемых строк версии. Если все строки были действительно просто указатели на массивы символов, как в C, тогда это был бы четкий подход (где вы бы заменили a '.'с нулевым Терминатором, как он найден и переместить некоторые 2 или 4 указателя вокруг.
мысли о трех подходов:
- было сообщение в блоге связано это показало, как идти с 1. Ограничения заключаются в длине строки версии, количестве разделов и максимальном значении раздела. Я не думаю, что это ненормально иметь такие строка, которая ломает 10,000 в одной точке. Кроме того, большинство реализаций по-прежнему заканчиваются разделением строки.
- разделение строк заранее ясно читать и думать, но мы проходим через каждую строку примерно в два раза для этого. Я хотел бы сравнить, как это происходит со следующим подходом.
- сравнение строки при ее разделении дает вам преимущество в том, что вы можете остановить разделение очень рано при сравнении: "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 < 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: сравнить целое число слева направо.