在Java 8中将标记流映射到n-gram流

时间:2016-02-02 22:21:47

标签: java java-8 java-stream n-gram

我认为这是一个关于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。

3 个答案:

答案 0 :(得分:4)

如果您没有随机访问源数据,可以使用自定义收集器完成此操作:

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

答案 1 :(得分:3)

这样的操作并不适合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获取列表值的结果(不包括在内)并将所有这些收集到列表中。

答案 2 :(得分:0)

基于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());