Frage

Ich habe eine Zeichenfolge, die Ziffern und Buchstaben enthält. Ich mag die Zeichenfolge in zusammenhängende Stücke von Ziffern und zusammenhängenden Abschnitten von Buchstaben trennen.

Betrachten Sie den String "34A312O5M444123A".

Ich würde ausgeben mag: [ "34", "A", "312", "O", "5", "M", "444.123", "A"]

Ich habe Code, der funktioniert und wie folgt aussieht:

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

Ich betrachtete Splitting str zweimal um ein Array zu erhalten enthält alle Zahlen Brocken und ein Array mit den alle Buchstaben Stücke enthält. Dann verschmelzen die Ergebnisse. Ich scheute weg von diesem, wie es würde die Lesbarkeit schaden.

Ich habe absichtlich löse dies mit einem RegexMuster vermieden, wie ich regex Muster zu sein ein Haupthindernis für die Lesbarkeit zu finden.

  • Debuggers nicht behandeln sie gut.
  • Sie unterbrechen den Fluss von jemand liest Quellcode.
  • Überstunden regex des organisch wachsen und werden Monster.
  • Sie sind tief nicht intuitiv.

Meine Fragen sind:

  • Wie kann ich die Lesbarkeit des obigen Codes verbessern?
  • Gibt es einen besseren Weg, dies zu tun? Eine Util-Klasse, die dieses Problem elegant löst.
  • Wenn Sie die Linie zwischen der Verwendung eines REGEX ziehen können und Codierung etwas simpilar zu dem, was ich oben geschrieben habe?
  • Wie erhöhen Sie die Lesbarkeit / Wartbarkeit von regulären Ausdrücken?
War es hilfreich?

Lösung

Seien Sie bereit, reguläre Ausdrücke zu verwenden, wenn es um das Problem in einer Zeile Code gemeint lösen?

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

Mit dem Kommentar die Regex zu erklären, ich denke, das ist besser lesbar als eine der nicht-regex-Lösungen (oder eine der anderen regex Lösungen, für diese Angelegenheit).

Andere Tipps

Für diese besondere Aufgabe würde ich immer einen regulären Ausdruck verwenden, anstatt von Hand schreiben etwas ähnliches. Der Code, den Sie oben gegeben haben, ist, zumindest für mich, weniger lesbar ist als ein einfacher regulärer Ausdruck (was in diesem Fall (\d+|[^\d]+) würde, soweit ich sehen kann).

Sie können wollen reguläre Ausdrücke zu vermeiden, schreiben, ein paar Zeilen nicht überschreiten. Derjenigediejenigedasjenige sein können und sind in der Regel nicht lesbar und schwer zu verstehen, aber so ist der Code, können sie ersetzt werden! Parser ist so gut wie nie hübsch und Sie sind in der Regel besser, die ursprüngliche Grammatik zu lesen als zu versuchen, machen Sinn des erzeugten (oder handgeschriebene) Parsers. Das Gleiche gilt (imho) für reguläre Ausdrücke, die nur eine kurze Beschreibung einer regulären Grammatik sind.

Also, im Allgemeinen würde ich sagen, reguläre Ausdrücke für Code Verbot wie Sie in Ihrer Frage gegeben haben, klingen wie eine furchtbar dumme Idee. Und reguläre Ausdrücke sind nur ein Werkzeug, nicht weniger, nicht mehr. Wenn etwas anderes eine bessere Arbeit der Textanalyse tut (sagen wir, ein echter Parser, einige Teilzeichen Magie, etc.), dann benutze ihn. Aber nicht werfen Möglichkeiten weg, nur weil man mit ihnen unwohl fühlen -. Andere haben weniger Probleme mit ihnen fertig und alle Menschen sind in der Lage zu lernen,

EDIT:. Aktualisiert regex nach Kommentar von mmyers

Für eine Utility-Klasse Besuche java.util.Scanner . Es gibt eine Reihe von Optionen in dort, wie Sie über die Lösung Ihres Problems gehen könnten. Ich habe ein paar Kommentare auf Ihre Fragen.

  

Debuggers nicht behandeln sie (reguläre Ausdrücke) und

Ob eine Regex funktioniert oder nicht, hängt von was in Ihren Daten. Es gibt einige nette Plugins, die Sie verwenden, können Sie bauen einen regulären Ausdruck zu helfen, wie QuickREx für Eclipse, ist ein Debugger tatsächlich helfen Ihnen die richtigen Parser für Ihre Daten schreiben?

  

Sie unterbrechen den Fluss von jemand liest Quellcode.

Ich denke, es hängt davon ab, wie gut Sie mit ihnen sind. Ich persönlich würde lieber eine vernünftige regex lesen als 50 Zeilen String-Parsing-Code, aber vielleicht ist das eine persönliche Sache.

  

Überstunden regex des organisch wachsen und werden Monster.

Ich denke, sie könnten, aber das ist wahrscheinlich ein Problem mit dem Code, der sie in immer unscharfen lebt. Wenn die Komplexität der Quelldaten zu erhöhen, müssen Sie wahrscheinlich auf ein Auge behalten, ob Sie eine ausdrucks Lösung benötigen (vielleicht ein Parser-Generator wie ANTLR)

  

Sie sind tief nicht intuitiv.

Sie sind ein Musterabgleich Sprache. Ich würde sagen, dass sie in diesem Zusammenhang recht intuitiv sind.

  

Wie kann ich die Lesbarkeit des obigen Codes verbessern?

Nicht sicher, abgesehen von der Verwendung einer regex.

  

Gibt es einen besseren Weg, dies zu tun? Eine Util-Klasse, die dieses Problem elegant löst.

Oben erwähnt, java.util.Scanner.

  

Wo ziehen Sie die Grenze zwischen einem regex und Codierung etwas simpilar zu dem, was ich oben geschrieben habe?

Ich persönlich verwende regex für alles relativ einfach.

  

Wie erhöhen Sie die Lesbarkeit / Wartbarkeit von regulären Ausdrücken?

Denken Sie sorgfältig, bevor Sie erstreckt, seien Sie besonders vorsichtig, den Code und die Regex im Detail zu kommentieren, so dass es klar ist, was Sie tun.

würde ich so etwas wie diese (Warnung, nicht getesteten Code) verwenden. Für mich ist das viel besser lesbar als zu versuchen, regexps zu vermeiden. Regexps ist ein großes Werkzeug, wenn sie in richtigen Stelle eingesetzt.

Kommentar Methoden und Beispiele von Ein- und Ausgangswerten in den Kommentaren auch die Bereitstellung hilft.

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

Ich bin nicht allzu verrückt nach mir regex, aber dies scheint wie ein Fall, wo sie wirklich die Dinge vereinfachen werden. Was möchten Sie vielleicht zu tun ist, um sie in die kleinste Methode setzen Sie entwickeln können, nennen Sie es treffend, und dann die Kontrolle aller Code setzen in einem anderen Verfahren.

Zum Beispiel, wenn Sie einen „Grab Block von Zahlen oder Buchstaben“ Methode codieren, würde der Anrufer ein sehr einfacher, straight-forward Loop Druck nur die Ergebnisse jeden Anruf, und die Methode, die Sie wären fordern gut definiert, so würde die Absicht des Regex auch klar sein, wenn man nichts über die Syntax nicht kennt, und das Verfahren begrenzt sein würde, damit die Menschen im Laufe der Zeit nicht wahrscheinlich wären es vermasseln.

Das Problem dabei ist, dass die Regex-Tools sind so einfach und gut angepasst zu diesem Gebrauch, es ist schwer, einen Methodenaufruf für diese zu rechtfertigen.

Da niemand scheint noch richtigen Code geschrieben zu haben, gebe ich ihm einen Schuss.

Zuerst wird die Nicht-regex-Version. Beachten Sie, dass ich die Stringbuilder verwenden, zum Akkumulieren je nachdem, welche Art von Charakter zuletzt gesehen wurde (Ziffer oder nicht-stellig). Wenn die Zustandsänderungen, Dump ich seinen Inhalt in die Liste und einen neuen String starten. Auf diese Weise aufeinanderfolgende Nicht-Ziffern gruppiert werden wie aufeinanderfolgende Ziffern sind.

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

Nun ist die regex-Version. Dies ist im Grunde der gleiche Code, der von Juha S. geschrieben wurde, aber die regex tatsächlich funktioniert.

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

Ein Weg, ich versuche, meine regulären Ausdrücke zu halten lesbar ist ihr Name. Ich denke, DIGIT_OR_NONDIGIT_STRING ziemlich gut vermittelt, was ich (der Programmierer) denke, es tut, und sollten Tests sicherstellen, dass es wirklich tut, was es soll tun.

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

druckt:

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

Awww, jemand hat mich zum Code. Ich denke, die regex-Version ist leichter zu lesen / aufrechtzuerhalten. Beachten Sie auch, den Unterschied in der Leistung zwischen den zwei Implementierungen vs dem erwarteten Ausgang ...

Ausgang:

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]

vergleichen:

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

}

könnten Sie diese Klasse benutzen, um Ihre Schleife zu vereinfachen:

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

Jetzt können Sie diese neu schreiben:

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

mit:

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

my 2 cents

BTW ist diese Klasse auch wiederverwendbar in anderem Kontext.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top