[英]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,將它們中的每一個映射到一個新列表,該列表是從i
到i+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.