是否有Rx运算符仅在流2发出事物时才组合最新的流1和2?

时间:2014-08-19 02:47:51

标签: system.reactive reactive-programming rx-java

这是我尝试绘制大理石图 -

STREAM 1 = A----B----C---------D------> (magical operator) STREAM 2 = 1----------2-----3-----4---> STREAM 3 = 1A---------2C----3C----4D-->

我基本上在寻找从流1和流2生成流3的东西。基本上,每当从流2发出某些东西时,它就会将它与流1中的最新内容相结合。combineLatest类似于我想要的但是我只想要从流2发出的东西从流2发出,而不是流1。这样的运营商是否存在?

6 个答案:

答案 0 :(得分:5)

有一个运算符可以满足您的需要:sample的一个重载需要另一个可观察而不是持续时间作为参数。文档在这里:https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#sample-or-throttlelast

用法(我将在scala中给出示例):

import rx.lang.scala.Observable
import scala.concurrent.duration
import duration._

def o = Observable.interval(100.milli)
def sampler = Observable.interval(180.milli)

// Often, you just need the sampled observable
o.sample(sampler).take(10).subscribe(x ⇒ println(x +  ", "))
Thread.sleep(2000)
// or, as for your use case
o.combineLatest(sampler).sample(sampler).take(10).subscribe(x ⇒ println(x +  ", "))
Thread.sleep(2000)

输出:

0, 
2, 
4, 
6, 
7, 
9, 
11, 
13, 
15, 
16, 

(2,0), 
(4,1), 
(6,2), 
(7,3), 
(9,4), 
(11,5), 
(13,6), 
(15,7), 
(16,8), 
(18,9),

有一个轻微的问题,即吞下了来自采样的observable的重复条目(参见https://github.com/ReactiveX/RxJava/issues/912的讨论)。除此之外,我认为这正是你所寻找的。

答案 1 :(得分:3)

据我所知,现有的单一运营商不会做你想做的事情。但是,您可以使用CombineLatestDistinctUntilChanged进行撰写,如下所示:

var joined = Observable.CombineLatest(sourceA, sourceB, (a,b) => new { A = a, B = b })
  .DistinctUntilChanged(pair => pair.B);

编辑:

只要STREAM 1的值每次都改变,上述操作就会起作用。如果他们不这样做,那么请使用以下内容,但不太清楚,但适用于所有情况(我已经测试过)。

var joined = Observable.Join(
    sourceB,
    sourceA,
    _ => Observable.Return(Unit.Default),
    _ => sourceA,
    (a, b) => new { A = a, B = b });

Join运算符对我来说永远不会直观,我发现的最佳解释是here

回答@ Matthew的评论

var buttonClicks = Observable.FromEventPattern<MouseButtonEventArgs>(this,
    "MouseLeftButtonDown")
    .Select(_ => Unit.Default);

var sequence = Observable.Interval(TimeSpan.FromSeconds(1));

var joined = Observable.Join(
  buttonClicks,
  sequence,
  _ => Observable.Return(Unit.Default),
  _ => sequence,
  (b, s) => s); // No info in button click here

答案 2 :(得分:3)

这是一个相当简单的方法:

var query = stream2.Zip(
   stream1.MostRecent(' '),
   (s2,s1) => string.Format("{0}{1}", s2, s1));

MostRecent可以提供“零”值,该值在事件流1尚未发出时使用。对于引用类型,这可能为null,但我为stream1使用了char,因此提供了一个空格。

答案 3 :(得分:2)

withLatestFrom似乎完全符合我的要求 - http://rxmarbles.com/#withLatestFrom

答案 4 :(得分:0)

我认为Switch运算符是关键所在。

试试这个:

var query =
    stream1
        .Select(s1 => stream2.Select(s2 => new { s1, s2 }))
        .Switch();

以下测试代码:

query
    .Select(s => String.Format("{0}{1}", s.s2, s.s1))
    .Subscribe(Console.WriteLine);

stream1.OnNext('A');
stream2.OnNext(1);
stream1.OnNext('B');
stream1.OnNext('C');
stream2.OnNext(2);
stream2.OnNext(3);
stream1.OnNext('D');
stream2.OnNext(4);

给出了这些结果:

1A
2C
3C
4D

如果这是正确的,请告诉我。

答案 5 :(得分:0)

解决方案

    public static IObservable<TR> Sample<TSource, TSampler, TR>
        (this IObservable<TSource> source,
         IObservable<TSampler> sampler,
         Func<TSource, TSampler, TR> combiner)
    {
        return source.Publish
           (rs => sampler
               .Zip
                 ( rs.MostRecent(default(TSource))
                 , (samplerElement, sourceElement)
                     => combiner(sourceElement, samplerElement)
                 )
               .SkipUntil(rs)
           );
    } 

有一个测试用例,因为这样的事情很难做到。

public class SampleSpec : ReactiveTest
{
    TestScheduler _Scheduler = new TestScheduler();

    [Fact]
    public void ShouldWork()
    {
        var sampler = _Scheduler.CreateColdObservable
            ( OnNext(10, "A")
            , OnNext(20, "B")
            , OnNext(30, "C")
            , OnNext(40, "D")
            , OnNext(50, "E")
            , OnNext(60, "F")
            );

        var source = _Scheduler.CreateColdObservable
            ( Enumerable
                .Range(5,100)
                .Where(i=>i%10!=0)
                .Select(i=>OnNext(i,i)).ToArray());

        var sampled = source.Sample
            (sampler, Tuple.Create);

        var actual = _Scheduler.Start
            (() =>
             sampled
             , created: 0
             , subscribed: 1
             , disposed: 1000);

        actual.Messages.Count()
              .Should()
              .Be(6);

        var messages = actual.Messages.Take(6)
                             .Select(v => v.Value.Value)
                             .ToList();

        messages[0].Should().Be(Tuple.Create(9,"A"));
        messages[1].Should().Be(Tuple.Create(19,"B"));
        messages[2].Should().Be(Tuple.Create(29, "C"));
        messages[3].Should().Be(Tuple.Create(39, "D"));
        messages[4].Should().Be(Tuple.Create(49, "E"));
        messages[5].Should().Be(Tuple.Create(59, "F"));

    }
}