Считайте элементы из потока, но примите во внимание только N - PullRequest
0 голосов
/ 09 октября 2018

Возможна ли следующая лямбда в Java?Я хотел бы посчитать элементы из моего отфильтрованного потока, но совместным образом сохранить первые 10

stream().filter(myFilter)  //Reduces input to forthcoming operations
        .limit(10)         //Limits to ten the amount of elements to finish stream 
        .peek(myList::add) //Stores the ten elements into a list
        .count();          //Here is the difficult one. Id like to count everything  the total of elements that pass the filter, beyond the 10 I am fetching

EDIT : это было слишком неявно с моей стороны, но идея подразумевается, конечно, какпотенциальное решение, которое было бы самым быстрым (быстрее, чем вызов дважды генератора потока и, по крайней мере, выполнение обеих операций по отдельности):

List<Entity> entities = stream().filter(myFilter) 
                                .limit(10)
                                .collect(Collectors.toList());
long entitiesCount = stream().filter(myFilter) 
                             .count();

... получение прибыли за одну итерацию и без необходимости загружатьВся коллекция на память.Я делаю тесты с распараллеливанием ответов

Ответы [ 4 ]

0 голосов
/ 10 октября 2018

Пользовательский сборщик - вот ответ:

Entry<List<Integer>, Integer> result = list.stream()
            .collect(Collector.of(
                    () -> new SimpleEntry<>(new ArrayList<>(), 0),
                    (l, x) -> {
                        if (l.getKey().size() < 10) {
                            l.getKey().add(x);
                        }
                        l.setValue(l.getValue() + 1);
                    },
                    (left, right) -> {
                        List<Integer> leftList = left.getKey();
                        List<Integer> rightList = right.getKey();
                        while (leftList.size() < 10 && rightList.size() > 0) {
                            leftList.add(rightList.remove(0));
                        }
                        left.setValue(left.getValue() + right.getValue());
                        return left;
                    }));

Предположим, у вас есть этот код:

Set.of(1, 2, 3, 4)
            .stream()
            .parallel()
            .collect(Collector.of(
                    ArrayList::new,
                    (list, ele) -> {
                        System.out.println("Called accumulator");
                        list.add(ele);
                    },
                    (left, right) -> {
                        System.out.println("Combiner called");
                        left.addAll(right);
                        return left;
                    },
                    new Characteristics[] { Characteristics.CONCURRENT }));

Прежде чем мы начнем думать об этом коде (важно, насколько он правильныйдля примера) нам нужно немного прочитать документацию для характеристики CONCURRENT:

Если коллектор CONCURRENT также не является UNORDERED, то он должен оцениваться одновременно только в случае его применения кнеупорядоченный источник данных.

Эта документация в основном говорит о том, что если ваш сборщик CONCURRENT и , то источником потока является UNORDERED (например, Set) или мы явно вызываем unordered, тогда слияние никогда не будет вызвано.

Если вы запустите предыдущий код, вы увидите, что Combiner called никогда не присутствует в выходных данных.

Еслиизменив Set.of(1, 2, 3, 4) на List.of(1, 2, 3, 4), вы увидите другую картинку (игнорируйте правильность полученного результата - поскольку ArrayList не является поточно-ориентированным, но это не главное).Если у вас есть источник потока, равный List и в то же время вы звоните unordered, вы снова увидите, что вызывается только аккумулятор, то есть:

 List.of(1, 2, 3, 4)
            .stream()
            .unordered()
            .parallel()
            .collect(Collector.of(
                    ArrayList::new,
                    (list, ele) -> {
                        System.out.println("Called accumulator");
                        list.add(ele);
                    },
                    (left, right) -> {
                        System.out.println("Combiner called");
                        left.addAll(right);
                        return left;
                    },
                    new Characteristics[] { Characteristics.CONCURRENT }));
0 голосов
/ 09 октября 2018

Вот простое лямбда-выражение, которое подсчитает добавление в ваш список любых элементов, до 10, которые пройдут через ваш фильтр:

i -> {if (myList.size() < 10) myList.add(i);}

Но вы не можете просто использовать count() on Stream:

Реализация может предпочесть не выполнять конвейер потока (либо последовательно, либо параллельно), если она способна вычислить счет непосредственно из источника потока.В таких случаях исходные элементы не будут прослежены, а промежуточные операции не будут оцениваться.Поведенческие параметры с побочными эффектами, которые категорически не приветствуются, за исключением безопасных случаев, таких как отладка, могут быть затронуты.

В моем случае использование count(), peek() не вызывалось, потому что элементыне были просмотрены, и мой список был пуст.

Выберите простое сокращение для подсчета элементов.

.reduce(0, (a, b) -> a + 1);

Мой код:

int count = yourCollection.stream()
    .filter(myFilter)
    .peek(i -> {if (myList.size() < 10) myList.add(i);} )
    .reduce(0, (a, b) -> a + 1);
0 голосов
/ 09 октября 2018

Это еще одно решение, не уверенное, соответствует ли оно вашим требованиям.

    final Count c = new Count();

    coll.stream().forEach(e -> {
        c.setTotCount(c.getTotCount() + 1);

        if (/*your filter*/) {
           // add till 10 elements only
           if (c.getMyList().size() <= 10) {
              c.addMyList(e);
           }
        }
    });

И класс помощника определен

class Count {
    int totCount;
    // Student for an example
    List<Student> myList = new ArrayList<>();

    public List<Student> getMyList() {
        return myList;
    }

    public void addMyList(Student std) {
        this.myList.add(std);
    }

    // getter and setter for totCount
}

Теперь у вас есть список и общая сумма.count, которые все хранятся в вспомогательном объекте c.Получить общее количество списка, используя:

  System.out.println(c.getTotCount());
  System.out.println(c.getMyList().size());
0 голосов
/ 09 октября 2018

Далее используется изменяемое сокращение с помощью локального класса, содержащего сводку.Ограничение для собранных элементов достигается путем выбора первых 10 элементов в функции combiner.

Пример использования IntStream:

Stat result = IntStream.range(0, 100)
        .boxed()
        .filter(i -> i % 2 == 0)
        .collect(() -> new Stat(0, new ArrayList<Integer>()), 
            (stat, integer) -> {
                stat.count++;
                if (stat.list.size() < 10) {
                    stat.list.add(integer);
                }
            }, 
            (stat1, stat2) -> {
                stat1.list.addAll(stat2.list.subList(0, Math.min(stat2.list.size(), 
                    10 - stat1.list.size())));
            });

А вот класс Stat, используемый в потоке (вы можете легко использовать что-то вроде Pair<Long, List<Integer>>):

private static class Stat {
    long count;
    List<Integer> list;

    public Stat(long count, List<Integer> list) {
        this.count = count;
        this.list = list;
    }
}

Приведенный выше пример приводит к [count=50,list=[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]]

...