如何在不事先知道最高价值的情况下为具有最高价值的元素过滤发布者?
这里有一个小测试来说明我要达到的目标:
@Test
fun filterForHighestValuesTest() {
val numbers = Flux.just(1, 5, 7, 2, 8, 3, 8, 4, 3)
// what operators to apply to numbers to make the test pass?
StepVerifier.create(numbers)
.expectNext(8)
.expectNext(8)
.verifyComplete()
}
我从reduce运算符开始:
@Test
fun filterForHighestValuesTestWithReduce() {
val numbers = Flux.just(1, 5, 7, 2, 8, 3, 8, 4, 3)
.reduce { a: Int, b: Int -> if( a > b) a else b }
StepVerifier.create(numbers)
.expectNext(8)
.verifyComplete()
}
当然可以通过测试,但是只会发出一个Mono
,而我想获得一个Flux
,其中包含所有具有最高值的元素,例如在这个简单的示例中是8和8。
答案 0 :(得分:0)
首先,您需要为此状态,因此您需要谨慎设置每个订阅状态。确保在合并运算符时的一种方法是使用compose
。
Flux<Integer> allMatchingHighest = numbers.compose(f -> {
AtomicInteger highestSoFarState = new AtomicInteger(Integer.MIN_VALUE);
AtomicInteger windowState = new AtomicInteger(Integer.MIN_VALUE);
return f.filter(v -> {
int highestSoFar = highestSoFarState.get();
if (v > highestSoFar) {
highestSoFarState.set(v);
return true;
}
if (v == highestSoFar) {
return true;
}
return false;
})
.bufferUntil(i -> i != windowState.getAndSet(i), true)
.log()
.takeLast(1)
.flatMapIterable(Function.identity());
});
请注意,整个compose
lamdba都可以提取为方法,从而使代码使用方法引用并更具可读性。
该解决方案分4个步骤完成,其中前两个分别具有自己的AtomicInteger
状态:
filter
找出较小的元素。这样会导致Flux<Integer>
(单数)增加,例如1 5 7 8 8
。buffer
的数量相等。我们使用bufferUntil
而不是window*
或groupBy
,因为最退化的情况是数字都不同,并且已经排序的数字会失败takeLast(1)
)以外的所有缓冲区flatMapIterable
)出现的次数这通过发出StepVerifier
正确通过了您的8 8
测试。注意发出的中间缓冲区是:
onNext([1])
onNext([5])
onNext([7, 7, 7])
onNext([8, 8])
bufferUntil
一个更复杂的源,它将以groupBy
失败,但不是此解决方案:
Random rng = new Random();
//generate 258 numbers, each randomly repeated 1 to 10 times
//also, shuffle the whole thing
Flux<Integer> numbers = Flux
.range(1, 258)
.flatMap(i -> Mono.just(i).repeat(rng.nextInt(10)))
.collectList()
.map(l -> {
Collections.shuffle(l);
System.out.println(l);
return l;
})
.flatMapIterable(Function.identity())
.hide();
这是可以过滤到哪些缓冲区序列的一个示例(请记住,仅重播最后一个缓冲区):
onNext([192])
onNext([245])
onNext([250])
onNext([256, 256])
onNext([257])
onNext([258, 258, 258, 258, 258, 258, 258, 258, 258])
onComplete()
注意:如果删除map
会拖曳,则会获得“退化情况”,即使windowUntil
也不起作用(takeLast
会导致打开过多未消费的窗户)。
这是一个有趣的想法!
答案 1 :(得分:0)
一种实现方法是将int的通量映射到每个具有一个int的列表的通量,减少结果,并以flatMapMany结尾,即
final Flux<Integer> numbers = Flux.just(1, 5, 7, 2, 8, 3, 8, 4, 3);
final Flux<Integer> maxValues =
numbers
.map(
n -> {
List<Integer> list = new ArrayList<>();
list.add(n);
return list;
})
.reduce(
(l1, l2) -> {
if (l1.get(0).compareTo(l2.get(0)) > 0) {
return l1;
} else if (l1.get(0).equals(l2.get(0))) {
l1.addAll(l2);
return l1;
} else {
return l2;
}
})
.flatMapMany(Flux::fromIterable);
答案 2 :(得分:0)
一个对我有用的简单解决方案-
Flux<Integer> flux =
Flux.just(1, 5, 7, 2, 8, 3, 8, 4, 3).collectSortedList(Comparator.reverseOrder()).flatMapMany(Flux::fromIterable);
StepVerifier.create(flux).expectNext(8).expectNext(8).expectNext(7).expectNext(5);
答案 3 :(得分:-1)
一种可行的解决方案是在归约之前对Flux
进行分组,然后对GroupedFlux进行平面映射,如下所示:
@Test
fun filterForHighestValuesTest() {
val numbers = Flux.just(1, 5, 7, 2, 8, 3, 8, 4, 3)
.groupBy { it }
.reduce { t: GroupedFlux<Int, Int>, u: GroupedFlux<Int, Int> ->
if (t.key()!! > u.key()!!) t else u
}
.flatMapMany {
it
}
StepVerifier.create(numbers)
.expectNext(8)
.expectNext(8)
.verifyComplete()
}