Возможным решением для этого было бы написать свой собственный Spliterator
, который бы обернул разделитель данного Stream
. Этот Spliterator
делегировал бы наступательные вызовы обернутому разделителю и содержал бы логику подсчета многих различных элементов.
Для этого мы можем подкласса AbstractSpliterator
и предоставить нашу собственную логику tryAdvance
. В дальнейшем все элементы, которые встречаются, добавляются в набор. Когда размер этого набора становится больше нашего максимума или когда у обернутого разделителя нет оставшихся элементов, мы возвращаем false
, чтобы указать, что нет оставшихся элементов для рассмотрения. Это остановится, когда будут достигнуты числа отдельных элементов.
private static <T> Stream<T> distinctLimit(Stream<T> stream, int max) {
Spliterator<T> spltr = stream.spliterator();
Spliterator<T> res = new AbstractSpliterator<T>(spltr.estimateSize(), spltr.characteristics()) {
private Set<T> distincts = new HashSet<>();
private boolean stillGoing = true;
@Override
public boolean tryAdvance(Consumer<? super T> action) {
boolean hasRemaining = spltr.tryAdvance(elem -> {
distincts.add(elem);
if (distincts.size() > max) {
stillGoing = false;
} else {
action.accept(elem);
}
});
return hasRemaining && stillGoing;
}
};
return StreamSupport.stream(res, stream.isParallel()).onClose(stream::close);
}
С вашего пример кода, вы бы:
Map<String, Long> m =
distinctLimit(Stream.of("a", "a", "b", "c", "d"), 3)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
и выход будет ожидаемый {a=2, b=1, c=1}
, то есть карты с 3 различными ключами.
Не можете ли вы просто фильтровать? – SamTebbs33
Не могли бы вы сделать что-то вроде этого? Не уверен, разрешено ли это. 'Map m = Stream.of (" a "," a "," b "," c "," d ") .limit (m.size() <3? Integer.MAX_VALUE: 3) .collect (toMap (Function.identity(), 1, Integer :: sum)); ' –
nbokmans
*« Есть ли способ? »* Да, напишите свой собственный' toMap() 'collector. Это * удобно *? Вопрос перспективы, я думаю. – Andreas