簡體   English   中英

在 Java 8 中將標記流映射到 n-gram 流

[英]Mapping a stream of tokens to a stream of n-grams in Java 8

我認為這是關於 Java 8 流的一個相當基本的問題,但是我很難考慮正確的搜索詞。 所以我在這里問。 我剛剛進入 Java 8,所以請耐心等待。

我想知道如何將標記流映射到 n-gram 流(表示為大小為 n 的標記數組)。 假設n = 3,那么我想轉換以下流

{1, 2, 3, 4, 5, 6, 7}

{[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7]}

我將如何使用 Java 8 流來實現這一點? 應該可以同時計算這個,這就是為什么我有興趣用流來完成這個(處理 n 數組的順序也無關緊要)。

當然,我可以使用老式的 for 循環輕松完成,但我更喜歡使用流 API。

如果您無法隨機訪問源數據,則可以使用自定義收集器完成此操作:

List<Integer> data = Arrays.asList(1,2,3,4,5,6,7);

List<List<Integer>> result = data.stream().collect(window(3, toList(), toList()));  

這是window的來源。 它是並行友好的:

public static <T, I, A, R> Collector<T, ?, R> window(int windowSize, Collector<T, ?, ? extends I> inner, Collector<I, A, R> outer) {

    class Window {
        final List<T> left = new ArrayList<>(windowSize - 1);
        A mid = outer.supplier().get();
        Deque<T> right = new ArrayDeque<>(windowSize);

        void add(T t) {
            right.addLast(t);
            if (left.size() == windowSize - 1) {
                outer.accumulator().accept(mid, right.stream().collect(inner));
                right.removeFirst();
            } else {
                left.add(t);
            }
        }

        Window merge(Window other) {
            other.left.forEach(this::add);
            if (other.left.size() == windowSize - 1) { 
                this.mid = outer.combiner().apply(mid, other.mid);
                this.right = other.right;
            }
            return this;
        }

        R finish() {
            return outer.finisher().apply(mid);
        }
    }

    return Collector.of(Window::new, Window::add, Window::merge, Window::finish);
}

這樣的操作並不真正適合 Stream API。 在功能術語中,您嘗試執行的操作稱為大小為n的滑動窗口。 Scala 內置了sliding()方法,但 Java Stream API 中沒有內置任何內容。

您必須依靠在輸入列表的索引上使用 Stream 來實現這一點。

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    List<List<Integer>> result = nGrams(list, 3);
    System.out.println(result);
}

private static <T> List<List<T>> nGrams(List<T> list, int n) {
    return IntStream.range(0, list.size() - n + 1)
                    .mapToObj(i -> new ArrayList<>(list.subList(i, i + n)))
                    .collect(Collectors.toList());
}

此代碼只是在輸入列表的索引上創建一個 Stream,將它們中的每一個映射到一個新列表,該列表是從ii+n (排除)獲取列表值的結果,並將所有這些收集到一個列表中。

基於https://stackoverflow.com/a/20507988/11451863

以下應該工作

int n = 3;
List<Integer> intList = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

IntStream.rangeClosed(0, intList.size() - n)
        .mapToObj(i -> intList.subList(i, i+n))
        .collect(Collectors.toList());

此解決方案使用 reduce 功能。 它不適用於並行化。

一般的想法是創建一個 n 克的列表。 對於每個元素,我們取最后一個 n-gram,並使用該元素滑動窗口。

public static void main(String[] args) {
    int n = 3;
    // creating the initial list of tokens
    List<Integer> ints = IntStream.range(1,8).boxed().toList();

    /creating the first ngram
    List<List<Integer>> ngram = new ArrayList<>();
    ngram.add(ints.subList(0,n));

    // This is where the ngram list is created
    List<List<Integer>> ngrams = ints.stream().skip(n)
                   .reduce(ngram, WikiDictionaryExtractor::addWindow, (l1, l2)-> null);
}


public static List<List<Integer>> addWindow(List<List<Integer>> input, Integer newInt){
    List<Integer> res = new ArrayList(input.get(input.size()-1));
    res.remove(0);
    res.add(newInt);
    input.add(res);
    return input;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM