简体   繁体   English

分区有限制

[英]PartitioningBy with limit

I need to split the list into two lists by predicate with limiting elements that are going to true part. 我需要通过谓词将列表拆分为两个列表,其中限制元素将成为true部分。
Eg Let's say I have such list : A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] and I want to split it by predicate o -> o % 2 == 0 and with limit 3 . 例如,假设我有这样的列表: A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ,我想用谓词o -> o % 2 == 0和限制3
I want to get Map<Boolean, List<Integer>> where: 我想得到Map<Boolean, List<Integer>>其中:

true -> [2, 4, 6] // objects by predicate and with limit (actually, order is not important)
false -> [1, 3, 5, 7, 8, 9, 10]  // All other objects

Java 8 has collector that splits stream by predicate - Collectors.partitioningBy(...) , but it doesn't support limits. Java 8具有通过谓词拆分流的Collectors.partitioningBy(...) ,但它不支持限制。 Is it possible to do this with java 8 streams / guava / apache, or should I create my own implementation of this function? 是否可以使用java 8 streams / guava / apache执行此操作,还是应该创建自己的此函数实现?

EDIT: I wrote this function. 编辑:我写了这个函数。 If you have any suggestion about this, feel free to tell me. 如果您对此有任何建议,请随时告诉我。 MultiValuedMap is optional and can be replaced with Map . MultiValuedMap是可选的,可以用Map替换。

private <E> MultiValuedMap<Boolean, E> partitioningByWithLimit(Predicate<E> predicate, List<E> src, int limit) {
    MultiValuedMap<Boolean, E> result = new ArrayListValuedHashMap<>();
    Iterator<E> iterator = src.iterator();
    while (iterator.hasNext()) {
        E next = iterator.next();
        if (limit > 0 && predicate.test(next)) {
            result.put(true, next);
            iterator.remove();
            limit--;
        }
    }
    result.putAll(false, src);
    return result;
}

Here is a way to do it based on a custom collector: 以下是基于自定义收集器的方法:

public static <E> Collector<E, ?, Map<Boolean, List<E>>> partitioningByWithLimit(
        Predicate<E> predicate,
        int limit) {

    class Acc {
        Map<Boolean, List<E>> map = new HashMap<>();

        Acc() {
            map.put(true, new ArrayList<>());
            map.put(false, new ArrayList<>());
        }

        void add(E elem) {
            int size = map.get(true).size();
            boolean key = size < limit && predicate.test(elem);
            map.get(key).add(elem);
        }

        Acc combine(Acc another) {
            another.map.get(true).forEach(this::add);
            another.map.get(false).forEach(this::add);
            return this;
        }
    }

    return Collector.of(Acc::new, Acc::add, Acc::combine, acc -> acc.map));
}

I'm using a local Acc class that wraps the map and exposes the logic to accumulate and combine elements of the stream into a map. 我正在使用一个包装地图的本地Acc类,并公开逻辑以累积和组合流的元素到地图中。 This map is partitioned according to the predicate and limit provided. 此映射根据提供的谓词和限制进行分区。

At the end, I'm collecting the stream with Collector.of . 最后,我正在使用Collector.of收集流。

Usage: 用法:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Map<Boolean, List<Integer>> map = list.stream()
        .collect(partitioningByWithLimit(n -> n % 2 == 0, 3));

Output is: 输出是:

{false=[1, 3, 5, 7, 8, 9, 10], true=[2, 4, 6]}

The main advantage of this approach is that it also supports parallel streams. 这种方法的主要优点是它还支持并行流。

There is no clean Stream solution, as the task relies on a stateful predicate. 没有干净的Stream解决方案,因为任务依赖于有状态谓词。

So your loop is not bad, but it can be cleanup up a bit: 所以你的循环并不坏,但它可以清理一下:

private <E> MultiValuedMap<Boolean, E> partitioningByWithLimit(
                                       Predicate<E> predicate, List<E> src, int limit) {
    MultiValuedMap<Boolean, E> result = new ArrayListValuedHashMap<>();
    for(E next: src) {
        boolean key = limit>0 && predicate.test(next);
        result.put(key, next);
        if(key) limit--;
    }
    return result;
}

If you really want to get the feeling of being a little faster when the limit has been reached, you may use 如果您真的希望在达到限制时感觉更快一些,您可以使用

private <E> MultiValuedMap<Boolean, E> partitioningByWithLimit(
                                       Predicate<E> predicate, List<E> src, int limit) {
    MultiValuedMap<Boolean, E> result = new ArrayListValuedHashMap<>();
    for(Iterator<E> iterator = src.iterator(); iterator.hasNext(); ) {
        E next = iterator.next();
        boolean key = predicate.test(next);
        result.put(key, next);
        if(key && --limit==0) iterator.forEachRemaining(result.get(false)::add);
    }
    return result;
}

This avoids rechecking the limit and even the map lookup for the remaining elements, however, I wouldn't expect a big performance difference. 这可以避免重新检查限制甚至是剩余元素的地图查找,但是,我不希望出现大的性能差异。 The first variant is much simpler. 第一个变体更简单。

Another alternative, utilizing more Java 8 features, is 使用更多Java 8功能的另一种选择是

private <E> MultiValuedMap<Boolean, E> partitioningByWithLimit(
                                       Predicate<E> predicate, List<E> src, int limit) {
    MultiValuedMap<Boolean, E> result = new ArrayListValuedHashMap<>();
    result.putAll(false, src);
    List<E> pos = result.get(true);
    result.get(false).removeIf(e -> pos.size()<limit && predicate.test(e) && pos.add(e));
    return result;
}

What about this: 那这个呢:

List<Integer> lista = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> count = new ArrayList<>(); 
Map<Boolean, List<Integer>> collect = lista.stream().collect(Collectors.groupingBy(new Function<Integer, Boolean>() {

    private int count = 0;

    @Override
    public Boolean apply(Integer o) {
        if(o % 2 == 0 && count < 3){
            count++;
            return true;
        } else {
            return false;
        }
    }
}));
System.out.println(collect);

Prints: {false=[1, 3, 5, 7, 8, 9, 10], true=[2, 4, 6]} 打印:{false = [1,3,5,7,8,9,10],true = [2,4,6]}

How about: 怎么样:

list.stream().collect(Collectors.partitioningBy(withLimit(i -> i % 2 == 0, 3)));

public static <E> Predicate<E> withLimit(final Predicate<E> predicate, final int limit) {
    Objects.requireNonNull(predicate);
    final AtomicInteger counter = new AtomicInteger(limit);
    return e -> predicate.test(e) && counter.decrementAndGet() > 0;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM