Rx .NET首先采取时间间隔或条件后跳过

时间:2018-08-28 09:03:22

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

我正在.NET中学习rx,我有以下要求:

  • 一系列字符串来自API。他们以不同的时间间隔来到,我不知道。有时在一秒钟内会出现5个字符串,有时5秒内只会有1个字符串。
  • 字符串基本上是五个命令:“开始”,“停止”,“左”,“右”,“后退”。还有其他命令,但是可以将其过滤掉。
  • 现在,只要有命令传入,程序就应该执行。
  • 如果在给定的时间段内(例如2秒)传入了相同命令,则该命令应该仅执行一次。如果在此期间传入另一个命令,则应立即执行。如果在给定的时间段后收到与先前执行的命令相同的命令,则应执行该命令。
  • 没有为传入的数据分配时间戳(但是可以根据需要执行此操作)。

因此,给出示例数据:

using System;
using System.Reactive.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var results = new[]
                {
                 "right", //1
                 "left", //2
                 "right", //3
                 "right", //4
                 "left", //5
                 "right", //6
                 "right", //7
                 "right", //8
                 "left" //9
                };

            var observable =  Observable.Generate(0, 
                x => x < results.Length, 
                x => x + 1,
                x => results[x], 
                x => 
                TimeSpan.FromSeconds(1)).Timestamp();

            observable.Subscribe(...);

            Console.ReadKey();
        }        
    }
}

结果应为:

right //1
left //2
right //3
left //5
right //6
right //8
left //9

字符串4已被跳过,因为它距最后一个“右”仅1秒,字符串7也是如此。但是,字符串8未被跳过,因为字符串6有2秒。

可能的解决方案:

我试图使用窗口函数来跳过条目,但这会跳过所有字符串,即使它们的值不同:

observable.Publish(ps =>
           ps.Window(() => ps.Delay(TimeSpan.FromSeconds(2)))
             .SelectMany(x => x.Take(1))).Subscribe(f => Console.WriteLine(f.Value));

我还尝试将时间戳添加到每个值,并在DistinctUntilChanged()EqualityComparer中进行检查,但这似乎也无法按预期工作。

3 个答案:

答案 0 :(得分:2)

我还没有测试过这段代码,但是您已经有了大致的认识。

        source
            .Select(x => (str: x, timestamp: DateTime.Now))
            .Scan((last: (str: "", timestamp: DateTime.MinValue), next: (str: "", timestamp: DateTime.MinValue)),
                (state, val) =>
                    (last: (str: state.next.str, timestamp: state.next.timestamp), next: (str: val.str, timestamp: val.timestamp))
                )
            .Where(x => (x.next.str != x.last.str) || (x.next.timestamp - x.last.timestamp) > TimeSpan.FromSeconds(2))
            .Select(x=>x.next.str);

答案 1 :(得分:2)

这比我想象的要棘手,因为有三重情况(右,右,右相隔一秒钟)。在这里无法使用直.Zip

这类似于Sentinel的答案,并且可以正确处理三重情况:

source
    .Timestamp()        
    .Scan((state, item) => state == null || item.Timestamp - state.Timestamp > TimeSpan.FromSeconds(2) || state.Value != item.Value
        ? item
        : state
    )
    .DistinctUntilChanged()
    .Select(t => t.Value);

说明:

  • .Timestamp()将每封邮件及其到达的时间戳包装起来
  • .Scan(1 arg)如果2秒钟内出现重复,则会重复上一条消息,否则发出新消息
  • .DistinctUntilChanged()删除重复的消息,这是因为.Scan发出了两次旧消息
  • .Select删除时间戳记。

答案 2 :(得分:0)

嗯..听起来像observable.DistinctUntilChanged可以检测到不同的事件,但是通过CombineLatestobservable.Debounce合并在一起也可以得到重复。 >

这将涵盖基础知识,但是我不确定如果在比反跳时间更长的时间之后出现了与之前不同的项目,将会发生什么情况。源DistinctUntilChanged和Debounce运算符都会在“同时”,我不确定此时的CombineLatest会做什么。发生变化是,您将两次获得此类事件(在很短的时间内就会发生同一事件),因此您需要再次将其重复数据删除。

如果您愿意添加某种时间戳记,那么还有一种相当明确的方法可以做到这一点,尽管我不确定这样做是否真的更简单。

  • 获取源事件-{command}流
  • GroupBy的类型-{command}的子流,每个子流仅包含一种命令
  • TimeInterval应用于每个子流,您将获得{command,time-since-last-seen}的子流,每个子流仅包含一种类型的命令
  • 结合在一起,您将获得{command,time-since-THIS-TYPE-was-last-seen}的流
  • 根据“时间”将其转换为{command,null-or-ObjectThatIsAlwaysDifferent},如果时间小于延迟时间,则为NULL,如果时间大于延迟时间,则使用一些不可思议的值IsAlwaysDifferent
  • DistinctUntil更改了

您应该能够通过简单地使整个gethashcode类返回0并等于equals返回false来实现神奇的ObjectThatIsAlwaysDifferent。

这应该返回与上一个命令具有不同命令的事件,或者与之前相同但在比推迟时间更长的时间之后发生的事件。

现在考虑一下,应该可以通过压缩当前值和先前值来非常简单地做到这一点:

  • 获取{command}的源流
  • 添加时间戳-{command,timestamp}
  • 将其延迟1,同时记住来源和延迟
  • 将它们压缩在一起,现在您可以获得[{command,timestamp},{prevcommand,prevtimestamp}]的流
  • 使用您的代码对其进行过滤:
    • 在命令!= prevcommand时通过
    • 在命令== prevcommand && timestamp-prevtimestamp>释抑时通过

应该是这样。和往常一样,做同一件事的方法不止一种。