Domanda

Ho una stringa che contiene cifre e lettere. Vorrei dividere la stringa in blocchi contigui di cifre e blocchi contigui di lettere.

Si consideri la stringa "34A312O5M444123A".

Vorrei uscita: [ "34", "A", "312", "O", "5", "M", "444123", "A"]

Ho codice che funziona e si presenta come:

List<String> digitsAsElements(String str){
  StringBuilder digitCollector = new StringBuilder();

  List<String> output = new ArrayList<String>();

  for (int i = 0; i < str.length(); i++){
    char cChar = str.charAt(i);

    if (Character.isDigit(cChar))
       digitCollector.append(cChar);
    else{
      output.add(digitCollector.toString());
      output.add(""+cChar);

      digitCollector = new StringBuilder();
    }         
  }

  return output;
}

Ho considerato la scissione str due volte per ottenere un array contenente tutti i numeri di pezzi e un array contenente i pezzi tutte le lettere. Poi fusione dei risultati. Ho evitato di questo in quanto avrebbe danneggiato la leggibilità.

Ho volutamente evitato di risolvere questo con un modello di espressione regolare come trovo i modelli regex di essere un ostacolo per la leggibilità.

  • I debugger non gestisce bene.
  • Si interrompono il flusso di qualcuno che il codice sorgente di lettura.
  • Overtime regex di crescere organicamente e diventare mostri.
  • Sono profondamente non intuitivo.

Le mie domande sono:

  • Come potrei migliorare la leggibilità del codice di cui sopra?
  • C'è un modo migliore per fare questo? Una classe Util che risolve questo problema con eleganza.
  • Dove è il confine tra l'utilizzo di una regex e codifica qualcosa simpilar a quello che ho scritto sopra?
  • Come si fa a aumentare la leggibilità / manutenzione di espressioni regolari?
È stato utile?

Soluzione

Vuoi essere disposti a usare espressioni regolari se significava risolvere il problema in una riga di codice?

// Split at any position that's either:
// preceded by a digit and followed by a non-digit, or
// preceded by a non-digit and followed by a digit.
String[] parts = str.split("(?<=\\d)(?=\\D)|(?<=\\D)(?=\\d)");

Con il commento di spiegare l'espressione regolare, penso che sia più leggibile rispetto a qualsiasi delle soluzioni non-regex (o una qualsiasi delle altre soluzioni regex, è per questo).

Altri suggerimenti

Per questo compito particolare mi piacerebbe usare sempre un regex invece di qualcosa di scrittura a mano simile. Il codice che hai dato sopra è, almeno per me, meno leggibile di una semplice espressione regolare (che sarebbe (\d+|[^\d]+) in questo caso, per quanto posso vedere).

Si consiglia di evitare di scrivere espressioni regolari che superano poche righe. Quelli possono essere e di solito sono illeggibili e difficile da comprendere, ma così è il codice che possono essere sostituiti con! Parser sono quasi mai abbastanza e si è di solito meglio leggere la grammatica originale che cercare di fare senso del parser generato (oa mano). Lo stesso vale (secondo me) per il regex che sono solo una breve descrizione di una grammatica regolare.

Quindi, in generale, direi che vieta espressioni regolari a favore di codice come hai dato nella tua domanda suona come un terribilmente stupida idea. E le espressioni regolari sono solo uno strumento, niente di meno, niente di più. Se qualcosa fa un lavoro migliore di analisi del testo (ad esempio, un vero e proprio parser, qualche magia sottostringa, ecc), quindi utilizzarlo. Ma non buttare via le possibilità solo perché si sente a disagio con loro -. Altri possono avere meno problemi affrontando con loro e tutte le persone sono in grado di imparare

EDIT:. Aggiornato regex dopo commento di mmyers

Per una classe di utilità, controllare java.util.Scanner . Ci sono una serie di opzioni in là su come si potrebbe fare per risolvere il tuo problema. Ho un paio di commenti sulle vostre domande.

  

I debugger non li (espressioni regolari) non gestiscono bene

Sia che funziona un regex o meno dipende da che cosa è nei dati. Ci sono alcuni plugin belle si possono usare per aiutare a costruire un'espressione regolare, come QuickREx per Eclipse, fa un debugger effettivamente aiutare a scrivere il parser giusto per i vostri dati?

  

Si interrompono il flusso di qualcuno che il codice sorgente di lettura.

Credo che dipende da come si sta comodi con loro. Personalmente, preferirei leggere una regex ragionevole di 50 più righe di codice stringa di analisi, ma forse questa è una cosa personale.

  

Gli straordinari regex di crescere organicamente e diventare mostri.

Credo che potrebbe, ma questo è probabilmente un problema con il codice in cui vivono diventare unfocussed. Se la complessità dei dati di origine è in aumento, probabilmente è necessario tenere d'occhio se avete bisogno di una soluzione più espressiva (forse un generatore di parser come ANTLR)

  

Sono profondamente non intuitivo.

Si tratta di un linguaggio di pattern matching. Direi che sono abbastanza intuitiva in quel contesto.

  

Come potrei migliorare la leggibilità del codice di cui sopra?

Non è sicuro, a parte l'uso di un'espressione regolare.

  

C'è un modo migliore per fare questo? Una classe Util che risolve questo problema con eleganza.

Di cui sopra, java.util.Scanner.

  

Dove tracciare la linea tra l'utilizzo di una regex e codifica qualcosa simpilar a quello che ho scritto sopra?

Personalmente io uso espressioni regolari per qualsiasi cosa abbastanza semplice.

  

Come si fa a aumentare la leggibilità / manutenzione di espressioni regolari?

Pensaci bene prima di estendere, fare particolare attenzione per commentare il codice e la regex in dettaglio in modo che sia chiaro quello che stai facendo.

Vorrei usare qualcosa di simile (attenzione, codice non testato). Per me questo è molto più leggibile rispetto cercando di evitare espressioni regolari. Espressioni regolari sono un ottimo strumento quando viene utilizzato in posto giusto.

Nel commentare i metodi e fornendo esempi di valori di input e output nei commenti aiuta anche.

List<String> digitsAsElements(String str){
    Pattern p = Pattern.compile("(\\d+|\\w+)*");
    Matcher m = p.matcher(str);

    List<String> output = new ArrayList<String>();
    for(int i = 1; i <= m.groupCount(); i++) {
       output.add(m.group(i));
    }
    return output;
}

Io non sono eccessivamente pazza di regex me stesso, ma questo mi sembra un caso in cui sarà davvero semplificare le cose. loro che cosa si potrebbe desiderare di fare è mettere nel metodo più piccolo si può concepire, nominarlo giustamente, e poi mettere tutto il codice di controllo in un altro metodo.

Per esempio, se si codificato un "blocco Grab di numeri o lettere" metodo, il chiamante sarebbe una molto semplice, anello straight-forward solo la stampa dei risultati di ogni chiamata, e il metodo si stava chiamando sarebbe ben definito così l'intenzione del regex sarebbe chiaro, anche se non si sa nulla sulla sintassi, e il metodo sarebbe delimitate così la gente non sarebbero suscettibili di letame in su nel corso del tempo.

Il problema di questo è che gli strumenti regex sono così semplici e ben adattate a questo uso che è difficile da giustificare una chiamata di metodo per questo.

Dal momento che nessuno sembra aver postato codice corretto ancora, io darò un colpo.

In primo luogo la versione non-regex. Notare che io uso lo StringBuilder per accumulare qualsiasi tipo di carattere è stato visto l'ultima (cifra o non cifra). Se i cambiamenti di stato, ho discarica il suo contenuto nella lista e iniziare un nuovo StringBuilder. Questo modo consecutivo non cifre sono raggruppati come cifre consecutive sono.

static List<String> digitsAsElements(String str) {
    StringBuilder collector = new StringBuilder();

    List<String> output = new ArrayList<String>();
    boolean lastWasDigit = false;
    for (int i = 0; i < str.length(); i++) {
        char cChar = str.charAt(i);

        boolean isDigit = Character.isDigit(cChar);
        if (isDigit != lastWasDigit) {
            if (collector.length() > 0) {
                output.add(collector.toString());
                collector = new StringBuilder();
            }
            lastWasDigit = isDigit;
        }
        collector.append(cChar);
    }
    if (collector.length() > 0)
        output.add(collector.toString());

    return output;
}

Ora la versione regex. Questo è fondamentalmente lo stesso codice che è stato inviato da Juha S., ma l'espressione regolare funziona realmente.

private static final Pattern DIGIT_OR_NONDIGIT_STRING =
        Pattern.compile("(\\d+|[^\\d]+)");
static List<String> digitsAsElementsR(String str) {
    // Match a consecutive series of digits or non-digits
    final Matcher matcher = DIGIT_OR_NONDIGIT_STRING.matcher(str);
    final List<String> output = new ArrayList<String>();
    while (matcher.find()) {
        output.add(matcher.group());
    }
    return output;
}

Un modo cerco di mantenere il mio regex leggibile è il loro nome. Penso DIGIT_OR_NONDIGIT_STRING esprime abbastanza bene quello che io (il programmatore) credo di sì, e la sperimentazione dovrebbe assicurarsi che lo fa davvero quello che ha significato fare.

public static void main(String[] args) {
    System.out.println(digitsAsElements( "34A312O5MNI444123A"));
    System.out.println(digitsAsElementsR("34A312O5MNI444123A"));
}

stampe:

[34, A, 312, O, 5, MNI, 444123, A]
[34, A, 312, O, 5, MNI, 444123, A]

Awww, qualcuno mi ha battuto al codice. Credo che la versione regex è più facile da leggere / mantenere. Inoltre, si noti la differenza di potenza tra i 2 implementazioni vs l'uscita prevista ...

Output:

digitsAsElements1("34A312O5MNI444123A") = [34, A, 312, O, 5, M, , N, , I, 444123, A]
digitsAsElements2("34A312O5MNI444123A") = [34, A, 312, O, 5, MNI, 444123, A]
Expected: [34, A, 312, O, 5, MN, 444123, A]

Confronto:

DigitsAsElements.java:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DigitsAsElements {

    static List<String> digitsAsElements1(String str){
        StringBuilder digitCollector = new StringBuilder();

        List<String> output = new ArrayList<String>();

        for (int i = 0; i < str.length(); i++){
          char cChar = str.charAt(i);

          if (Character.isDigit(cChar))
             digitCollector.append(cChar);
          else{
            output.add(digitCollector.toString());
            output.add(""+cChar);

            digitCollector = new StringBuilder();
          }         
        }

        return output;
      }

    static List<String> digitsAsElements2(String str){
        // Match a consecutive series of digits or non-digits
        final Pattern pattern = Pattern.compile("(\\d+|\\D+)");
        final Matcher matcher = pattern.matcher(str);

        final List<String> output = new ArrayList<String>();
        while (matcher.find()) {
            output.add(matcher.group());
        }

        return output;
      }

    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("digitsAsElements(\"34A312O5MNI444123A\") = " +
                digitsAsElements1("34A312O5MNI444123A"));
        System.out.println("digitsAsElements2(\"34A312O5MNI444123A\") = " +
                digitsAsElements2("34A312O5MNI444123A"));
        System.out.println("Expected: [" +
                "34, A, 312, O, 5, MN, 444123, A"+"]");
    }

}

è possibile utilizzare questa classe al fine di semplificare il ciclo:

public class StringIterator implements Iterator<Character> {

    private final char[] chars;
    private int i;

    private StringIterator(char[] chars) {
        this.chars = chars;
    }

    public boolean hasNext() {
        return i < chars.length;
    }

    public Character next() {
        return chars[i++];
    }

    public void remove() {
        throw new UnsupportedOperationException("Not supported.");
    }

    public static Iterable<Character> of(String string) {
        final char[] chars = string.toCharArray();

        return new Iterable<Character>() {

            @Override
            public Iterator<Character> iterator() {
                return new StringIterator(chars);
            }
        };
    }
}

Ora si può riscrivere questo:

for (int i = 0; i < str.length(); i++){
    char cChar = str.charAt(i);
    ...
}

con:

for (Character cChar : StringIterator.of(str)) {
    ...
}

i miei 2 centesimi

A proposito di questa classe è anche riutilizzabile in altri contesti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top