简体   繁体   English

Java 8流过滤器

[英]Java 8 stream filter

I hope someone can help me I am trying to find a way with which i can filter a list based on a condition 我希望有人可以帮助我,我正试图找到一种方法,我可以根据条件筛选列表

public class Prices {
    private String item;
    private double price;
    //......    
}

For example i have a list of above object List has the following data 例如,我有一个上面的对象List列表有以下数据

item, price

a     100,
b     200,
c     250,
d     350,
e     450

is there a way to use streams and filter on List so that at the end of it we are left with only objects that have a sum of prices less that a given input value 有没有办法在List上使用流和过滤器,这样在最后我们只剩下价格总和小于给定输入值的对象

Say if the input value is 600, so the resultant list would only have a,b,c,d as these are the objects whose price, when added to each other, the sum takes it closer to 600. So e would not be included in the final filtered list. 如果输入值是600,那么结果列表将只有a,b,c,d,因为这些是对象的价格,当相加时,总和会使它接近600.因此,e将不包括在内在最终筛选的列表中。 If the input/given value is 300 then the filtered list will only have a and b. 如果输入/给定值为300,则过滤后的列表将只有a和b。

The list is already sorted and will start from the top and keep on adding till the given value is reached 该列表已经排序,将从顶部开始并继续添加,直到达到给定值

Thanks Regards 感谢和问候

You can write this static method, that create suitable predicate: 您可以编写这个静态方法,创建合适的谓词:

public static Predicate<Prices> byLimitedSum(int limit) {
    return new Predicate<Prices>() {
        private int sum = 0;
        @Override
        public boolean test(Prices prices) {
            if (sum < limit) {
                sum += prices.price;
                return true;
            }
            return false;
        }
    };
}

And use it: 并使用它:

List<Prices> result = prices.stream()
        .filter(byLimitedSum(600))
        .collect(Collectors.toList());

But it is bad solution for parallelStream. 但这对parallelStream来说是不好的解决方案。

Anyway i think in this case stream and filter using is not so good decision, cause readability is not so good. 无论如何我认为在这种情况下流和过滤器的使用并不是那么好决定,导致可读性不是那么好。 Better way, i think, is write util static method like this: 我认为更好的方法是编写这样的util static方法:

public static List<Prices> filterByLimitedSum(List<Prices> prices, int limit) {
    List<Prices> result = new ArrayList<>();
    int sum = 0;
    for (Prices price : prices) {
        if (sum < limit) {
            result.add(price);
            sum += price.price;
        } else {
            break;
        }
    }
    return result;
}

Or you can write wrapper for List<Prices> and add public method into new class. 或者,您可以为List<Prices>编写包装器,并将公共方法添加到新类中。 Use streams wisely. 明智地使用流。

Given you requirements, you can use Java 9's takeWhile . 根据您的要求,您可以使用Java 9的takeWhile

You'll need to define a Predicate having a state: 你需要定义一个具有状态的Predicate

Predicate<Prices> pred = new Predicate<Prices>() {
    double sum = 0.0;
    boolean reached = false;
    public boolean test (Prices p) {
        sum += p.getPrice();
        if (sum >= 600.0) { // reached the sum
            if (reached) { // already reached the some before, reject element
                return false;
            } else { // first time we reach the sum, so current element is still accepted
                reached = true;
                return true;
            }
        } else { // haven't reached the sum yet, accept current element
            return true;
        }
    }
};

List<Prices> sublist = 
    input.stream()
         .takeWhile(pred)
         .collect(Collectors.toList());

The simplest solution for this kind of task is still a loop, eg 这种任务最简单的解决方案仍然是循环,例如

double priceExpected = 600;
int i = 0;
for(double sumCheck = 0; sumCheck < priceExpected && i < list.size(); i++)
    sumCheck += list.get(i).getPrice();
List<Prices> resultList = list.subList(0, i);

A Stream solution fulfilling all formal criteria for correctness, is much more elaborated: 满足所有正式标准的Stream解决方案更加详细:

double priceThreshold = 600;
List<Prices> resultList = list.stream().collect(
    () -> new Object() {
        List<Prices> current = new ArrayList<>();
        double accumulatedPrice;
    },
    (o, p) -> {
        if(o.accumulatedPrice < priceThreshold) {
            o.current.add(p);
            o.accumulatedPrice += p.getPrice();
        }
    },
    (a,b) -> {
        if(a.accumulatedPrice+b.accumulatedPrice <= priceThreshold) {
            a.current.addAll(b.current);
            a.accumulatedPrice += b.accumulatedPrice;
        }
        else for(int i=0; a.accumulatedPrice<priceThreshold && i<b.current.size(); i++) {
            a.current.add(b.current.get(i));
            a.accumulatedPrice += b.current.get(i).getPrice();
        }
    }).current;

This would even work in parallel by just replacing stream() with parallelStream() , but it would not only require a sufficiently large source list to gain a benefit, since the loop can stop at the first element exceeding the threshold, the result list must be significantly larger than ¹/ n of the source list (where n is the number of cores) before the parallel processing can have an advantage at all. 这甚至可以通过用parallelStream()替换stream()parallelStream() ,但它不仅需要足够大的源列表才能获得好处,因为循环可以在超过阈值的第一个元素处停止,结果列表必须在并行处理完全具有优势之前,要明显大于源列表的¹/ n (其中n是核的数量)。

Also the loop solution shown above is non-copying. 上面显示的循环解决方案也是非复制的。

Using a simple for loop would be much much simpler, and this is abusive indeed as Holger mentions, I took it only as an exercise. 使用简单的for循环会简单得多,而且正如Holger提到的那样,这实际上是滥用 ,我只把它作为练习。

Seems like you need a stateful filter or a short-circuit reduce . 好像你需要有状态滤波器短路减少 I can think of this: 我能想到这个:

static class MyException extends RuntimeException {

    private final List<Prices> prices;

    public MyException(List<Prices> prices) {
        this.prices = prices;
    }

    public List<Prices> getPrices() {
        return prices;
    }

    // make it a cheap "stack-trace-less" exception
    @Override
    public Throwable fillInStackTrace() {
        return this;
    }
}

This is needed to break from the reduce when we are done. 当我们完成时,这需要从reduce脱离出来。 From here the usage is probably trivial: 从这里使用可能是微不足道的:

 List<Prices> result;

    try {
        result = List.of(
                new Prices("a", 100),
                new Prices("b", 200),
                new Prices("c", 250),
                new Prices("d", 350),
                new Prices("e", 450))
                .stream()
                .reduce(new ArrayList<>(),
                        (list, e) -> {
                            double total = list.stream().mapToDouble(Prices::getPrice).sum();
                            ArrayList<Prices> newL = new ArrayList<>(list);
                            if (total < 600) {
                                newL.add(e);
                                return newL;
                            }

                            throw new MyException(newL);
                        },
                        (left, right) -> {
                            throw new RuntimeException("Not for parallel");
                        });
    } catch (MyException e) {
        e.printStackTrace();
        result = e.getPrices();
    }

    result.forEach(x -> System.out.println(x.getItem()));

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

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