Java 流:首先查找多个过滤谓词
Java Streams: Find first for multiple filter predicates
我有一个 Collection<Product>
和一个 Collection<Predicate<Product>>
。谓词是布尔标志的简单组合。例如:
| isNew | isSoldOut
--------------------------------
predicate 1: | false | false
predicate 2: | false | true
predicate 3: | true | false
predicate 4: | true | true
我想找到“每一种”,虽然我想找到产品集合中每个谓词的第一个匹配项。
目前我的代码是这样的:
List<Product> products = getProducts();
List<Predicate<Product>> predicates = getPredicates();
List<Product> result = predicates.stream()
.flatMap(predicate -> products.stream().filter(predicate).findFirst().stream())
.collect(Collectors.toList());
但这当然会多次迭代产品集合,这是不希望的,因为在我的例子中,我有 100000 个产品和 64 个谓词,这需要很长时间。
在我的特殊情况下,谓词是互斥的:如果谓词 returns 为真,则可以跳过该特定产品的所有其他谓词。而且因为我使用 findFirst
这个谓词然后可以跳过所有其他产品。
我想知道是否可以迭代 Product Collection,并针对所有 Predicates 对每个 Product 只检查一次。
如果我没理解错的话,你正在寻找类似这样的东西:
List<Product> results = products.stream()
.filter(prod -> predicates.stream()
.anyMatch(pred -> pred.test(prod)))
.collect(Collectors.toList());
反过来做怎么样?流式传输产品,并对其应用谓词。
List<Predicate<Product>> predicates = getPredicates();
List<Product> products = getProducts();
List<Product> filtered = products.stream().filter(product -> {
Iterator<Predicate<Product>> iterator = predicates.iterator();
while (iterator.hasNext()) {
Predicate<Product> currentPredicate = iterator.next();
if (currentPredicate.test(product)) {
iterator.remove();
return true;
}
}
return false;
}).collect(Collectors.toList());
缺点是您必须小心将哪个集合用于谓词,Iterator.remove
并不总是受支持。
编辑: 看来我没有仔细阅读。我认为最好用循环获得每一个。
List<Product> products = getProducts();
List<Predicate<Product>> predicates = getPredicates();
List<Product> matchingProducts = new ArrayList<>(predicates.size());
for (Product product : products) {
if (predicates.isEmpty()) {
break;
}
for (int predicateIndex = 0; predicateIndex < predicates.size(); predicateIndex++) {
Predicate<Product> predicate = predicates.get(predicateIndex);
if (predicate.test(product)) {
matchingProducts.add(product);
predicates.remove(predicateIndex);
break;
}
}
}
实际上是通过流实现的,takeWhile
,你是对的,本杰明。
List<Predicate<Product>> predicates = getPredicates();
List<Product> products = getProducts();
List<Product> matches = products.stream()
.takeWhile(product -> !predicates.isEmpty())
.filter(product -> {
Iterator<Predicate<Product>> iterator = predicates.iterator();
while (iterator.hasNext()) {
if (iterator.next().test(product)) {
iterator.remove();
return true;
}
}
return false;
})
.collect(Collectors.toList());
只需确保 takeWhile
在 filter
之前,否则将跳过最后一个匹配元素。
您当前的解决方案将多次遍历集合,但由于 findFirst
是一个 short-circuiting 运算符,它会在找到匹配项后立即停止。你有bench-marked它来确保它不够好吗?
另一种方法是使用 状态过滤器(参见 this post 的最佳答案):
public static Predicate<Product> matchAndDiscard(final List<Predicate<Product>> predicates) {
final Set<Predicate<Product>> remaining = new HashSet<>(predicates);
return product -> {
final var match = remaining.stream().filter(pred -> pred.test(product)).findFirst();
match.ifPresent(remaining::remove);
return match.isPresent();
};
}
很像@Chaosfire 的方法,但状态包含在过滤器函数中。如果您认为所有谓词都会被至少一种产品匹配,您还可以通过将流限制为谓词的数量来节省一些时间,如下所示:
final var predicates = getPredicates()
final var result = getProducts().stream()
.filter(matchAndDiscard(predicates))
.limit(predicates.size())
.toList()
在您当前的解决方案中,您将“水平”遍历产品:
--> products
pred1: ffffffffffffft
pred2: fffft
pred3: ffffffffffffffft
pred4: ft
etc.
替代方案将进行“垂直”遍历:
products
pred1: | ffffffffffffft
pred2: | fffft
pred3: v ffff fffffffffft
pred4: ft
所以一个比另一个快得多并不明显,这取决于特定的配置。
我有一个 Collection<Product>
和一个 Collection<Predicate<Product>>
。谓词是布尔标志的简单组合。例如:
| isNew | isSoldOut
--------------------------------
predicate 1: | false | false
predicate 2: | false | true
predicate 3: | true | false
predicate 4: | true | true
我想找到“每一种”,虽然我想找到产品集合中每个谓词的第一个匹配项。
目前我的代码是这样的:
List<Product> products = getProducts();
List<Predicate<Product>> predicates = getPredicates();
List<Product> result = predicates.stream()
.flatMap(predicate -> products.stream().filter(predicate).findFirst().stream())
.collect(Collectors.toList());
但这当然会多次迭代产品集合,这是不希望的,因为在我的例子中,我有 100000 个产品和 64 个谓词,这需要很长时间。
在我的特殊情况下,谓词是互斥的:如果谓词 returns 为真,则可以跳过该特定产品的所有其他谓词。而且因为我使用 findFirst
这个谓词然后可以跳过所有其他产品。
我想知道是否可以迭代 Product Collection,并针对所有 Predicates 对每个 Product 只检查一次。
如果我没理解错的话,你正在寻找类似这样的东西:
List<Product> results = products.stream()
.filter(prod -> predicates.stream()
.anyMatch(pred -> pred.test(prod)))
.collect(Collectors.toList());
反过来做怎么样?流式传输产品,并对其应用谓词。
List<Predicate<Product>> predicates = getPredicates();
List<Product> products = getProducts();
List<Product> filtered = products.stream().filter(product -> {
Iterator<Predicate<Product>> iterator = predicates.iterator();
while (iterator.hasNext()) {
Predicate<Product> currentPredicate = iterator.next();
if (currentPredicate.test(product)) {
iterator.remove();
return true;
}
}
return false;
}).collect(Collectors.toList());
缺点是您必须小心将哪个集合用于谓词,Iterator.remove
并不总是受支持。
编辑: 看来我没有仔细阅读。我认为最好用循环获得每一个。
List<Product> products = getProducts();
List<Predicate<Product>> predicates = getPredicates();
List<Product> matchingProducts = new ArrayList<>(predicates.size());
for (Product product : products) {
if (predicates.isEmpty()) {
break;
}
for (int predicateIndex = 0; predicateIndex < predicates.size(); predicateIndex++) {
Predicate<Product> predicate = predicates.get(predicateIndex);
if (predicate.test(product)) {
matchingProducts.add(product);
predicates.remove(predicateIndex);
break;
}
}
}
实际上是通过流实现的,takeWhile
,你是对的,本杰明。
List<Predicate<Product>> predicates = getPredicates();
List<Product> products = getProducts();
List<Product> matches = products.stream()
.takeWhile(product -> !predicates.isEmpty())
.filter(product -> {
Iterator<Predicate<Product>> iterator = predicates.iterator();
while (iterator.hasNext()) {
if (iterator.next().test(product)) {
iterator.remove();
return true;
}
}
return false;
})
.collect(Collectors.toList());
只需确保 takeWhile
在 filter
之前,否则将跳过最后一个匹配元素。
您当前的解决方案将多次遍历集合,但由于 findFirst
是一个 short-circuiting 运算符,它会在找到匹配项后立即停止。你有bench-marked它来确保它不够好吗?
另一种方法是使用 状态过滤器(参见 this post 的最佳答案):
public static Predicate<Product> matchAndDiscard(final List<Predicate<Product>> predicates) {
final Set<Predicate<Product>> remaining = new HashSet<>(predicates);
return product -> {
final var match = remaining.stream().filter(pred -> pred.test(product)).findFirst();
match.ifPresent(remaining::remove);
return match.isPresent();
};
}
很像@Chaosfire 的方法,但状态包含在过滤器函数中。如果您认为所有谓词都会被至少一种产品匹配,您还可以通过将流限制为谓词的数量来节省一些时间,如下所示:
final var predicates = getPredicates()
final var result = getProducts().stream()
.filter(matchAndDiscard(predicates))
.limit(predicates.size())
.toList()
在您当前的解决方案中,您将“水平”遍历产品:
--> products
pred1: ffffffffffffft
pred2: fffft
pred3: ffffffffffffffft
pred4: ft
etc.
替代方案将进行“垂直”遍历:
products
pred1: | ffffffffffffft
pred2: | fffft
pred3: v ffff fffffffffft
pred4: ft
所以一个比另一个快得多并不明显,这取决于特定的配置。