我在Java流操作中遇到了边缘情况...
我想编码以下行为:“从任意一篮子水果中收集20个最小的,除了最小的梨,因为我们不希望这样。”
额外奖励:来的篮子可能根本没有任何梨。
示例:
到目前为止,我正迈出这一步:
output = basket.stream()
.sorted(Comparator.comparing(Fruit::getSize))
//.filter(???)
.limit(20)
.collect(fruitCollector);
这似乎是有状态 lambda过滤器的情况,我不知道该怎么做。
我不能使用本地firstPear
布尔值并在过滤第一个梨之后将其设置为true
,因为lambda中的所有局部变量都必须是最终的。
最糟糕的情况我可以将篮子分成两个,梨和非梨,对梨进行分类,如果有的话,将它们适当地分类。这似乎非常低效和丑陋。 有更好的方法吗?
这里发布的答案有很多种,其中大多数都是有效的。为了回馈社区,我整理了一个small testing harness来比较这些算法的性能。
这种比较并没有我想要的那么广泛 - 已经有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.
由于其良好的性能和“黑盒”功能(状态管理代码在外部类中,并且贡献者可以专注于它),因此我认为这是我们在项目中实现的那个问题。商业逻辑)。
请注意,接受的答案可能不是最适合您的:查看其他答案,或查看my testing project以便亲自查看。
答案 0 :(得分:8)
您是否考虑过直接的方法?找到最小的梨,将其过滤掉(如果存在)并收集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());
答案 1 :(得分:7)
据我所知,这已经写了 custom ,所以我在这里尝试了一个自定义收藏家:
Configure<T>
使用方法是:
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);
}
答案 2 :(得分:5)
为了便于实施,我附上了一个示例:
class Fruit {
String name;
Long size;
}
以下内容可行:
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());
};
和
output = basket.stream()
.sorted(Comparator.comparing(Fruit::getSize))
.limit(21)
.sorted(fruitComparator)
.limit(20)
.sorted(Comparator.comparing(Fruit::getSize))
.collect(fruitCollector);
我的比较器会将最小的Peach放到第21位,保持其他Fruit
的顺序自然,所以如果没有Peach
,它将返回第21个最大的元素。然后我按正常顺序对其余部分进行排序。
这会奏效。这是一个黑客,在某些情况下可能是一个糟糕的选择。我想指出,排序20个元素应该不是问题。
答案 3 :(得分:3)
关键行动是按照最小梨的方式按类型和大小排序 先走了。这样的事情:
// create a dummy pear; size value does not matter as comparing by ref
final Pear dummy = new Pear(-1);
basket
// mix basket with the dummy pear
.concat(basket, Stream.of(dummy))
// sort by type so pears go first, then by size
.sorted(Comparator
.<Fruit>comparingInt(
// arrange the dummy to always be the last
// among other pears but before other types
f -> (f == dummy ?
0 :
(Pear.class.equals(f.getClass()) ? -1 : 1))
)
.thenComparing(f -> f.size)
)
// skip the smallest pear
.skip(1)
// filter out the dummy
.filter(f -> f != dummy)
// sort again the rest by size
.sorted(Comparator.comparingInt(f -> f.size))
// take 20 at max
.limit(20);
答案 4 :(得分:2)
您可以使用有状态谓词:
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);
打印:1, 3, -4, -5, 6
如果并发是一个问题,你可以使用原子布尔值。
如果您希望跳过多个元素,请将该参数添加到构造函数中,并在StatefulPredicate
此谓词过滤第一个负面元素,然后让所有其他元素都通过,无论如何。在您的情况下,您应该测试instanceof Pear
由于人们表示担心过滤器无状态,请从文档中
中间操作进一步分为无状态操作和有状态操作。无状态操作(例如过滤器和映射)在处理新元素时保留之前看到的元素的无状态 - 可以独立于其他元素上的操作处理每个元素。有状态操作(例如distinct和sorted)可以在处理新元素时包含先前看到的元素的状态。
该谓词不保留以前看到的元素的信息。它保留了有关以前结果的信息。
此外,它可以使线程安全,以避免并发问题。
答案 5 :(得分:1)
不要尝试提前过滤。考虑
List<Fruit> output = basket.stream()
.sorted(Comparator.comparing(Fruit::getSize))
.limit(21)
.collect(Collectors.toCollection(ArrayList::new));
int index = IntStream.range(0, output.size())
.filter(ix -> output.get(ix).isPear())
.findFirst().orElse(20);
if(index < output.size()) output.remove(index);
仅限21
元素而不是20
才能删除一个元素。通过使用Collectors.toCollection(ArrayList::new)
,您可以确保收到可变集合。
然后,有三种情况
该列表包含Pear
。由于列表按水果大小排序,因此第一个Pear
也将是最小的Pear
,这是必须删除的… .findFirst()
。随后的Pear
将评估元素的索引。
该列表不包含21
,但其大小为20
。在这种情况下,我们必须删除最后一个元素,即索引.orElse(20)
,以获得所需的结果大小。这是由OptionalInt
提供的,它会将空20
映射到Pear
。
该列表可能不包含任何21
且小于remove
,因为源列表已经较小。在这种情况下,我们不会移除任何元素,通过在if(index < output.size())
前加21
操作进行检查。
正如我们之前已经知道的那样,这整个后处理可以被认为与性能无关,它将被应用于一个非常小的列表,在这个例子中最多只有basket
个元素。这与源- name: My Great Playbook
hosts: all
gather_facts: False
accelerate: False
strategy: free
vars:
dbname: "@DBNAME@"
repldbname: "connect to mydb"
tasks:
- block:
- name: finding fl
find:
paths: "/home/username1/temp"
patterns: "*.sql"
file_type: "file"
register: repos
- name: some thing
debug: msg="{{ item }}"
with_items: "{{ repos.files }}"
- name: replacing string
replace:
path: "{{ item }}"
#path: "/home/username1/temp/1.sql"
regexp: ({{ dbname }})
replace: '{{ repldbname }}'
backup: no
unsafe_writes: yes
with_items: "{{ repos.files }}"
列表的大小无关。
答案 6 :(得分:0)
[更新],在阅读更新后的OP后,我对需求有了更好的理解:以下是StreamEx的更新代码:
Optional<Integer> smallestPear = StreamEx.of(basket).filter(Fruit::isPear)
.mapToInt(Fruit::getSize).min();
StreamEx.of(basket)
.chain(s -> smallestPear.map(v -> s.remove(f -> f.isPear() && f.getSize() == v).orElse(s))
.sortedBy(Fruit::getSize).limit(20).toList();
[再次更新] 上述解决方案与Misha提供的解决方案非常相似。如果你不想经过两次流,如果篮子中的(水果类型,大小)对是唯一的话,这是另一种有限谓词的解决方案:
// Save this method in your toolkit.
public class Fn {
public static <T> Predicate<T> limited(final Predicate<T> predicate, final int limit) {
Objects.requireNonNull(predicate);
return new Predicate<T>() {
private final AtomicInteger counter = new AtomicInteger(limit);
@Override
public boolean test(T t) {
return predicate.test(t) && counter.decrementAndGet() >= 0;
}
};
}
}
StreamEx.of(basket).sortedBy(Fruit::getSize)
.remove(f -> Fn.limited(Fruit::isPear, 1))
.limit(20).toList();
答案 7 :(得分:0)
我认为 ID Date v3 v4
1 2015.01.01 a 5
1 2015.02.01 b 5
1 2015.03.01 f 1
1 2015.04.01 z 5
1 2015.05.01 a 2
1 2015.06.01 a 2
2 2013.03.01 a 6
2 2013.04.01 a 2
2 2013.05.01 g 13
2 2013.06.01 a 2
2 2013.07.01 e 8
2 2013.08.01 h 9
2 2013.09.01 h 9
是您操作的原子操作符。因此,最简单的方法是编写自己的Predicate
来包装原始的Predicate
。假设名为Predicate
的换行符,那么您的代码可以简化为如下所示:
once
output = basket.stream().sorted(comparing(Fruit::getSize))
.filter(once(Fruit::isPear))
.limit(20).collect(fruitCollector);
如果您想支持并发,则可以改为使用static <T> Predicate<T> once(Predicate<T> predicate){
boolean[] seen = {true};
return it -> !seen[0] || (seen[0]=predicate.test(it));
}
,例如:
AtomicInteger
答案 8 :(得分:0)
我遇到了同样的问题,但是我自己使用了Map和忽略列表来解决了。这是供您参考的示例。希望能有所帮助。
@Test
public void testGetStckTraceElements() {
StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
// define a list for filter out
List<String> ignoreClasses = Arrays.asList(
Thread.class.getName(),
this.getClass().getName()
);
// Map is using for check found before or not
Map<String,Boolean> findFrist = new HashMap<String,Boolean>();
Arrays.asList(stElements).stream()
.filter(s -> {
Platform.print("check: {}", s.getClassName());
if (Optional.ofNullable(findFrist.get(s.getClassName())).orElse(false)) {
return true;
}
findFrist.put(s.getClassName(), true);
for (String className:ignoreClasses) {
if (s.getClassName().equals(className)) return false;
}
return true;
})
.forEach(s->{
Platform.print("Result: {} {} {} {}", s.getClassName(), s.getMethodName(), s.getFileName(), s.getLineNumber());
});
}
答案 9 :(得分:-1)
这样的事情可能有用(但是如你所提到的那样分成2个篮子)
Function<Fruit, Boolean> isPear = f -> f.getType().equals("Pear");
Comparator<Fruit> fruitSize = Comparator.comparing(Fruit::getSize);
Map<Boolean, List<Fruit>> pearsAndOthers = basket.sorted(fruitSize).limit(21).collect(Collectors.groupingBy(isPear));
List<Fruit> pears = pearsAndOthers.get(true);
List<Fruit> others = pearsAndOthers.get(false);
Stream<Fruit> result;
if (pears.size() == 0) {
result = others.stream().limit(20);
} else if (pears.size() == 1) {
result = others.stream();
} else {
// You can probably merge in a nicer fashion since they should be sorted
result = Stream.concat(pears.stream().skip(1), others.stream()).sorted(fruitSize);
}