2015-06-19 17 views
31

Recentemente ho sfruttato la potenza di un look-ahead espressione regolare per dividere una stringa:Come è Guava Splitter.onPattern (..). Split() diverso da String.split (..)?

"abc8".split("(?=\\d)|\\W") 

Se stampata sulla console questa espressione restituisce:

[abc, 8] 

Molto soddisfatti di questo risultato, Volevo trasferirlo a Guava per ulteriori sviluppi, che assomigliava a questo:

Splitter.onPattern("(?=\\d)|\\W").split("abc8") 

Con mia sorpresa l'output è stato modificato in:

[abc] 

Perché?

+0

A me sembra come un insetto off-by-one per le parti dei personaggi singoli, in combinazione con il tuo delimitatore di lunghezza zero. Per me non sembra funzionare all'inizio della stringa, ma funziona bene nel mezzo. –

+0

Pertinente, ma non una risposta: https://github.com/google/guava/issues/1378. Ciò aiuterebbe, potresti quindi abbinare semplicemente quello che vuoi e mantenere i separatori ... –

risposta

15

aver trovato un bug!

System.out.println(s.split("abc82")); // [abc, 8] 
System.out.println(s.split("abc8")); // [abc] 

questo è il metodo che Splitter utilizza per realtà diviso String s (Splitter.SplittingIterator::computeNext):

@Override 
protected String computeNext() { 
    /* 
    * The returned string will be from the end of the last match to the 
    * beginning of the next one. nextStart is the start position of the 
    * returned substring, while offset is the place to start looking for a 
    * separator. 
    */ 
    int nextStart = offset; 
    while (offset != -1) { 
    int start = nextStart; 
    int end; 

    int separatorPosition = separatorStart(offset); 

    if (separatorPosition == -1) { 
     end = toSplit.length(); 
     offset = -1; 
    } else { 
     end = separatorPosition; 
     offset = separatorEnd(separatorPosition); 
    } 

    if (offset == nextStart) { 
     /* 
     * This occurs when some pattern has an empty match, even if it 
     * doesn't match the empty string -- for example, if it requires 
     * lookahead or the like. The offset must be increased to look for 
     * separators beyond this point, without changing the start position 
     * of the next returned substring -- so nextStart stays the same. 
     */ 
     offset++; 
     if (offset >= toSplit.length()) { 
     offset = -1; 
     } 
     continue; 
    } 

    while (start < end && trimmer.matches(toSplit.charAt(start))) { 
     start++; 
    } 
    while (end > start && trimmer.matches(toSplit.charAt(end - 1))) { 
     end--; 
    } 

    if (omitEmptyStrings && start == end) { 
     // Don't include the (unused) separator in next split string. 
     nextStart = offset; 
     continue; 
    } 

    if (limit == 1) { 
     // The limit has been reached, return the rest of the string as the 
     // final item. This is tested after empty string removal so that 
     // empty strings do not count towards the limit. 
     end = toSplit.length(); 
     offset = -1; 
     // Since we may have changed the end, we need to trim it again. 
     while (end > start && trimmer.matches(toSplit.charAt(end - 1))) { 
     end--; 
     } 
    } else { 
     limit--; 
    } 

    return toSplit.subSequence(start, end).toString(); 
    } 
    return endOfData(); 
} 

L'area di interesse è:

if (offset == nextStart) { 
    /* 
    * This occurs when some pattern has an empty match, even if it 
    * doesn't match the empty string -- for example, if it requires 
    * lookahead or the like. The offset must be increased to look for 
    * separators beyond this point, without changing the start position 
    * of the next returned substring -- so nextStart stays the same. 
    */ 
    offset++; 
    if (offset >= toSplit.length()) { 
    offset = -1; 
    } 
    continue; 
} 

Questa logica funziona alla grande, a meno che il la corrispondenza vuota si verifica alla fine di un String. Se la corrispondenza vuota si verifica alla fine di un String, finirà per ignorare quel personaggio. Cosa questa parte dovrebbe essere simile è (preavviso >= ->>):

if (offset == nextStart) { 
    /* 
    * This occurs when some pattern has an empty match, even if it 
    * doesn't match the empty string -- for example, if it requires 
    * lookahead or the like. The offset must be increased to look for 
    * separators beyond this point, without changing the start position 
    * of the next returned substring -- so nextStart stays the same. 
    */ 
    offset++; 
    if (offset > toSplit.length()) { 
    offset = -1; 
    } 
    continue; 
} 
+0

Pensavo che non fosse consentito avere un pattern che potrebbe corrispondere a una stringa vuota usando lookahead o lookbehind, ma sembra che tu abbia trovato il punto corretto per il bug. – Bubletan

+0

@Bubletan (? = \ D) non corrisponderà alla stringa vuota. Corrisponde a una stringa vuota seguita da un numero, che non è la stessa cosa. – Jeffrey

+1

Sì, questo è quello che ho detto; _an_ stringa vuota. – Bubletan

5

Guava Splitter sembra avere un bug quando un modello corrisponde a una stringa vuota. Se si tenta di creare un Matcher e stampare ciò che corrisponde:

Pattern pattern = Pattern.compile("(?=\\d)|\\W"); 
Matcher matcher = pattern.matcher("abc8"); 
while (matcher.find()) { 
    System.out.println(matcher.start() + "," + matcher.end()); 
} 

Si ottiene l'uscita 3,3 che lo fa apparire come sarebbe abbinare il 8. Pertanto si divide semplicemente lì risultando solo abc.

È possibile utilizzare ad es. Pattern#split(String) che sembra dare l'uscita corretta:

Pattern.compile("(?=\\d)|\\W").split("abc8") 
+0

'" abc8 ".split (" (? = \\ d) | \\ W ")' è una scorciatoia per ' Pattern.compile ("(= \\ d) |? \\ W"). split ("abc8") '. – SqueezyMo

+0

@Jeffrey Sì, ed è per questo che il controllo fallisce. Sembra un bug in 'Splitter'. – Bubletan

+0

@SqueezyMo C'è altro codice in 'String # split', ma piuttosto sì. – Bubletan