Поток токенизация с позиционными индексами в Java - PullRequest
0 голосов
/ 30 октября 2019

При токенизации потока байтов в Java мне нужно извлечь информацию об индексах токенизированных слов. Например, дано:

Строка "Hello world! Java8 program."-> stream -> ...

Мне нужно что-то вроде:

[(Hello, 0, 5), (world, 6, 11), (Java8, 13, 18), (program, 19, 25)]

Проблема похожа на Java NLP: извлечение политик при токенизации текста

Разница заключается в том, что программный ввод - это поток с неизвестной длиной. Я хотел применить SimpleTokenizer к этой задаче, но, к сожалению, он принимает в качестве входных данных только строку. Поскольку поток может быть очень длинным, я не могу напрямую преобразовать поток в String (что заставит программу остановиться, пока поток не будет закрыт). Я не могу обработать поток в чанках, вычисленных в буфере символов, так как я могу разбить слова в этом процессе. Можете ли вы помочь мне найти подходящее решение? Текущий код может выполнять только токенизацию без учета индексов:

public static void main(String[] args) throws IOException, ParseException {
    final Reader input = initializeInput();
    final Writer output = initializeOutput();
    try {
        final long count = process(input, output);
        output.flush();

    } finally {
        input.close();
        output.close();
    }
}

protected static long process(Reader input, Writer output) throws IOException {
    final StreamTokenizer st = new StreamTokenizer(input);
    st.eolIsSignificant(false);

    long count = 0;
    int token;
    while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
        if (token == StreamTokenizer.TT_WORD) {
            final String word = st.sval;
            count++;
            output.write(word);
            output.flush();
        }
    }

    return count;
}

private static String asString(CharSequence stem) {
    if (stem == null)
        return "-";
    return stem.toString();
}

private static Writer initializeOutput()
        throws IOException, ParseException {
    final Writer output;

    output = new OutputStreamWriter(System.out, "UTF-8");

    return output;
}

private static Reader initializeInput()
        throws IOException {
    final Reader input;
    input = new InputStreamReader(System.in, "UTF-8");
    return input;
}

Ответы [ 2 ]

1 голос
/ 30 октября 2019

Я бы не стал использовать StreamTokenizer (OpenNLP несколько мощнее!). Скорее, сосредоточьтесь на том, как читать строки из ввода, не нарушая структуру.

Я думаю, вы, возможно, захотите посмотреть, как инструмент командной строки opennlp SimpleTokenizer обрабатывает это в CommandLineTokenizer . Если разрывы строк не достаточно хороши, вы могли бы написать ObjectStream<String>, что «слово оборачивает» входные данные в строки. Естественно, все позиции Span будут зависеть от линии, а не от InputStream, хотя вы можете исправить это, если вам действительно нужно.

0 голосов
/ 04 ноября 2019

Для людей с подобной проблемой в будущем:

Я изменил реализацию StreamTokenizer. К сожалению, наследство здесь не существует. getLastTokenStart () и getLastTokenEnd () возвращают индексы разбираемого токена после каждого шага синтаксического анализа. Это было подготовлено только для строки и чисел (не проверял это на CT_QUOTE, CT_COMMENT)

<code>package wrapper;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Arrays;

public class CustomStreamTokenizer {
    private long lastTokenStart = 0;
    private long lastTokenEnd = -1;

    /* Only one of these will be non-null */
    private Reader reader = null;
    private InputStream input = null;

    private char buf[] = new char[20];

    private int peekc = NEED_CHAR;

    private static final int NEED_CHAR = Integer.MAX_VALUE;
    private static final int SKIP_LF = Integer.MAX_VALUE - 1;

    private boolean pushedBack;
    private boolean forceLower;
    /** The line number of the last token read */
    private int LINENO = 1;

    private boolean eolIsSignificantP = false;
    private boolean slashSlashCommentsP = false;
    private boolean slashStarCommentsP = false;

    private byte ctype[] = new byte[256];
    private static final byte CT_WHITESPACE = 1;
    private static final byte CT_DIGIT = 2;
    private static final byte CT_ALPHA = 4;
    private static final byte CT_QUOTE = 8;
    private static final byte CT_COMMENT = 16;

    /**
     * After a call to the {@code nextToken} method, this field
     * contains the type of the token just read. For a single character
     * token, its value is the single character, converted to an integer.
     * For a quoted string token, its value is the quote character.
     * Otherwise, its value is one of the following:
     * <ul>
     * <li>{@code TT_WORD} indicates that the token is a word.
     * <li>{@code TT_NUMBER} indicates that the token is a number.
     * <li>{@code TT_EOL} indicates that the end of line has been read.
     *     The field can only have this value if the
     *     {@code eolIsSignificant} method has been called with the
     *     argument {@code true}.
     * <li>{@code TT_EOF} indicates that the end of the input stream
     *     has been reached.
     * </ul>
     * <p>
     * The initial value of this field is -4.
     *
     * @see     java.io.StreamTokenizer#eolIsSignificant(boolean)
     * @see     java.io.StreamTokenizer#nextToken()
     * @see     java.io.StreamTokenizer#quoteChar(int)
     * @see     java.io.StreamTokenizer#TT_EOF
     * @see     java.io.StreamTokenizer#TT_EOL
     * @see     java.io.StreamTokenizer#TT_NUMBER
     * @see     java.io.StreamTokenizer#TT_WORD
     */
    public int ttype = TT_NOTHING;

    /**
     * A constant indicating that the end of the stream has been read.
     */
    public static final int TT_EOF = -1;

    /**
     * A constant indicating that the end of the line has been read.
     */
    public static final int TT_EOL = '\n';

    /**
     * A constant indicating that a number token has been read.
     */
    public static final int TT_NUMBER = -2;

    /**
     * A constant indicating that a word token has been read.
     */
    public static final int TT_WORD = -3;

    /* A constant indicating that no token has been read, used for
     * initializing ttype.  FIXME This could be made public and
     * made available as the part of the API in a future release.
     */
    private static final int TT_NOTHING = -4;

    /**
     * If the current token is a word token, this field contains a
     * string giving the characters of the word token. When the current
     * token is a quoted string token, this field contains the body of
     * the string.
     * <p>
     * The current token is a word when the value of the
     * {@code ttype} field is {@code TT_WORD}. The current token is
     * a quoted string token when the value of the {@code ttype} field is
     * a quote character.
     * <p>
     * The initial value of this field is null.
     *
     * @see     java.io.StreamTokenizer#quoteChar(int)
     * @see     java.io.StreamTokenizer#TT_WORD
     * @see     java.io.StreamTokenizer#ttype
     */
    public String sval;

    /**
     * If the current token is a number, this field contains the value
     * of that number. The current token is a number when the value of
     * the {@code ttype} field is {@code TT_NUMBER}.
     * <p>
     * The initial value of this field is 0.0.
     *
     * @see     java.io.StreamTokenizer#TT_NUMBER
     * @see     java.io.StreamTokenizer#ttype
     */
    public double nval;

    /** Private constructor that initializes everything except the streams. */
    private CustomStreamTokenizer() {
        wordChars('a', 'z');
        wordChars('A', 'Z');
        wordChars(128 + 32, 255);
        whitespaceChars(0, ' ');
        commentChar('/');
        quoteChar('"');
        quoteChar('\'');
        parseNumbers();
    }


    /**
     * Create a tokenizer that parses the given character stream.
     *
     * @param r  a Reader object providing the input stream.
     * @since   JDK1.1
     */
    public CustomStreamTokenizer(Reader r) {
        this();
        if (r == null) {
            throw new NullPointerException();
        }
        reader = r;
    }

    /**
     * Resets this tokenizer's syntax table so that all characters are
     * "ordinary." See the {@code ordinaryChar} method
     * for more information on a character being ordinary.
     *
     * @see     java.io.StreamTokenizer#ordinaryChar(int)
     */
    public void resetSyntax() {
        for (int i = ctype.length; --i >= 0;)
            ctype[i] = 0;
    }

    /**
     * Specifies that all characters <i>c</i> in the range
     * <code>low&nbsp;&lt;=&nbsp;<i>c</i>&nbsp;&lt;=&nbsp;high</code>
     * are word constituents. A word token consists of a word constituent
     * followed by zero or more word constituents or number constituents.
     *
     * @param   low   the low end of the range.
     * @param   hi    the high end of the range.
     */
    public void wordChars(int low, int hi) {
        if (low < 0)
            low = 0;
        if (hi >= ctype.length)
            hi = ctype.length - 1;
        while (low <= hi)
            ctype[low++] |= CT_ALPHA;
    }

    /**
     * Specifies that all characters <i>c</i> in the range
     * <code>low&nbsp;&lt;=&nbsp;<i>c</i>&nbsp;&lt;=&nbsp;high</code>
     * are white space characters. White space characters serve only to
     * separate tokens in the input stream.
     *
     * <p>Any other attribute settings for the characters in the specified
     * range are cleared.
     *
     * @param   low   the low end of the range.
     * @param   hi    the high end of the range.
     */
    public void whitespaceChars(int low, int hi) {
        if (low < 0)
            low = 0;
        if (hi >= ctype.length)
            hi = ctype.length - 1;
        while (low <= hi)
            ctype[low++] = CT_WHITESPACE;
    }

    /**
     * Specifies that all characters <i>c</i> in the range
     * <code>low&nbsp;&lt;=&nbsp;<i>c</i>&nbsp;&lt;=&nbsp;high</code>
     * are "ordinary" in this tokenizer. See the
     * {@code ordinaryChar} method for more information on a
     * character being ordinary.
     *
     * @param   low   the low end of the range.
     * @param   hi    the high end of the range.
     * @see     java.io.StreamTokenizer#ordinaryChar(int)
     */
    public void ordinaryChars(int low, int hi) {
        if (low < 0)
            low = 0;
        if (hi >= ctype.length)
            hi = ctype.length - 1;
        while (low <= hi)
            ctype[low++] = 0;
    }

    /**
     * Specifies that the character argument is "ordinary"
     * in this tokenizer. It removes any special significance the
     * character has as a comment character, word component, string
     * delimiter, white space, or number character. When such a character
     * is encountered by the parser, the parser treats it as a
     * single-character token and sets {@code ttype} field to the
     * character value.
     *
     * <p>Making a line terminator character "ordinary" may interfere
     * with the ability of a {@code CustomStreamTokenizer} to count
     * lines. The {@code lineno} method may no longer reflect
     * the presence of such terminator characters in its line count.
     *
     * @param   ch   the character.
     * @see     java.io.StreamTokenizer#ttype
     */
    public void ordinaryChar(int ch) {
        if (ch >= 0 && ch < ctype.length)
            ctype[ch] = 0;
    }

    /**
     * Specified that the character argument starts a single-line
     * comment. All characters from the comment character to the end of
     * the line are ignored by this stream tokenizer.
     *
     * <p>Any other attribute settings for the specified character are cleared.
     *
     * @param   ch   the character.
     */
    public void commentChar(int ch) {
        if (ch >= 0 && ch < ctype.length)
            ctype[ch] = CT_COMMENT;
    }

    /**
     * Specifies that matching pairs of this character delimit string
     * constants in this tokenizer.
     * <p>
     * When the {@code nextToken} method encounters a string
     * constant, the {@code ttype} field is set to the string
     * delimiter and the {@code sval} field is set to the body of
     * the string.
     * <p>
     * If a string quote character is encountered, then a string is
     * recognized, consisting of all characters after (but not including)
     * the string quote character, up to (but not including) the next
     * occurrence of that same string quote character, or a line
     * terminator, or end of file. The usual escape sequences such as
     * {@code "\u005Cn"} and {@code "\u005Ct"} are recognized and
     * converted to single characters as the string is parsed.
     *
     * <p>Any other attribute settings for the specified character are cleared.
     *
     * @param   ch   the character.
     * @see     java.io.StreamTokenizer#nextToken()
     * @see     java.io.StreamTokenizer#sval
     * @see     java.io.StreamTokenizer#ttype
     */
    public void quoteChar(int ch) {
        if (ch >= 0 && ch < ctype.length)
            ctype[ch] = CT_QUOTE;
    }

    /**
     * Specifies that numbers should be parsed by this tokenizer. The
     * syntax table of this tokenizer is modified so that each of the twelve
     * characters:
     * <blockquote><pre>
     *      0 1 2 3 4 5 6 7 8 9 . -
     * 
*

* имеет атрибут "числовой". *

* Когда синтаксический анализатор встречает маркер слова, имеющий формат * числа с плавающей запятой двойной точности, он обрабатывает токен как * число, а не как слово, устанавливая поле {@code ttype} * в значениезначение {@code TT_NUMBER} и помещение числового значения * токена в поле {@code nval}. * * @see java.io.StreamTokenizer # nval * @see java.io.StreamTokenizer # TT_NUMBER * @see java.io.StreamTokenizer # ttype * / public void parseNumbers () {for (int i = '0'; i <= '9'; i ++) ctype [i] | = CT_DIGIT;ctype ['.'] | = CT_DIGIT;ctype ['-'] | = CT_DIGIT;} / ** * Определяет, обрабатываются ли концы строки как токены. * Если аргумент флага равен true, этот токенизатор обрабатывает конец строк * как токены;метод {@code nextToken} возвращает * {@code TT_EOL}, а также устанавливает для поля {@code ttype} значение *, когда читается конец строки. * <p>* Строка - это последовательность символов, заканчивающаяся либо символом возврата каретки * ({@code '\ u005Cr'}), либо символом новой строки * ({@code '\ u005Cn'}). Кроме того, символ возврата каретки *, за которым сразу следует символ новой строки, * обрабатывается как один маркер конца строки. *

* Если {@code flag} равен false, символы конца строки * обрабатываются как пробелы и служат только для разделения токенов. * * @param flag {@code true} указывает, что символы конца строки * являются отдельными токенами;{@code false} указывает, что * символы конца строки являются пробелами. * @see java.io.StreamTokenizer # nextToken () * @see java.io.StreamTokenizer # ttype * @see java.io.StreamTokenizer # TT_EOL * / public void eolIsSignificant (логический флаг) {eolIsSignificantP = flag;} / ** * Определяет, распознает ли токенизатор комментарии в стиле C. * Если аргумент флага равен {@code true}, этот потоковый токенизатор * распознает комментарии в стиле C. Весь текст между последовательными * вхождениями {@code / *} и *&#47; отбрасывается. *

* Если аргумент флага равен {@code false}, то комментарии в стиле C * не обрабатываются специально. * * @param flag {@code true} указывает на распознавание и игнорирование комментариев в стиле C. * / public void slashStarComments (логический флаг) {slashStarCommentsP = flag;} / ** * Определяет, распознает ли токенизатор комментарии в стиле C ++. * Если аргумент флага равен {@code true}, этот потоковый токенизатор * распознает комментарии в стиле C ++. Любое вхождение двух последовательных символов косой черты ({@code '/'}) рассматривается как начало комментария, который простирается до конца строки. *

* Если аргумент флага равен {@code false}, то комментарии в стиле C ++ не обрабатываются специально. * * @param flag {@code true} указывает на распознавание и игнорирование комментариев в стиле * C ++. * / public void slashSlashComments (логический флаг) {slashSlashCommentsP = flag;} / ** * Определяет, будет ли токен слова автоматически подставляться в нижний регистр. * Если аргументом флага является {@code true}, то значение в поле * {@code sval} будет указываться в нижнем регистре при каждом * возвращении токена слова (поле {@code ttype} имеет * значение {@code TT_WORD}с помощью метода {@code nextToken} * этого токенизатора. *

* Если аргумент флага равен {@code false}, то поле * {@code sval} не изменяется. * * @param fl {@code true} указывает на то, что все лексемы должны быть * в нижнем регистре. * @see java.io.StreamTokenizer # nextToken () * @see java.io.StreamTokenizer # ttype * @see java.io.StreamTokenizer # TT_WORD * / public void lowerCaseMode (boolean fl) {forceLower = fl;} / ** Прочитать следующий символ * / private int read () throws IOException {lastTokenEnd ++;if (reader! = null) return reader.read ();иначе if (input! = null) return input.read ();еще бросить новый IllegalStateException ();} / ** * Анализирует следующий токен из входного потока этого токенизатора. * Тип следующего токена возвращается в поле {@code ttype} *. Дополнительная информация о токене может быть в поле * {@code nval} или в поле {@code sval} данного * токена. *

* Типичные клиенты этого * класса сначала устанавливают таблицы синтаксиса, а затем сидят в цикле *, вызывая nextToken для анализа последовательных токенов до тех пор, пока не будет возвращено TT_EOF *. * * @ вернуть значение поля {@code ttype}. * @exception IOException, если происходит ошибка ввода-вывода. * @see java.io. вернуть ttype;} byte ct [] = ctype;sval = ноль;int c = peekc;if (c <0) c = NEED_CHAR;if (c == SKIP_LF) {c = read ();if (c <0) return ttype = TT_EOF;if (c == '\ n') c = NEED_CHAR;} if (c == NEED_CHAR) {c = read ();if (c <0) return ttype = TT_EOF;} ttype = c;/ * Просто для безопасности * / / * Установите peekc так, чтобы при следующем вызове nextToken читался * другой символ, если peekc не сбрасывается в этом вызове * / peekc = NEED_CHAR;int ctype = c <256? ct [c]: CT_ALPHA;while ((ctype & CT_WHITESPACE)! = 0) {if (c == '\ r') {LINENO ++;if (eolIsSignificantP) {peekc = SKIP_LF;return ttype = TT_EOL;} c = read ();if (c == '\ n') c = read ();} else {if (c == '\ n') {LINENO ++;if (eolIsSignificantP) {return ttype = TT_EOL;}} c = read ();} if (c <0) return ttype = TT_EOF;ctype = c <256? ct [c]: CT_ALPHA;} lastTokenStart = lastTokenEnd;if ((ctype & CT_DIGIT)! = 0) {логическое отрицание = ложь;if (c == '-') {c = read ();if (c! = '.' && (c <'0' || c> '9')) {peekc = c;return ttype = '-';} neg = true;} double v = 0;int decexp = 0;int seendot = 0;while (true) {if (c == '.' && seendot == 0) seendot = 1;иначе if ('0' <= c && c <= '9') {v = v * 10 + (c - '0');decexp + = seendot;} еще перерыв;c = read ();} peekc = c;if (decexp! = 0) {double denom = 10;decexp--;while (decexp> 0) {деном * = 10;decexp--;} / * Сделайте одно деление числа, которое, скорее всего, будет более точным * / v = v / denom;} nval = neg? -v: v;return ttype = TT_NUMBER;} if ((ctype & CT_ALPHA)! = 0) {int i = 0;do {if (i> = buf.length) {buf = Arrays.copyOf (buf, buf.length * 2);} buf [i ++] = (char) c;c = read ();ctype = c <0? CT_WHITESPACE: c <256? ct [c]: CT_ALPHA;} while ((ctype & (CT_ALPHA | CT_DIGIT))! = 0);peekc = c;sval = String.copyValueOf (buf, 0, i);if (forceLower) sval = sval.toLowerCase ();return ttype = TT_WORD;} if ((ctype & CT_QUOTE)! = 0) {ttype = c;int i = 0;/ * Инварианты (потому что \ Octal нужен прогноз): * (i) c содержит значение char * (ii) d содержит прогноз * / int d = read ();while (d> = 0 && d! = ttype && d! = '\ n' && d! = '\ r') {if (d == '\\') {c = read ();int first = c;/ * Разрешить \ 377, но не \ 477 * / if (c> = '0' && c <= '7') {c = c - '0';int c2 = read ();if ('0' <= c2 && c2 <= '7') {c = (c << 3) + (c2 - '0');c2 = read ();if ('0' <= c2 && c2 <= '7' && first <= '3') {c = (c << 3) + (c2 - '0');d = read ();} иначе d = c2;} иначе d = c2;} else {switch (c) {case 'a': c = 0x7;сломать;case 'b': c = '\ b';сломать;case 'f': c = 0xC;сломать;case 'n': c = '\ n';сломать;case 'r': c = '\ r';сломать;case 't': c = '\ t';сломать;случай 'v': c = 0xB;сломать;} d = read ();}} else {c = d;d = read ();} if (i> = buf.length) {buf = Arrays.copyOf (buf, buf.length * 2);} buf [i ++] = (char) c;} / * Если мы вышли из цикла, потому что нашли соответствующий символ * в кавычках, тогда договоримся прочитать новый символ в следующий раз *;в противном случае сохраните персонажа. * / peekc = (d == ttype)? NEED_CHAR: d;sval = String.copyValueOf (buf, 0, i);вернуть ttype;} if (c == '/' && (slashSlashCommentsP || slashStarCommentsP)) {c = read ();if (c == '*' && slashStarCommentsP) {int prevc = 0;while ((c = read ())! = '/' || prevc! = '*') {if (c == '\ r') {LINENO ++;c = read ();if (c == '\ n') {c = read ();}} else {if (c == '\ n') {LINENO ++;c = read ();}} if (c <0) return ttype = TT_EOF;prevc = c;} return nextToken ();} else if (c == '/' && slashSlashCommentsP) {while ((c = read ())! = '\ n' && c! = '\ r' && c> = 0);peekc = c;return nextToken ();} else {/ * Теперь посмотрим, если это все еще однострочный комментарий * / if ((ct ['/'] & CT_COMMENT)! = 0) {while ((c = read ())! = '\ n' &&c! = '\ r' && c> = 0);peekc = c;return nextToken ();} else {peekc = c;return ttype = '/';}}} if ((ctype & CT_COMMENT)! = 0) {while ((c = read ())! = '\ n' && c! = '\ r' && c> = 0);peekc = c;return nextToken ();} return ttype = c;} / ** * Заставляет следующий вызов метода {@code nextToken} этого * токенайзера возвращать текущее значение в поле {@code ttype} *, а не изменять значение в {@code nval} или* {@code sval} поле. * * @see java.io.StreamTokenizer # nextToken () * @see java.io.StreamTokenizer # nval * @see java.io.StreamTokenizer # sval * @see java.io.StreamTokenizer # ttype * / public void pushBack (){if (ttype! = TT_NOTHING) / * Нет операции, если nextToken () не вызван * / pressedBack = true;} / ** * Возвращает текущий номер строки. * * @ вернуть номер текущей строки этого потокового токенизатора. * / public int lineno () {возвращение LINENO;} / ** * Возвращает строковое представление токена текущего потока и * номер строки, в которой он встречается. * *

Точная возвращаемая строка не указана, хотя следующий * пример может считаться типичным: * *

Token['a'], line 10
* * @ вернуть строковое представление токена * @see java.io. StreamTokenizer # nval * @see java.io.StreamTokenizer # sval * @see java.io.StreamTokenizer # ttype * / public String toString () {String ret;switch (ttype) {case TT_EOF: ret = "EOF";сломать;case TT_EOL: ret = "EOL";сломать;case TT_WORD: ret = sval;сломать;case TT_NUMBER: ret = "n =" + nval;сломать;case TT_NOTHING: ret = "НИЧЕГО";сломать;default: {/ * * ttype - это первый символ строки в кавычках или * - обычный символ. ttype определенно не может быть меньше * 0, поскольку это зарезервированные значения, использованные в предыдущих * операторах case * / if (ttype <256 && ((ctype [ttype] & CT_QUOTE)! = 0)) {ret = sval;сломать;} char s [] = новый символ [3];s [0] = s [2] = '\' ';s [1] = (char) ttype;ret = новая строка (ы);сломать;}} return "Token [" + ret + "], строка" + LINENO;} public long getLastTokenStart () {return lastTokenStart;} public long getLastTokenEnd () {return lastTokenEnd;}} </code>
...