Modo efficiente per confrontare le stringhe di versione in Java

Possibile duplicato:
Come confronti due stringhe di versione in Java?

Ho 2 stringhe che contiene informazioni sulla versione come mostrato di seguito:

str1 = "1.2" str2 = "1.1.2" 

Ora, qualcuno può dirmi il modo efficace per confrontare queste versioni all’interno di stringhe in Java e restituire 0, se sono uguali, -1, se 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); } 

Come altri hanno sottolineato, String.split () è un modo molto semplice per fare il confronto che vuoi, e Mike Deck fa l’eccellente osservazione che con tali (probabilmente) brevi stringhe, probabilmente non importa molto, ma che cosa Hey! Se si desidera effettuare il confronto senza analizzare manualmente la stringa e avere la possibilità di uscire anticipatamente, è ansible provare la class 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 } 

Stavo cercando di farlo da solo e vedo tre diversi approcci per farlo, e finora praticamente tutti stanno dividendo le stringhe di versione. Non vedo che sia efficiente, anche se la dimensione del codice è buona, legge bene e sembra buona.

approcci:

  1. Assumere un limite superiore al numero di sezioni (numeri ordinali) in una stringa di versione e un limite al valore rappresentato lì. Spesso 4 punti max e 999 massimo per qualsiasi ordinale. Puoi vedere dove sta andando, e sta andando a trasformare la versione in una stringa come: “1.0” => “001000000000” con formato stringa o un altro modo per riempire ogni ordinale. Quindi fai una stringa di confronto.
  2. Dividere le stringhe sul separatore ordinale (‘.’) E scorrere su di esse e confrontare una versione analizzata. Questo è l’approccio dimostrato bene da Alex Gitelman.
  3. Confrontando gli ordinali come li si analizza dalle stringhe di versione in questione. Se tutte le stringhe fossero in realtà solo indicatori di matrici di caratteri come in C, questo sarebbe l’approccio chiaro (in cui sostituiresti un ‘.’ Con un terminatore nullo così come è stato trovato e sposta circa 2 o 4 puntatori in giro.

Pensieri sui tre approcci:

  1. C’era un post di blog collegato che mostrava come andare con 1. Le limitazioni sono nella lunghezza della stringa di versione, il numero di sezioni e il valore massimo della sezione. Non penso che sia pazzesco avere una tale stringa che rompe 10.000 a un certo punto. Inoltre, la maggior parte delle implementazioni finisce per dividere la stringa.
  2. Dividere le corde in anticipo è chiaro da leggere e riflettere, ma stiamo esaminando ogni stringa circa due volte per farlo. Mi piacerebbe confrontare come va con il prossimo approccio.
  3. Confrontando la stringa mentre la si divide, si ha il vantaggio di poter interrompere la divisione molto presto in un confronto di: “2.1001.100101.9999998” su “1.0.0.0.0.0.1.0.0.0.1”. Se questo fosse C e non Java, i vantaggi potrebbero andare a limitare la quantità di memoria allocata per nuove stringhe per ogni sezione di ogni versione, ma non lo è.

Non ho visto nessuno dare un esempio di questo terzo approccio, quindi mi piacerebbe aggiungerlo qui come risposta per efficienza.

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

Test unitario che dimostra l'output atteso.

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

Questo non è quasi certamente il modo più efficace per farlo, ma dato che le stringhe del numero di versione saranno quasi sempre solo pochi caratteri non penso che valga la pena di ottimizzare ulteriormente:

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

Dividere la stringa su “.” o qualunque sia il tuo delimitatore, quindi analizza ciascuno di quei token al valore Integer e confronta.

 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 

Lascerò l'altra se l'affermazione come esercizio.

Confrontare le stringhe di versione può essere un disastro; stai ricevendo risposte inutili perché l’unico modo per farlo funzionare è essere molto specifici su quale sia la tua convenzione di ordinazione. Ho visto una funzione di confronto delle versioni relativamente breve e completa su un post del blog , con il codice inserito nel pubblico dominio – non è in Java, ma dovrebbe essere semplice vedere come adattarlo.

Adattato dalla risposta di 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 

Quindi, come puoi vedere, non così banale. Anche questo fallisce quando si consente di portare gli 0, ma non ho mai visto una versione "1.04.5" o w / e. Dovresti utilizzare il confronto tra interi nel ciclo while per risolvere il problema. Questo diventa ancora più complesso quando mischiate lettere con numeri nelle stringhe di versione.

Dividili in array e poi confronta.

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

Vorrei dividere il problema in due, formando e confrontando. Se si può presumere che il formato sia corretto, la semplice comparazione dei numeri è molto semplice:

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

Quindi entrambe le versioni possono essere confrontate come numeri interi. Quindi il “grande problema” è il formato, ma può avere molte regole. Nel mio caso ho appena completato un minimo di due coppie di cifre, quindi il formato è sempre “99.99.99”, quindi eseguo la conversione sopra; quindi nel mio caso la logica del programma è nella formattazione e non nel confronto delle versioni. Ora, se stai facendo qualcosa di molto specifico e magari ti puoi fidare dell’origine della stringa di versione, forse puoi semplicemente controllare la lunghezza della stringa di versione e poi fare la conversione int … ma penso che sia una buona pratica assicurati che il formato sia come previsto.

Step1: Usa StringTokenizer in java con punto come delimitatore

StringTokenizer(String str, String delimiters) o

È ansible utilizzare String.split() e Pattern.split() , dividere il punto e quindi convertire ogni stringa in intero utilizzando Integer.parseInt(String str)

Passaggio 2: confronta il numero intero da sinistra a destra.