Faire une analyse de phrase en Java 8 (partie 2)

introduction

J'ai créé un analyseur de phrases simple à utiliser dans mon propre programme de console Java. J'ai créé une classe de jetons dans Article précédent, donc cette fois ce sera le corps principal de l'analyseur de phrases. Pour l'analyse des phrases, il est plus facile d'utiliser diverses bibliothèques, mais comme il est utilisé sur le lieu de travail où le téléchargement à partir de sites externes est interdit, il est créé à partir de zéro en utilisant uniquement les fonctions Java8.

2018/10/22 La source a été modifiée. 2018/10/24 Correction de l'enregistrement d'une copie de la chaîne de caractères cible d'analyse dans le constructeur.

En le créant, je me réfère à Implémentation d'une analyse de phrase simple en Java.

point important

Cette fois, la source et son explication seront longues, veuillez donc consulter Article précédent.

Classe d'analyse de phrase

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 ======================================================================
    /**Découpez le caractère de citation suivant sous forme de bloc de jetons. */
    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
    }

    /**Découpez les séparateurs et les caractères vides sous forme de bloc de jetons. */
    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
}

Commentaire

Méthode d'usine

À l'origine, je veux rendre la chaîne de caractères à analyser comme argument obligatoire, mais il n'y a aucun moyen de la forcer à l'appelant, alors définissez une chaîne de caractères vide sur la chaîne de caractères cible. Cela libère l'appelant des tracas de la vérification des valeurs nulles et de la gestion des exceptions. En passant, il n'est pas nécessaire de vérifier les arguments dans le constructeur.

** ** Définissez une copie de l'argument lors de la définition de la chaîne de caractères. Si vous ne le copiez pas, vous pouvez modifier la chaîne de caractères cible en externe.

    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

Méthode d'assistance

Puisqu'il s'agit d'un analyseur de phrases, il extrait la chaîne de caractères cible caractère par caractère et la traite. Les fonctions de base pour cela sont le groupe de méthodes suivant.

    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

Traitement d'analyse

Effectue une analyse lexicale et renvoie le résultat sous la forme d'une liste d'objets Token créés précédemment. La valeur de retour est une copie de la liste de jetons tokens_ conservée en interne. Comme il s'agit d'une liste standard, l'appelant peut ajouter / supprimer des éléments, ce qui affecte les jetons_ détenus par l'objet (faisant exactement référence au même objet). Si vous appelez analy () une deuxième fois ou plus tard, il renverra une copie de la liste de jetons qui a déjà été créée. Aucune réanalyse n'est effectuée.

En modifiant l'implémentation de next () (en sautant les caractères vides) et en ajoutant une méthode de jugement, il est plus propre que le contrôle de l'instruction switch précédente. En passant, l'intention du jugement est reflétée dans le nom de la méthode, donc aucun commentaire n'est nécessaire. (N'est-ce pas?)

    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

Traitement de la chaîne citée

Transformez la chaîne entre guillemets en un seul jeton, y compris des caractères vides. L'espace blanc entre le guillemet et le premier caractère et entre le dernier caractère et le guillemet est supprimé, mais l'espace blanc entre les autres chaînes, y compris les sauts de ligne et les tabulations, est conservé. ..

    /**Découpez le caractère de citation suivant sous forme de bloc de jetons. */
    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
    }

Traitement des chaînes de caractères (identifiant)

Découpez un jeton de chaîne qui n'est pas entre guillemets.

    /**Découpez les séparateurs et les caractères vides sous forme de bloc de jetons. */
    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) );
    }

Acquisition de statut

Méthode d'acquisition d'état LexicalAnalyzer.

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

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

tester

Dernier point mais non le moindre, voici la méthode main () pour tester et traiter les résultats. ~~ Je ne connais pas JUnit ~~ Le jeton délimiteur est masqué pour réduire le nombre de lignes dans le résultat du traitement.

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

Résultat d'exécution

test 1 ... cas où nul est passé en argument

Dans la méthode factory, une chaîne de caractères vide est définie pour qu'elle fonctionne sans erreur, donc il n'y a pas de problème même si analy () et le stream () suivant sont exécutés. Rien ne s'affiche. "isEmpty () ..." est le résultat du test de la méthode isEmpty ().

test 2 ... analyser un seul caractère

Plutôt que de vérifier les résultats de l'analyse, vérifiez les spécifications de isEmpty () avant et après l'exécution d'analyser ().

test 3 ... Analyse d'une telle chaîne

Puisque "seq 2 ..." contient un saut de ligne dans le devis, le résultat du traitement contient également un saut de ligne. La raison pour laquelle isValid () à la fin est false est que l'analyse a été lancée sous la forme d'une chaîne de caractères entre guillemets, mais il n'y avait pas de paire de guillemets et la chaîne de caractères s'est terminée. S'il est fermé correctement, ce sera vrai.

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

Résumé

Cela fait longtemps, mais c'est la fin de la création d'un analyseur de phrases.

Qu'en est-il de l'analyse syntaxique jusqu'à présent? Cependant, je ne peux pas l'écrire car la syntaxe de l'utilisateur n'a pas été décidée. La grammaire attendue a été décidée dans une certaine mesure, mais on ne sait pas quand l'écrire car la préparation ne rattrape pas. (Vous devez d'abord terminer la classe de console ...)

Recommended Posts

Faire une analyse de phrase en Java 8 (partie 2)
Création d'une analyse de phrase dans Java 8 (partie 1)
1 Implémentez une analyse de phrase simple en Java
Création d'une classe de matrice dans Java Partie 1
Analyse de sujets (LDA) en Java
Utilisez OpenCV_Contrib (ArUco) avec Java! (Partie 2-Programmation)
[Création] Un mémorandum sur le codage en Java
Partition en Java
Changements dans Java 11
Janken à Java
java pratique partie 1
Taux circonférentiel à Java
FizzBuzz en Java
Utilisez OpenCV_Contrib (ArUco) avec Java! (Partie 1-Construire) (OpenCV-3.4.4)
Analyse de code statique par Checkstyle avec Java + Gradle
NLP4J [001b] Analyse morphologique en Java (utilisant kuromoji)
Lire JSON en Java
[LeJOS] Programmons mindstorm-EV3 avec Java [Construction de l'environnement partie 2]
Implémentation de l'interpréteur par Java
Faites un blackjack avec Java
Application Janken en Java
Programmation par contraintes en Java
Mettez java8 dans centos7
NVL-ish guy en Java
Joindre des tableaux en Java
"Hello World" en Java
Un examen rapide de Java appris en classe part4
Commentaires dans la source Java
Fonctions Azure en Java
Formater XML en Java
Simple htmlspecialchars en Java
Implémentation Boyer-Moore en Java
Hello World en Java
Utiliser OpenCV avec Java
Ce que j'ai appris en Java (partie 3) Déclaration d'exécution des instructions
Détermination de type en Java
Divers threads en java
Implémentation du tri de tas (en java)
API Zabbix en Java
Art ASCII à Java
Comparer des listes en Java
POST JSON en Java
Étudier Java ~ Partie 8 ~ Cast
Un examen rapide de Java appris en classe part3
Exprimer l'échec en Java
Un examen rapide de Java appris en classe part2
Créer JSON en Java
Manipulation de la date dans Java 8
Nouveautés de Java 8
Utiliser PreparedStatement en Java
Nouveautés de Java 9,10,11
Exécution parallèle en Java
JSON en Java et Jackson Partie 1 Renvoyer JSON à partir du serveur
Agrégation et analyse de journaux (utilisation d'AWS Athena en Java)
Ce que j'ai appris en Java (partie 4) Branchement conditionnel et répétition
45 Techniques d'optimisation des performances Java (partie 1)
Essayez d'utiliser RocksDB avec Java
Lire des fichiers binaires en Java 1
Évitez l'erreur que Yuma a donnée en Java