在前一个元素符合条件之后获取流的第一个元素

时间:2018-08-21 08:44:11

标签: c# .net rxjs reactive-programming system.reactive

我是反应性扩展(rx)的新手,并尝试在.NET中执行以下操作(但在JS和其他语言中应相同)。

我有一个带有包含字符串和bool属性的对象的流。流将是无限的。我具有以下条件:

  • 应该始终打印第一个对象。
  • 现在应跳过所有传入的对象,直到将bool属性设置为“ true”的对象到达为止。
  • 当一个对象的bool属性设置为“ true”时,应跳过该对象,但应打印下一个对象(无论这些属性是什么)。
  • 现在这种方式继续进行,应该打印出属性设置为true的对象之后的每个对象。

示例:

("one", false)--("two", true)--("three", false)--("four", false)--("five", true)--("six", true)--("seven", true)--("eight", false)--("nine", true)--("ten", false)

预期结果:

"one"--"three"--"six"--"seven"--"eight"--"ten"

请注意,已经打印了“六个”和“七个”,因为它们跟随属性设置为true的对象,即使它们自己的属性也设置为“ true”。

一个简单的.NET程序对其进行测试:

using System;
using System.Reactive.Linq;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        class Result
        {
            public bool Flag { get; set; }
            public string Text { get; set; }
        }

        static void Main(string[] args)
        {               
            var source =
               Observable.Create<Result>(f =>
               {
                   f.OnNext(new Result() { Text = "one", Flag = false });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "two", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "three", Flag = false });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "four", Flag = false });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "five", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "six", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "seven", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "eight", Flag = false });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "nine", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "ten", Flag = false });

                   return () => Console.WriteLine("Observer has unsubscribed");
               });
        }
    }
}

我尝试使用.Scan.Buffer扩展名,但我不知道在我的场景中如何正确使用它们。

性能当然应该尽可能的好,因为最终流将是无限的。

4 个答案:

答案 0 :(得分:3)

尝试这种方法:

var results = new[]
{
    new Result() { Text = "one", Flag = false },
    new Result() { Text = "two", Flag = true },
    new Result() { Text = "three", Flag = false },
    new Result() { Text = "four", Flag = false },
    new Result() { Text = "five", Flag = true },
    new Result() { Text = "six", Flag = true },
    new Result() { Text = "seven", Flag = true },
    new Result() { Text = "eight", Flag = false },
    new Result() { Text = "nine", Flag = true },
    new Result() { Text = "ten", Flag = false },
};

IObservable<Result> source =
    Observable
        .Generate(
            0, x => x < results.Length, x => x + 1,
            x => results[x],
            x => TimeSpan.FromSeconds(1.0));

与您的source方法相比,以上方法只是更惯用地产生了Observable.Create<Result>

现在是query

IObservable<Result> query =
    source
        .StartWith(new Result() { Flag = true })
        .Publish(ss =>
            ss
                .Skip(1)
                .Zip(ss, (s1, s0) =>
                    s0.Flag
                    ? Observable.Return(s1) 
                    : Observable.Empty<Result>())
                .Merge());

此处使用.Publish允许可观察的源仅具有一个订阅,但是在.Publish方法中可以多次使用它。然后可以使用标准的Skip(1).Zip方法来检查随后产生的值。

以下是输出:

output


在受到Shlomo的启发之后,这是我使用.Buffer(2, 1)的方法:

IObservable<Result> query2 =
    source
        .StartWith(new Result() { Flag = true })
        .Buffer(2, 1)
        .Where(rs => rs.First().Flag)
        .SelectMany(rs => rs.Skip(1));

答案 1 :(得分:2)

有很多方法可以做到:

var result1 = source.Publish(_source => _source
    .Zip(_source.Skip(1), (older, newer) => (older, newer))
    .Where(t => t.older.Flag == true)
    .Select(t => t.newer)
    .Merge(_source.Take(1))
    .Select(r => r.Text)
);

var result2 = source.Publish(_source => _source
    .Buffer(2, 1)
    .Where(l => l[0].Flag == true)
    .Select(l => l[1])
    .Merge(_source.Take(1))
    .Select(l => l.Text)
);

var result3 = source.Publish(_source => _source
    .Window(2, 1)
    .SelectMany(w => w
        .TakeWhile((r, index) => (index == 0 && r.Flag) || index == 1)
        .Skip(1)
    )
    .Merge(_source.Take(1))
    .Select(l => l.Text)
);

var result4 = source
    .Scan((result: new Result {Flag = true, Text = null}, emit: false), (state, r) => (r, state.result.Flag))
    .Where(t => t.emit)
    .Select(t => t.result.Text);

我偏爱“扫描一号”,但实际上取决于您。

答案 2 :(得分:0)

由于@Picci的回答,我找到了一种方法:

Func<bool, Action<Result>> printItem = print =>
                {
                    return data => {
                        if(print)
                        {
                            Console.WriteLine(data.Text);
                        }
                    };
                };

var printItemFunction = printItem(true);

source.Do(item => printItemFunction(item))
      .Do(item => printItemFunction = printItem(item.Flag))
      .Subscribe();

但是,我不确定这是否是最好的方法,因为对我而言,不使用Subscribe()而是副作用,这似乎有点奇怪。最后,我不仅要打印这些值,还要用它调用Webservice。

答案 3 :(得分:-1)

这是我在TypeScript中编码的方式

const printItem = (print: boolean) => {
    return (data) => {
        if (print) {
            console.log(data);
        }
    };
}

let printItemFunction = printItem(true);

from(data)
.pipe(
    tap(item => printItemFunction(item.data)),
    tap(item => printItemFunction = printItem(item.printBool))
)
.subscribe()

基本思想是使用更高级别的函数printItem,该函数返回一个知道是否打印什么内容的函数。返回的函数存储在变量printItemFunction中。

对于源Observable发出的每个项目,首先要做的是执行printItemFunction,传递源Observable通知的数据。

第二件事是求值printItem函数并将结果存储在变量printItemFunction中,以便为以下通知做好准备。

在程序开始时,printItemFunctiontrue初始化,以便始终打印第一项。

我不熟悉C#为您提供.NET的答案