我希望有人可以帮助我,我正试图找到一种方法,我可以根据条件筛选列表
public class Prices {
private String item;
private double price;
//......
}
例如我有一个上面的对象列表List有以下数据
item, price
a 100,
b 200,
c 250,
d 350,
e 450
有没有办法在List上使用流和过滤器,这样在最后我们只剩下价格总和小于给定输入值的对象
假设输入值是600,那么结果列表只有a,b,c,d,因为这些是对象的价格,当相加时,总和接近600.所以e会不包括在最终筛选列表中。 如果输入/给定值为300,则过滤后的列表将只有a和b。
列表已经排序,将从顶部开始并继续添加,直到达到给定值
由于 此致
答案 0 :(得分:3)
您可以编写这个静态方法,创建合适的谓词:
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;
}
};
}
并使用它:
List<Prices> result = prices.stream()
.filter(byLimitedSum(600))
.collect(Collectors.toList());
但这对于parallelStream来说是不好的解决方案。
无论如何我认为在这种情况下使用流和过滤器并不是那么好决定,导致可读性不是那么好。我认为更好的方法是编写这样的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;
}
或者您可以为List<Prices>
编写包装器并将公共方法添加到新类中。明智地使用流。
答案 1 :(得分:2)
这种任务的最简单的解决方案仍然是循环,例如
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);
满足所有正式的正确标准的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;
这甚至可以通过将stream()
替换为parallelStream()
来并行工作,但它不仅需要足够大的源列表才能获得好处,因为循环可以在第一个元素超出时停止在并行处理可以具有优势之前,结果列表必须远远大于源列表的¹/ n (其中n
是核心数)。< / p>
上面显示的循环解决方案也是非复制的。
答案 2 :(得分:1)
使用简单的for循环会简单得多,而且这实际上是 abusive ,正如Holger所提到的那样,我只把它作为练习。
好像你需要一个有状态过滤器或一个短路减少。我能想到这个:
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;
}
}
当我们完成时,需要从reduce
中断。从这里开始使用可能是微不足道的:
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()));