Führen Sie eine Phrasenanalyse in Java 8 durch (Teil 2).

Einführung

Ich habe einen einfachen Phrasenanalysator zur Verwendung in meinem eigenen Java-Konsolenprogramm erstellt. Ich habe eine Token-Klasse in Vorheriger Artikel erstellt. Diesmal ist dies der Hauptteil des Phrasenanalysators. Für die Phrasenanalyse ist es einfacher, verschiedene Bibliotheken zu verwenden. Da sie jedoch am Arbeitsplatz verwendet werden, an dem das Herunterladen von externen Websites verboten ist, wird sie nur mit Java8-Funktionen von Grund auf neu erstellt.

22.10.2018 Die Quelle wurde geändert. 2018/10/24 Es wurde ein Fehler behoben, durch den eine Kopie der Analysezielzeichenfolge im Konstruktor gespeichert wurde.

Beim Erstellen verweise ich auf Implementieren einer einfachen Phrasenanalyse in Java.

wichtiger Punkt

Diesmal sind die Quelle und ihre Erklärung lang, siehe Vorheriger Artikel.

Phrasenanalyseklasse

LexicalAnalyzer.java


package console;

public class LexicalAnalyzer {
    public static LexicalAnalyzer create(String target) {
        if ( target == null  || target.trim().isEmpty() ) { target = ""; }
        return new LexicalAnalyzer(target);
    }

    public java.util.List<Token> analyze() {
        char c;
        while ( (c = next()) != '\0' ) {
            if ( isSymbol_1(c) ) {
                tokens_.add( Token.create(c) );
                continue;
            }
            if ( isQuote(c) ) {
                quotedText(c);
                continue;
            }
            text(c);
        }
        return new java.util.ArrayList<>(tokens_);
    }

    // query methods ================================================================================
    public boolean isEmpty() { return tokens_.size() == 0;}

    public boolean isValid() {
        return !isEmpty()  &&  tokens_.stream().noneMatch( e -> e.kind() == Token.Kinds.Unknown );
    }

    // internal methods ======================================================================
    /**Schneiden Sie bis zum nächsten Anführungszeichen als Token-Block aus. */
    private void quotedText(char quote) {
        tokens_.add( Token.create(quote));  // create token of begin quote

        java.lang.StringBuilder builder = new java.lang.StringBuilder();
        char c;
        while ( (c = nextAll()) != '\0'  &&  c != quote) { builder.append(c); }
        if ( builder.length() != 0 ) {
            tokens_.add( Token.create(builder.toString()) );  // append string
        }

        tokens_.add( Token.create(c) );  // append token of end quote
    }

    /**Schneiden Sie Trennzeichen und leere Zeichen als Token-Block aus. */
    private void text(char first) {
        java.lang.StringBuilder builder = new java.lang.StringBuilder();
        builder.append(first);

        char c;
        while ( (c = nextAll()) != '\0'  &&  !isSeparator(c)  &&  !isWhitespace(c) ) { 
            builder.append(c);
        }
        tokens_.add( Token.create(builder.toString()) );

        // append separator token, if not end of text
        if ( isEnd() ) { return; }

        tokens_.add( isWhitespace(c) ? Token.create(' ') : Token.create(c) );
    }

    private char next() {
        skipSpace();
        return nextAll();
    }

    private char nextAll() {
        char c = aChar();
        ++pos_;
        return c;
    }

    private char aChar() { return isEnd() ? '\0' : target_.charAt(pos_); }

    private void skipSpace() {
        while ( !isEnd()  &&  Character.isWhitespace(aChar()) ) { pos_++; }
    }

    private boolean isEnd()                 { return length_ <= pos_; }
    private boolean isSeparator(char c)     { return exists(separators_, c);    }
    private boolean isQuote(char c)         { return exists(quotes_, c);        }
    private boolean isSymbol_1(char c)      { return exists(symbol1_, c);       }
    private boolean isWhitespace(char c)    { return Character.isWhitespace(c); }

    private boolean exists(char[] arr, char c) {
        return java.util.Arrays.binarySearch(arr, c) >= 0;
    }

    private LexicalAnalyzer(String target) {
        target_ = target;
        length_ = target.length();
    }

    // internal fields ======================================================================
    private static final char[] separators_ = { ':', ',', '=', '(', ')', '{', '}' };
    static { Arrays.sort(separators_); }

    private static final char[] quotes_ = { '"', '\'' };
    static { Arrays.sort(quotes_); }

    private static final char[] symbol1_ = {'(', ')', '{', '}', ':', ',', '=', '&' };
    static { Arrays.sort(symbol1_); }

    final String target_;       // analyze target string
    final int    length_;       // length of target string
    int          pos_ = 0;      // "next" analyzing position

    java.util.List<Token> tokens_ = new java.util.ArrayList<>();  // result
}

Kommentar

Fabrikmethode

Ursprünglich möchte ich die zu analysierende Zeichenfolge als Argument obligatorisch machen, aber es gibt keine Möglichkeit, sie dem Aufrufer aufzuzwingen. Setzen Sie daher eine leere Zeichenfolge auf die Zielzeichenfolge. Dies befreit den Anrufer von der Mühe der Nullprüfung und der Ausnahmebehandlung. Die Argumente im Konstruktor müssen übrigens nicht überprüft werden.

** <Ändern> ** Legen Sie beim Festlegen der Zeichenfolge eine Kopie des Arguments fest. Wenn Sie es nicht kopieren, können Sie die Zielzeichenfolge extern ändern.

    public static LexicalAnalyzer create(String target) {
        if ( target == null  || target.trim().isEmpty() ) { target = ""; }
        return new LexicalAnalyzer(target);
    }

    private LexicalAnalyzer(String target) {
        target_ = new String(target);
        length_ = target.length();
    }

    final String target_;       // analyze target string
    final int    length_;       // length of target string

Unterstützungsmethode

Da es sich um einen Phrasenanalysator handelt, extrahiert er die Zielzeichenfolge zeichenweise und verarbeitet sie. Die Grundfunktionen hierfür sind die folgende Methodengruppe.

    private char next() {
        skipSpace();
        return nextAll();
    }

    private char nextAll() {
        char c = aChar();
        ++pos_;
        return c;
    }

    private char aChar() { return isEnd() ? '\0' : target_.charAt(pos_); }

    private void skipSpace() {
        while ( !isEnd()  &&  Character.isWhitespace(aChar()) ) { pos_++; }
    }

    private boolean isEnd() { return length_ <= pos_; }
    private boolean isSeparator(char c)     { return exists(separators_, c);    }
    private boolean isQuote(char c)         { return exists(quotes_, c);        }
    private boolean isSymbol_1(char c)      { return exists(symbol1_, c);       }
    private boolean isWhitespace(char c)    { return Character.isWhitespace(c); }

    private boolean exists(char[] arr, char c) {
        return java.util.Arrays.binarySearch(arr, c) >= 0;
    }

    private static final char[] separators_ = { ',', '=', '(', ')', '{', '}', ':' };
    static { Arrays.sort(separators_); }

    private static final char[] quotes_ = { '"', '\'' };
    static { Arrays.sort(quotes_); }

    private static final char[] symbol1_ = { '(', ')', '{', '}', ':', ',', '=', '&' };
    static { Arrays.sort(symbol1_); }


    int          pos_ = 0;      // "next" analyzing position

Analyseverarbeitung

Führt eine lexikalische Analyse durch und gibt das Ergebnis als Liste zuvor erstellter Token-Objekte zurück. Der Rückgabewert ist eine Kopie der Token-Liste tokens_, die intern gespeichert ist. Da es sich um eine Standardliste handelt, kann der Aufrufer Elemente hinzufügen / entfernen, was sich auf die vom Objekt gehaltenen Token_ auswirkt (die sich genau auf dasselbe Objekt beziehen). Wenn Sie analyse () ein zweites Mal oder später aufrufen, wird eine Kopie der bereits erstellten Token-Liste zurückgegeben. Es wird keine erneute Analyse durchgeführt.

Durch Ändern der Implementierung von next () (Überspringen von Leerzeichen) und Hinzufügen einer Beurteilungsmethode ist diese sauberer als die Steuerung durch die vorherige switch-Anweisung. Die Absicht des Urteils spiegelt sich übrigens im Methodennamen wider, sodass kein Kommentar erforderlich ist. (Ist es nicht?)

    public java.util.List<Token> analyze() {
        char c;
        while ( (c = next()) != '\0' ) {
            if ( isSymbol_1(c) ) {
                tokens_.add( Token.create(c) );
                continue;
            }
            if ( isQuote(c) ) {
                quotedText(c);
                continue;
            }
            text(c);
        }
        return new java.util.ArrayList<>(tokens_);
    }

    java.util.List<Token> tokens_ = new java.util.ArrayList<>();  // result

Zitierte Zeichenfolgenverarbeitung

Machen Sie die Zeichenfolge in Anführungszeichen zu einem einzelnen Token, einschließlich Leerzeichen. Der Leerraum zwischen dem Anführungszeichen und dem ersten Zeichen sowie zwischen dem letzten Zeichen und dem Anführungszeichen wird entfernt, aber der Leerraum zwischen den anderen Zeichenfolgen, einschließlich des Zeilenvorschubs und der Tabulatorzeichen, bleibt erhalten ..

    /**Schneiden Sie bis zum nächsten Anführungszeichen als Token-Block aus. */
    private void quotedText(char quote) {
        tokens_.add( Token.create(quote));  // create token of begin quote

        java.lang.StringBuilder builder = new java.lang.StringBuilder();
        char c;
        while ( (c = nextAll()) != '\0'  &&  c != quote) { builder.append(c); }

        if ( builder.length() != 0 ) {
            tokens_.add( Token.create(builder.toString()) );  // append string
        }

        tokens_.add( Token.create(c) );  // append token of end quote
    }

Verarbeitung von Zeichenfolgen (Bezeichnern)

Schneiden Sie ein Zeichenfolgentoken aus, das nicht in Anführungszeichen steht.

    /**Schneiden Sie Trennzeichen und leere Zeichen als Token-Block aus. */
    private void text(char first) {
        java.lang.StringBuilder builder = new java.lang.StringBuilder();
        builder.append(first);

        char c;
        while ( (c = nextAll()) != '\0'  &&  !isSeparator(c)  &&  !Character.isWhitespace(c) ) {
            builder.append(c);
        }
        tokens_.add( Token.create(builder.toString()) );

        if ( isEnd() ) { return; }

        tokens_.add( isWhitespace(c) ? Token.create(' ') : Token.create(c) );
    }

Statuserfassung

LexicalAnalyzer-Statuserfassungsmethode.

    public boolean isEmpty() { return tokens_.size() == 0;}

    public boolean isValid() {
        return !isEmpty()  &&  tokens_.stream().noneMatch( e -> e.kind() == Token.Kinds.Unknown );
    }

Prüfung

Last but not least finden Sie hier die main () -Methode zum Testen und die Verarbeitungsergebnisse. ~~ Ich kenne JUnit nicht ~~ Das Trennzeichen-Token wird ausgeblendet, um die Anzahl der Zeilen im Verarbeitungsergebnis zu verringern.

    public static void main(String[] args) {
        String s;
        java.util.List<Token> tokens;
        LexicalAnalyzer lex;

        System.out.println("test 1 -----------------------------------------------------------------------------------");
        LexicalAnalyzer.create(null).analyze().stream().filter( e -> e.kind() != Token.Kinds.Separator )
                                                       .forEach( e -> System.out.println(e) );
        System.out.printf("isEmpty()  after analyze  : %s\r\n", lex.isEmpty());

        System.out.println("\r\ntest 2 -------------------------------------------------------------------------------");
        s = "s";
        lex = LexicalAnalyzer.create(s);
        System.out.printf("isEmpty() before analyze() : %s\r\n", lex.isEmpty());
        lex.analyze().stream().filter( e -> e.kind() != Token.Kinds.Separator )
                              .forEach( e -> System.out.println(e) );
        System.out.printf("isEmpty() after analyze()  : %s\r\n", lex.isEmpty());

        System.out.println("test 3 -----------------------------------------------------------------------------------");
        s = "   [some] -c  \" text  document  \", \r\n (sequence1, \"seq 2\r\n  quoted\",seq3 seq4 'seq 5  ')\"ss";
        lex = LexicalAnalyzer.create(s);
        System.out.printf("isValid() before analyze() : %s\r\n", lex.isValid());
        lex.analyze().stream().filter( e -> e.kind() != Token.Kinds.Separator )
                              .forEach( e -> System.out.println(e) );
        System.out.printf("isValid() after  analyze() : %s\r\n", lex.isValid());
    }

Ausführungsergebnis

Test 1 ... Fall, in dem null als Argument übergeben wird

Bei der Factory-Methode wird eine leere Zeichenfolge so festgelegt, dass sie fehlerfrei funktioniert. Selbst wenn analyse () und der nachfolgende stream () ausgeführt werden, ist dies kein Problem. Es wird nichts angezeigt. "isEmpty () ..." ist das Testergebnis der isEmpty () -Methode.

Test 2 ... nur ein Zeichen analysieren

Anstatt die Analyseergebnisse zu überprüfen, überprüfen Sie die Spezifikationen von isEmpty () vor und nach der Ausführung von analyse ().

Test 3 ... Eine solche Zeichenfolge analysieren

Da "seq 2 ..." einen Zeilenumbruch im Anführungszeichen enthält, enthält das Verarbeitungsergebnis auch einen Zeilenumbruch. Der Grund, warum isValid () am Ende falsch ist, ist, dass die Analyse als Anführungszeichen gestartet wurde, aber kein Anführungszeichenpaar vorhanden war und die Zeichenfolge beendet wurde. Wenn es richtig geschlossen wird, ist es wahr.

test 1 -------------------------------------------------------------------------------
isEmpty()  after analyze  : true

test 2 -------------------------------------------------------------------------------
isEmpty() before analyze() : true
[String          : "s"]
isEmpty() after analyze()  : false

test 3 -------------------------------------------------------------------------------
isValid() before analyze() : false
[String          : "[some]"]
[String          : "-c"]
[DoubleQuote     : """]
[String          : "text  document"]
[DoubleQuote     : """]
[LeftParenthesis : "("]
[String          : "sequence1"]
[DoubleQuote     : """]
[String          : "seq 2
  quoted"]
[DoubleQuote     : """]
[String          : "seq3"]
[String          : "seq4"]
[SingleQuote     : "'"]
[String          : "seq 5"]
[SingleQuote     : "'"]
[RightParenthesis: ")"]
[DoubleQuote     : """]
[String          : "ss"]
[Unknown         : "**Unknown**"]
isValid() after  analyze() : false

Zusammenfassung

Es ist lange her, aber dies ist das Ende der Erstellung eines Phrasenanalysators.

Wie wäre es bisher mit Syntaxanalyse? Ich kann es jedoch nicht schreiben, da die Syntax des Benutzers nicht festgelegt wurde. Die erwartete Grammatik wurde bis zu einem gewissen Grad festgelegt, aber es ist unentschlossen, wann sie geschrieben werden soll, da die Vorbereitung dafür nicht aufholt. (Sie müssen zuerst die Konsolenklasse abschließen ...)

Recommended Posts

Führen Sie eine Phrasenanalyse in Java 8 durch (Teil 2).
Erstellen einer Phrasenanalyse in Java 8 (Teil 1)
1 Implementieren Sie eine einfache Phrasenanalyse in Java
Erstellen einer Matrixklasse in Java Teil 1
Themenanalyse (LDA) in Java
Verwenden Sie OpenCV_Contrib (ArUco) mit Java! (Teil 2-Programmierung)
[Erstellen] Ein Memorandum über das Codieren in Java
Partisierung in Java
Änderungen in Java 11
Janken in Java
Java-Übung Teil 1
Umfangsrate in Java
FizzBuzz in Java
Verwenden Sie OpenCV_Contrib (ArUco) mit Java! (Teil 1-Build) (OpenCV-3.4.4)
Führen Sie eine statische Code-Analyse mit Checkstyle mit Java + Gradle durch
NLP4J Morphologische Analyse in Java (unter Verwendung von Kuromoji)
Lesen Sie JSON in Java
[LeJOS] Programmieren wir mindstorm-EV3 mit Java [Umgebungskonstruktion Teil 2]
Interpreter-Implementierung durch Java
Machen Sie einen Blackjack mit Java
Janken App in Java
Einschränkungsprogrammierung in Java
Setzen Sie Java8 in Centos7
NVL-artiger Typ in Java
Verbinden Sie Arrays in Java
"Hallo Welt" in Java
Ein kurzer Überblick über Java, das in Klasse 4 gelernt wurde
Kommentare in der Java-Quelle
Azure funktioniert in Java
Formatieren Sie XML in Java
Einfache HTML-Spezialchars in Java
Boyer-Moore-Implementierung in Java
Hallo Welt in Java
Verwenden Sie OpenCV mit Java
Was ich in Java gelernt habe (Teil 3) Anweisung zur Ausführung von Anweisungen
Typbestimmung in Java
Verschiedene Threads in Java
Implementierung der Heap-Sortierung (in Java)
Zabbix API in Java
ASCII-Kunst in Java
Listen in Java vergleichen
POST JSON in Java
Java studieren ~ Teil 8 ~ Besetzung
Ein kurzer Überblick über Java, das in Klasse 3 gelernt wurde
Fehler in Java ausdrücken
Ein kurzer Überblick über Java, das in Klasse 2 gelernt wurde
Erstellen Sie JSON in Java
Datumsmanipulation in Java 8
Was ist neu in Java 8?
Verwenden Sie PreparedStatement in Java
Was ist neu in Java 9,10,11
Parallele Ausführung in Java
JSON in Java und Jackson Teil 1 Gibt JSON vom Server zurück
Protokollaggregation und -analyse (Arbeiten mit AWS Athena in Java)
Was ich in Java gelernt habe (Teil 4) Bedingte Verzweigung und Wiederholung
45 Techniken zur Optimierung der Java-Leistung (Teil 1)
Versuchen Sie es mit RocksDB mit Java
Lesen Sie Binärdateien in Java 1
Vermeiden Sie den Fehler, den Yuma in Java gemacht hat