白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

WhileStreamってないの?

こんばんは。

今日から数日は少し涼しいみたいですね。
30℃を超えると仮死状態になるので、ありがたいです。


最近ちょっと自然言語処理をしたかったのでKuromojiを使っているのですが、
JapaneseTokenizerがstream化できなくてなんかもやもやするんです。

やっぱりwhileってあんまり使わないんですかね?

private List<String> toWords(String src){

    List<String> ret = new ArrayList<>();
    try (JapaneseTokenizer jt = new JapaneseTokenizer(null, false, JapaneseTokenizer.Mode.NORMAL)) {

        CharTermAttribute ct = jt.getAttribute(CharTermAttribute.class);        
        PartOfSpeechAttribute partOfSpeech = jt.getAttribute(PartOfSpeechAttribute.class);

        jt.setReader(new StringReader(src));
        jt.reset();

        while (jt.incrementToken()) {
            if ("名詞".equals(partOfSpeech.getPartOfSpeech().split("-")[0])) {
                ret.add(ct.toString());
            }
        }

    } catch (IOException e) {
        e.printStackTrace();
    }

    return ret;
}

こんな感じのやつ。
ResultSetなんかもこのタイプですよね。


ためしに、前回のStreamHelperにこんなのを足してみますか?
(※Javadocに無責任なことを書いてはいけません)

/**
 * while風なStream
 * 
 * <pre>
 * 第一引数が第二引数な間だけ、第三引数したStreamを返す。
 * (何言ってるかわからない)
 * </pre>
 */
public static <T, S> Stream<S> whileStream(T target, Predicate<T> whileFunc, Function<T, S> convertFunc) {
    return StreamSupport.stream(new Spliterator<>(target, whileFunc, convertFunc), false);
}

private static class Spliterator<T, S> extends Spliterators.AbstractSpliterator<S> {

    private T target;

    private Predicate<T> whileFunc;

    private Function<T, S> convertFunc;

    private Spliterator(T target, Predicate<T> whileFunc, Function<T, S> convertFunc) {

        super(Long.MAX_VALUE, ORDERED);

        this.target = target;
        this.whileFunc = whileFunc;
        this.convertFunc = convertFunc;
    }

    @Override
    public boolean tryAdvance(Consumer<? super S> action) {
        if (this.whileFunc.test(target)) {
            action.accept(this.convertFunc.apply(target));
            return true;
        } else {
            return false;
        }
    }
}


で、こんな感じ。

private List<String> toWords(String src) {

    try (JapaneseTokenizer jt = new JapaneseTokenizer(null, false, JapaneseTokenizer.Mode.NORMAL)) {

        jt.setReader(new StringReader(src));
        jt.reset();

        return StreamHelper.whileStream(jt, StreamHelper.throwingPredicate(j -> j.incrementToken()), this::toToken)
                           .filter(p -> "名詞".equals(p.getValue()))
                           .map(p -> p.getKey())
                           .collect(Collectors.toList());

    } catch (IOException e) {
        return Collections.EMPTY_LIST;
    }
}

private Pair<String, String> toToken(JapaneseTokenizer jt) {

    CharTermAttribute ct = jt.getAttribute(CharTermAttribute.class);        
    PartOfSpeechAttribute partOfSpeech = jt.getAttribute(PartOfSpeechAttribute.class);

    return new Pair(ct.toString(), partOfSpeech.getPartOfSpeech().split("-")[0]);
}

ちなみにPairはパッケージがjavafx.utilなので、FXじゃないアプリで使っていいのか怪しいです。
自分で作るか、Apache CommonsとかEclipse Collectionsのを使ったほうがいいかもしれませんね。
(あ、あとはAbstractMap.SimpleEntryとか…)