计算Stream中的元素,但仅考虑N进行收集

时间:2018-10-09 17:31:59

标签: java lambda java-stream

以下lambda是否可能在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

编辑:从我的角度来说,这太隐含了,但是这个主意当然是一个潜在的解决方案,它将是最快的(比两次调用流生成器并分别在两个流上执行两个操作更快)。至少):

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

...从一次迭代中获利,而不必将整个集合加载到内存中。我正在用答案的并行化进行测试

4 个答案:

答案 0 :(得分:4)

自定义收集器是这里的答案:

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收集器也不是UNDERDERED,那么只有将其应用于无序数据源时,才应同时评估它。

该文档的基本含义是,如果您的收集器是CONCURRENT ,并且流的来源是UNORDERED(例如Set),或者我们明确调用unordered,那么合并将永远不会被调用。

如果运行前面的代码,您将看到Combiner called永远不会出现在输出中。

如果您将Set.of(1, 2, 3, 4)更改为List.of(1, 2, 3, 4),则会看到不同的图片(忽略您得到的结果的正确性-因为ArrayList不是线程安全的,但这不是线程安全的)。点)。如果您将流的来源设为List 并同时,则调用unordered,您将再次看到仅调用累加器,即:< / p>

 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 }));

答案 1 :(得分:2)

下面的方法在具有摘要的本地类的帮助下使用可变的归约。
仅通过选择combiner函数中的前10个元素即可限制所收集元素的数量。

使用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]]

答案 2 :(得分:1)

这是一个简单的lambda表达式,该表达式会将超过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);

答案 3 :(得分:1)

这是另一种解决方案,不确定是否满足您的要求。

    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
}

现在,您有了列表以及总数,它们都存储在帮助对象c中。使用:

获取列表的总数
  System.out.println(c.getTotCount());
  System.out.println(c.getMyList().size());