Manera eficiente de comparar cadenas de versión en Java

Posible duplicado:
¿Cómo se comparan dos cadenas de versiones en Java?

Tengo 2 cadenas que contienen información de la versión como se muestra a continuación:

str1 = "1.2" str2 = "1.1.2" 

Ahora, ¿alguien me puede decir la forma eficiente de comparar estas versiones dentro de cadenas en Java y devolver 0, si son iguales, -1, si str1 str2?

 /** * Compares two version strings. * * Use this instead of String.compareTo() for a non-lexicographical * comparison that works for version strings. eg "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 // eg "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4" return Integer.signum(vals1.length - vals2.length); } 

Como han señalado otros, String.split () es una forma muy fácil de hacer la comparación que desea, y Mike Deck señala que con esas cadenas cortas (probables) probablemente no importará mucho, pero lo que ¡Oye! Si desea hacer la comparación sin analizar manualmente la cadena, y tiene la opción de abandonar anticipadamente, puede probar la clase java.util.Scanner .

 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 } 

Estaba buscando hacer esto yo mismo y veo tres enfoques diferentes para hacer esto, y hasta ahora prácticamente todo el mundo está dividiendo las cadenas de versión. No veo que sea eficiente, aunque el tamaño del código se lee bien y se ve bien.

Enfoques:

  1. Suponga un límite superior para el número de secciones (ordinales) en una cadena de versión, así como un límite para el valor representado allí. A menudo 4 puntos máximo y 999 máximo para cualquier ordinal. Puedes ver a dónde va esto y va a transformar la versión para que encaje en una cadena como: “1.0” => “001000000000” con formato de cadena u otra forma de rellenar cada ordinal. Luego haz una comparación de cuerdas.
  2. Divida las cadenas en el separador ordinal (‘.’) E itere sobre ellas y compare una versión analizada. Este es el enfoque demostrado bien por Alex Gitelman.
  3. Comparando los ordinales a medida que los analiza fuera de las cadenas de versión en cuestión. Si todas las cadenas fueran realmente solo punteros a matrices de caracteres como en C, este sería el enfoque claro (donde reemplazarías un ‘.’ Con un terminador nulo tal como se encuentra y moverías unos 2 o 4 punteros).

Pensamientos sobre los tres enfoques:

  1. Había una publicación de blog vinculada que mostraba cómo ir con 1. Las limitaciones están en la longitud de la cadena de versión, el número de secciones y el valor máximo de la sección. No creo que sea una locura tener una cuerda que rompa 10.000 en un punto. Además, la mayoría de las implementaciones terminan dividiendo la cadena.
  2. Es claro leer y pensar para separar las cuerdas de antemano, pero estamos examinando cada cuerda aproximadamente dos veces para hacer esto. Me gustaría comparar cómo funciona con el próximo enfoque.
  3. La comparación de la cadena a medida que la divide le da la ventaja de poder detener la división muy temprano en una comparación de: “2.1001.100101.9999998” a “1.0.0.0.0.0.1.0.0.0.1”. Si esto fuera C y no Java, las ventajas podrían llegar a limitar la cantidad de memoria asignada para nuevas cadenas para cada sección de cada versión, pero no es así.

No vi a nadie dando un ejemplo de este tercer enfoque, así que me gustaría añadirlo aquí como una respuesta para la eficiencia.

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

Prueba unitaria que demuestra el resultado esperado.

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

Es casi seguro que esta no es la forma más eficiente de hacerlo, pero dado que las cadenas de números de versión casi siempre tendrán solo unos pocos caracteres, no creo que valga la pena optimizarlo aún más:

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

Dividir la cadena en “.” o cualquiera que sea su delímetro, luego analizar cada uno de esos tokens con el valor entero y compararlos.

 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 

Dejaré la otra statement if como un ejercicio.

La comparación de cadenas de versiones puede ser un desastre; Obtendrá respuestas inútiles porque la única manera de hacer que esto funcione es ser muy específico acerca de cuál es su convención de pedidos. He visto una función de comparación de versión relativamente corta y completa en una publicación de blog , con el código colocado en el dominio público; no está en Java, pero debería ser sencillo ver cómo adaptarlo.

Adaptado de la respuesta de Alex Gitelman.

 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 

Como puede ver, no es tan trivial. También esto falla cuando permite 0's principales, pero nunca he visto una versión "1.04.5" o w / e. Necesitarás usar la comparación de enteros en el ciclo while para arreglar eso. Esto se vuelve aún más complejo al mezclar letras con números en las cadenas de versión.

Dividirlos en matrices y luego comparar.

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

Yo dividiría el problema en dos, formateando y comparando. Si puede asumir que el formato es correcto, entonces la comparación de la versión de solo números es muy simple:

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

Entonces ambas versiones se pueden comparar como enteros. Entonces, el “gran problema” es el formato, pero eso puede tener muchas reglas. En mi caso, simplemente completo un mínimo de dos pares de dígitos, por lo que el formato es “99.99.99” siempre, y luego hago la conversión anterior; entonces en mi caso la lógica del progtwig está en el formato, y no en la comparación de versión. Ahora, si estás haciendo algo muy específico y quizás puedas confiar en el origen de la cadena de versión, tal vez solo puedas verificar la longitud de la cadena de la versión y luego hacer la conversión int … pero creo que es una buena práctica asegúrese de que el formato sea el esperado.

Paso 1: utiliza StringTokenizer en java con punto como delimitador

StringTokenizer(String str, String delimiters) o

Puede usar String.split() y Pattern.split() , dividir en punto y luego convertir cada String en Integer usando Integer.parseInt(String str)

Paso 2: compara el número entero de izquierda a derecha.