Rxjs 可观察的,将groupby的行为与CombineTest相结合,并显示“外部连接”行为

Rxjs 可观察的,将groupby的行为与CombineTest相结合,并显示“外部连接”行为,rxjs,rx-java,reactive-programming,system.reactive,Rxjs,Rx Java,Reactive Programming,System.reactive,我正试图使用反应范式来创建一个可观察的模型,它的行为就像group by和CombineTest的组合。我有两个具有共享连接键的源观测值,如下面两个数据结构中所示 class Foo { string Key; string Funky; } class Bar { string Key; string Town; } 我想要的是一个可观察的,它能让我得到这两个在InstrumentID上连接的最新组合。最终结果应该类似于: class Target {

我正试图使用反应范式来创建一个可观察的模型,它的行为就像group by和CombineTest的组合。我有两个具有共享连接键的源观测值,如下面两个数据结构中所示

class Foo
{
    string Key;
    string Funky;
}

class Bar
{
    string Key;
    string Town;
}
我想要的是一个可观察的,它能让我得到这两个在InstrumentID上连接的最新组合。最终结果应该类似于:

class Target
{
   string Key;
   string Funky;
   string Town;
}
并表现出类似外部连接的行为,这意味着产生新键的第一个序列将产生另一侧为null的目标类,然后一旦另一侧也产生相同的连接键,只要给定键的任意一个序列中有新值,就会产生双方的最新值。

假设您的foo$流发出foo类型的值,bar$流发出bar类型的值。 以下是如何将它们结合起来:

combineLatest([
  foo$,
  bar$
    // use startWith(null) to ensure combineLatest will emit as soon as foo$ emits, not waiting for bar$ to emit its first value
    .pipe(startWith(null))
]).pipe(
  map(([foo, bar]) => ({
    // always keep all properties from foo
    ...foo,
    // only add properties from bar if it has the matching Key
    ...(bar && bar.Key === foo.Key ? bar : null)
  }))
)

从某些标准来看,这可能并不舒适,但却能满足我的需要。为寻找相同功能的任何人发布.NET版本的RX

公共静态类扩展 { 公共静态IObservable组合测试分组 首先,这是可以观察到的, 可观测秒, Func firstKeySelector, Func secondKeySelector, Func结果选择器 { var dic=新的ConcurrentDictionary; 返回可观察的。Createobs=> { 变量d1=第一个 .Selectx=> { var key=firstKeySelectorx; var tuple=dic.AddOrUpdate 钥匙 addValueFactory:key=>Tuple.Createx,defaultTSecond, updateValueFactory:key,existing=>Tuple.Createx,existing.Item2; 返回结果selectworkey,tuple.Item1,tuple.Item2; } .订阅; var d2=秒 .Selectx=> { var key=secondKeySelectorx; var tuple=dic.AddOrUpdate 钥匙 addValueFactory:key=>Tuple.CreatedefaultTFirst,x, updateValueFactory:key,existing=>Tuple.Createexisting.Item1,x; 返回结果selectworkey,tuple.Item1,tuple.Item2; } .订阅; 返回新的CompositeDisposabled1,d2; }; } }
在我看来,您希望:

一个新的键将产生一个目标类,另一端为null

当左侧或右侧输入一个新键prev:null或different

,然后一旦另一侧也产生相同的连接键

前提条件:一个流发出一个值-另一个流现在发出一个值和左右eq的键

只要给定键的任一序列中有新值,就会产生双方的最新值

发射由左、右、左、右组成的完整目标发射,当左、右的值发生明显变化时

我假设的RxJava2解决方案:

  @Test
  void test2() {
    PublishSubject<Foo> foo$ = PublishSubject.create();
    PublishSubject<Bar> bar$ = PublishSubject.create();

    Observable<Target> target$ = Observable.merge(Arrays.asList(foo$, bar$))
        // filter invalid values
        .filter(hasId -> hasId.key() != null)
        .scan(Target.NULL, (prev, change) -> {
          // when prev. target and current value#key are eq -> emit composed value
          if (change.key().equals(prev.key)) {
            return composedTarget(prev, change);
          } else if (change instanceof Foo) {
            return Target.fromFoo((Foo) change);
          } else if (change instanceof Bar) {
            return Target.fromBar((Bar) change);
          }
          return prev;
        }).filter(target -> target != Target.NULL)
        .distinctUntilChanged();

    TestObserver<Target> test = target$.test();

    // emit
    foo$.onNext(new Foo("123", "f1"));
    // emit
    bar$.onNext(new Bar("123", "f2"));
    // emit
    bar$.onNext(new Bar("123", "f3"));
    // skipped
    foo$.onNext(new Foo("123", "f1"));
    // emit
    foo$.onNext(new Foo("123", "f5"));
    // emit
    foo$.onNext(new Foo("key", "value"));
    // emit
    foo$.onNext(new Foo("key2", "value2"));
    // emit
    bar$.onNext(new Bar("bar2", "Berlin"));
    // emit
    foo$.onNext(new Foo("foo2", "Funkeey"));

    test.assertValues(
        new Target("123", "f1", null),
        new Target("123", "f1", "f2"),
        new Target("123", "f1", "f3"),
        new Target("123", "f5", "f3"),
        new Target("key", "value", null),
        new Target("key2", "value2", null),
        new Target("bar2", null, "Berlin"),
        new Target("foo2", "Funkeey", null)
    );
  }

  private Target composedTarget(Target prev, HasId change) {
    if (change instanceof Foo) {
      Foo foo = (Foo) change;
      return new Target(prev.key, foo.funky, prev.town);
    }
    if (change instanceof Bar) {
      Bar bar = (Bar) change;
      return new Target(prev.key, prev.funky, bar.town);
    }
    return prev;
  }

如果我错了,请纠正我的假设。通过模式匹配,可以更好地在C中实现该解决方案。实际上,如果C有F这样的并集类型,那就最好了。

您能指定哪个外部联接是完全的、左的、右的吗。此外,请提供一个大理石图,以便更清晰地查看,因为我不了解最后一部分。[system.reactive]标记特定于Rx的c实现。@Enigmativity,你的观点是什么?rx java标记特定于rx的java实现。@HansWurst-我是说用[system.reactive]标记它只与.Net相关,并且只有当语言是c或VB.Net或.Net系列的另一种语言时才应该这样标记。请注意,它似乎会泄漏内存,因为dic会随着时间积累越来越多的项,直到第一个流和第二个流都完成后才会处理。如果我错了,请纠正我,但似乎只有当具有匹配键的Foo和Bar值在给定的if change.key.equalsprev.key下依次发出时,此解决方案才有效。我想我在测试中得到了所有不变量。您能提供您的测试用例以及期望发出的值吗?我认为这只是对OP语句的不同解释:一个新的键将产生一个目标类,另一端为null。我读到的是以前没有看到过的键,而你读到的是与以前发出的键不同的键。@TrogDor,你完全正确。我打开每个发射,其中的关键点不匹配。您的解释对于按分组的主题名称更有意义。这就是为什么我要一个弹珠图。关于你的解释,我将设法解决这个问题。
  interface HasId {
    String key();
  }

  static final class Foo implements HasId {
    final String key;
    final String funky;

    Foo(String key, String funky) {
      this.key = key;
      this.funky = funky;
    }

    @Override
    public String key() {
      return key;
    }
  }

  static final class Bar implements HasId {
    String key;
    String town;

    Bar(String key, String town) {
      this.key = key;
      this.town = town;
    }

    @Override
    public String key() {
      return key;
    }
  }

  static final class Target {
    private static final Target NULL = new Target(null, null, null);
    final String key;
    final String funky;
    final String town;

    Target(String key, String funky, String town) {
      this.key = key;
      this.funky = funky;
      this.town = town;
    }

    static Target fromFoo(Foo foo) {
      return new Target(foo.key, foo.funky, null);
    }

    static Target fromBar(Bar bar) {
      return new Target(bar.key, null, bar.town);
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Target target = (Target) o;
      return key.equals(target.key) &&
          Objects.equals(funky, target.funky) &&
          Objects.equals(town, target.town);
    }

    @Override
    public int hashCode() {
      return Objects.hash(key, funky, town);
    }

    @Override
    public String toString() {
      return "Target{" +
          "key='" + key + '\'' +
          ", funky='" + funky + '\'' +
          ", town='" + town + '\'' +
          '}';
    }
  }