Sil*_*ier 17 java filtering java-8 java-stream
我在Java流操作中遇到了边缘情况......
我想编码以下行为:"从任意一篮子水果中收集20个最小的,除了最小的梨,因为我们不希望这样."
额外的奖励:来的篮子可能根本没有任何梨.
例子 :
到目前为止,我正迈出这一步:
output = basket.stream()
.sorted(Comparator.comparing(Fruit::getSize))
//.filter(???)
.limit(20)
.collect(fruitCollector);
Run Code Online (Sandbox Code Playgroud)
这似乎是状态 lambda过滤器的情况,我不知道该怎么做.
我不能使用局部firstPear布尔值并true在过滤第一个梨之后将其设置为,因为lambda中的所有局部变量必须是final.
最糟糕的情况我可以将篮子分成两个,梨和非梨,对梨进行分类,如果有的话,适当地对它们进行子目录.这似乎非常低效和丑陋.有没有更好的办法?
这里发布的答案有很多种,而且大多数都是有效的.为了回馈社区,我整理了一个小测试工具来比较这些算法的性能.
这种比较并没有我想要的那么广泛 - 已经有3周了.它仅涵盖简单项目的顺序处理的用法.随意提供测试工具,并添加更多测试,更多基准或您自己的实现.
我的分析:
Algorithm | Author | Perf | Comments -------------------------------------------------------------------------------- Indexed removal | Holger | Best | Best overall, somewhat obscure Stateful predicate | pedromss | Best | Do not use for parallel processing Straightforward approach | Misha | Best | Better when few elements match Custom collector | Eugene | Good | Better when all or no element match Comaprator hack w/ dummy | yegodm | Good | - Comparator hack | xenteros | * | Perf sensitive to output size, fails on edge cases.
由于其良好的性能和"黑盒"功能(状态管理代码在外部类中,并且贡献者可以专注于业务逻辑),我认为pedromss'答案是因为它是我们在项目中实现的那个).
请注意,接受的答案可能不是最适合您的:查看其他答案,或检查我的测试项目以便自己查看.
你考虑过一种直截了当的方法吗?找到最小的梨,将其过滤掉(如果存在)并收集最小的20个:
Optional<Fruit> smallestPear = basket.stream()
.filter(Fruit::isPear) // or whatever it takes to test if it's a pear
.min(Fruit::getSize);
Stream<Fruit> withoutSmallestPear = smallestPear
.map(p -> basket.stream().filter(f -> f != p))
.orElseGet(basket::stream);
List<Fruit> result = withoutSmallestPear
.sorted(comparing(Fruit::getSize))
.limit(20)
.collect(toList());
Run Code Online (Sandbox Code Playgroud)
据我所知,这已经定制了全部,所以我在这里尝试了一个自定义收集器:
private static <T> Collector<T, ?, List<T>> exceptCollector(Predicate<T> predicate, int size, Comparator<T> comparator) {
class Acc {
private TreeSet<T> matches = new TreeSet<>(comparator);
private TreeSet<T> doesNot = new TreeSet<>(comparator);
void accumulate(T t) {
if (predicate.test(t)) {
matches.add(t);
} else {
doesNot.add(t);
}
}
Acc combine(Acc other) {
matches.addAll(other.matches);
doesNot.addAll(other.doesNot);
return this;
}
List<T> finisher() {
T smallest = matches.first();
if (smallest != null) {
matches.remove(smallest);
}
matches.addAll(doesNot);
return matches.stream().limit(size).collect(Collectors.toList());
}
}
return Collector.of(Acc::new, Acc::accumulate, Acc::combine, Acc::finisher);
}
Run Code Online (Sandbox Code Playgroud)
用法是:
List<Fruit> fruits = basket.getFruits()
.stream()
.collect(exceptCollector(Fruit::isPear, 20, Comparator.comparing(Fruit::getSize)));
Run Code Online (Sandbox Code Playgroud)
为了便于实现,我附上了一个示例:
class Fruit {
String name;
Long size;
}
Run Code Online (Sandbox Code Playgroud)
以下将有效:
Comparator<Fruit> fruitComparator = (o1, o2) -> {
if (o1.getName().equals("Peach") && o2.getName().equals("Peach")) {
return o2.getSize().compareTo(o1.getSize()); //reverse order of Peaches
}
if (o1.getName().equals("Peach")) {
return 1;
}
if (o2.getName().equals("Peach")) {
return -1;
}
return o1.getSize().compareTo(o2.getSize());
};
Run Code Online (Sandbox Code Playgroud)
和:
output = basket.stream()
.sorted(Comparator.comparing(Fruit::getSize))
.limit(21)
.sorted(fruitComparator)
.limit(20)
.sorted(Comparator.comparing(Fruit::getSize))
.collect(fruitCollector);
Run Code Online (Sandbox Code Playgroud)
我的比较器将最小的Peach放到第21位,将保持其他Fruits 的顺序自然,所以如果没有Peach,它将返回第21个最大的元素.然后我按正常顺序对其余部分进行排序.
这会奏效.这是一个黑客,在某些情况下可能是一个糟糕的选择.我想指出,排序20个元素应该不是问题.
您可以使用有状态谓词:
class StatefulPredicate<T> implements Predicate<T> {
private boolean alreadyFiltered;
private Predicate<T> pred;
public StatefulPredicate(Predicate<T> pred) {
this.pred = pred;
this.alreadyFiltered = false;
}
@Override
public boolean test(T t) {
if(alreadyFiltered) {
return true;
}
boolean result = pred.test(t);
alreadyFiltered = !result;
return result;
}
}
Stream.of(1, -1, 3, -4, -5, 6)
.filter(new StatefulPredicate<>(i -> i > 0))
.forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)
印刷:1, 3, -4, -5, 6
如果并发是一个问题,您可以使用原子布尔值。
如果您希望跳过 1 个以上元素,请将该参数添加到您的构造函数中并在其中构建您的逻辑StatefulPredicate
该谓词过滤第一个负元素,然后让所有其他元素通过,无论如何。在你的情况下你应该测试instanceof Pear
由于人们对过滤器无状态表示担忧,从文档中可以看出:
中间操作又分为无状态操作和有状态操作。无状态操作(例如过滤器和映射)在处理新元素时不保留先前看到的元素的状态- 每个元素都可以独立于其他元素上的操作进行处理。有状态操作(例如不同和排序)可以在处理新元素时合并先前看到的元素的状态。
该谓词不保留有关先前看到的元素的信息。它保留有关先前结果的信息。
还可以使其线程安全以避免并发问题。
| 归档时间: |
|
| 查看次数: |
915 次 |
| 最近记录: |