Как получить ряд элементов из потока с помощью Java 8 lambda?
В предыдущем вопросе [Как динамически выполнять фильтрацию в Java 8?] Стюарт Маркс дал прекрасный ответ и предоставил несколько полезных утилит для обработки выбора topN и topPercent from stream.
Я включу их здесь из своего первоначального ответа:
@FunctionalInterface
public interface Criterion {
Stream<Widget> apply(Stream<Widget> s);
}
Criterion topN(Comparator<Widget> cmp, long n) {
return stream -> stream.sorted(cmp).limit(n);
}
Criterion topPercent(Comparator<Widget> cmp, double pct) {
return stream -> {
List<Widget> temp =
stream.sorted(cmp).collect(toList());
return temp.stream()
.limit((long)(temp.size() * pct));
};
}
Мои вопросы здесь:
[1] Как получить верхние элементы от 3 до 7 из потока с определенным количеством элементов, поэтому, если в потоке есть элементы из A1, A2.. A10, вызов
topNFromRange(Comparator<Widget> cmp, long from, long to) = topNFromRange(comparing(Widget::length), 3L, 7L)
вернет {A3, A4, A5, A6, A7}
Самый простой способ, по которому я могу думать, - получить верхнюю 7 [T7] от оригинала, получить верхнюю 3 [T3] от оригинала, а затем получить T7 - T3.
[2] Как получить верхние позиции от 10% до 30% от потока с определенным количеством элементов, поэтому, если в потоке есть элементы из X1, X2.. X100, вызов
topPercentFromRange(Comparator<Widget> cmp, double from, double to) = topNFromRange(comparing(Widget::length), 0.10, 0.30)
вернет {X10, X11, X12,..., X29, X30}
Самый простой способ, который я могу придумать, - получить 30% лучших [TP30] от оригинала, получить топ 10% [TP10] от оригинала, а затем получить TP30 - TP10.
Каковы некоторые лучшие способы использования Java 8 Lambda для краткого выражения вышеуказанных ситуаций?
Ответы
Ответ 1
Пользователь skiwi уже ответил на первую часть вопроса. Вторая часть:
(2) Как получить верхние позиции от 10% до 30% от потока с определенным количеством предметов....
Чтобы сделать это, вы должны использовать аналогичный метод, как topPercent
в ответе , на другой вопрос. То есть вам нужно собрать элементы в список, чтобы иметь возможность подсчитать количество элементов, возможно, после того, как была выполнена какая-либо фильтрация по потоку.
Как только у вас есть счетчик, вы вычисляете правильные значения для skip
и limit
на основе количества и процентов, которые вы хотите. Возможно, что-то подобное:
Criterion topPercentFromRange(Comparator<Widget> cmp, double from, double to) {
return stream -> {
List<Widget> temp =
stream.sorted(cmp).collect(toList());
return temp.stream()
.skip((long)(temp.size() * from))
.limit((long)(temp.size() * (to - from)));
};
}
Конечно, вам нужно будет выполнить проверку ошибок на from
и to
. Более тонкая проблема заключается в определении количества элементов для испускания. Например, если у вас есть десять элементов, они имеют индексы [0..9], которые соответствуют 0%, 10%, 20%,..., 90%. Но если бы вы попросили диапазон от 9% до 11%, приведенный выше код не испустил бы никаких элементов, а не тот, который был бы на 10%, как и следовало ожидать. Таким образом, некоторые манипуляции с процентными вычислениями, вероятно, необходимы для соответствия семантике того, что вы пытаетесь сделать.
Ответ 2
Чтобы получить диапазон от Stream<T>
, вы можете использовать skip(long n)
, чтобы сначала пропустить определенное количество элементов, а затем вы можете вызвать limit(long n)
, чтобы принимать только определенное количество элементов.
Рассмотрим поток с 10 элементами, затем для получения элементов с 3 по 7 вы обычно вызываете из List
:
list.subList(3, 7);
Теперь с Stream
вам нужно сначала пропустить 3 элемента, а затем взять 7 - 3 = 4 элемента, поэтому он станет следующим:
stream.skip(3).limit(4);
Как вариант решения @StuartMarks для второго ответа, я предлагаю вам следующее решение, которое оставляет возможность цепочки целыми, оно работает так же, как это делает @StuartMarks:
private <T> Collector<T, ?, Stream<T>> topPercentFromRangeCollector(Comparator<T> comparator, double from, double to) {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(comparator)
.skip((long)(list.size() * from))
.limit((long)(list.size() * (to - from)))
);
}
и
IntStream.range(0, 100)
.boxed()
.collect(topPercentFromRangeCollector(Comparator.comparingInt(i -> i), 0.1d, 0.3d))
.forEach(System.out::println);
Это напечатает элементы с 10 по 29.
Он работает с помощью Collector<T, ?, Stream<T>>
, который принимает ваши элементы из потока, преобразует их в List<T>
, затем получает Stream<T>
, сортирует его и применяет к нему (правильные) границы.