Как получить диапазон элементов из потока с помощью Java 8 lambda?

28

В предыдущем вопросе [ Как динамически выполнять фильтрацию в Java 8 ? ] Стюарт Маркс дал прекрасный ответ и предоставил несколько полезных утилит для обработки выбора topN и topPercent из потока.

Я включу их здесь из своего первоначального ответа:

@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 для краткого выражения вышеуказанных ситуаций?

    
задан Frank 07.04.2014 в 17:54
источник

2 ответа

23

Пользователь 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%, как и следовало ожидать. Таким образом, некоторые манипуляции с процентными вычислениями, вероятно, необходимы для соответствия семантике того, что вы пытаетесь сделать.

    
ответ дан Stuart Marks 07.04.2014 в 23:15
источник
  • Достаточно близко к тому, что я искал, я выработаю детали, спасибо! –  Frank 08.04.2014 в 01:04
  • Я обновил свой ответ, чтобы также включить форму того, что вы делаете, но затем с использованием Коллекционеров, возможно, это также может быть интересно для исходного вопроса о критериях? –  skiwi 08.04.2014 в 10:52
  • @skiwi Интересно, используя функцию финишера коллектора, чтобы вернуть коллекцию в поток. Я не уверен, что это обязательно лучше или хуже, чем просто объявление локальной переменной. (В этом случае параметр лямбда используется как локальный.) Однако это полезная техника, чтобы иметь в виду будущее. –  Stuart Marks 08.04.2014 в 22:27
27

Чтобы получить диапазон от 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> , сортирует его и применяет (правильные) к нему.

    
ответ дан skiwi 07.04.2014 в 17:57
источник
  • Если вы пропустите первые 10% предметов, то в потоке осталось только 90% предметов, как получить предметы из оригинала 30%, потому что 30% из 90% не являются оригинальными 30% , я прав ? –  Frank 07.04.2014 в 18:01
  • @Frank Вам нужно будет вычислить эти цифры заранее. –  skiwi 07.04.2014 в 18:33
  • Хорошо, спасибо! –  Frank 08.04.2014 в 01:04
  • @Frank Я обновил ответ, чтобы включить также вариант, который работает с цепочкой потоков вместе. –  skiwi 08.04.2014 в 10:57
  • Интересно и спасибо за ваши усилия. Так в чем разница с точки зрения пользователя? Когда использовать какой? Любые различия в эффективности / точности? То, что я вижу, связано со многими шагами и кажется более сложным, есть ли способ упростить его в форме: Критерий topPercentFromRange (Comparator <Widget> cmp, double from, double to), который более интуитивно понятен. –  Frank 08.04.2014 в 17:59