如何为所有具有最高值的元素过滤通量

时间:2018-12-14 11:34:32

标签: spring project-reactor

如何在不事先知道最高价值的情况下为具有最高价值的元素过滤发布者?

这里有一个小测试来说明我要达到的目标:

@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。

4 个答案:

答案 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状态:

  1. 逐步找到新的“最高”元素(到目前为止),并filter找出较小的元素。这样会导致Flux<Integer>(单数)增加,例如1 5 7 8 8
  2. buffer的数量相等。我们使用bufferUntil而不是window*groupBy,因为最退化的情况是数字都不同,并且已经排序的数字会失败
  3. 跳过除一个(takeLast(1))以外的所有缓冲区
  4. “重播”最后一个缓冲区,它代表我们的最高值(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()
}