简体   繁体   English

学习Rx:如何在.Window的输出上使用.Scan获取可观察的bool值序列

[英]Learning Rx: How to use .Scan on the output of .Window for an observable sequence of bool values

I have a sequence of true false values like so 我有一系列真假值,如此

        var alternatingTrueFalse = Observable.Generate(
            true,
            _ => true,
            x => !x,
            x => x,
            _ => TimeSpan.FromMilliseconds(new Random().Next(2000)))
            .Take(20).Publish();
        alternatingTrueFalse.Connect();
        var buffered = alternatingTrueFalse
            .Buffer(TimeSpan.FromMilliseconds(500))
            .Subscribe(x => Console.WriteLine($"{TimeStamp} {string.Join(",", x)}"));

I want to look at the sequence in terms of 500 ms (max) windows / buffers. 我想用500毫秒(最大)窗口/缓冲区来查看序列。 If there is only one true value (and nothing else) inside one such window, I want to flip a switch (just call a command, print to console for now). 如果在一个这样的窗口中只有一个真值(没有别的),我想翻转一个开关(只需调用一个命令,现在打印到控制台)。 Then, when the next false value arrives, I want to flip the switch back and close the current window / buffer of the original sequence and start a new one. 然后,当下一个假值到达时,我想要将开关向后翻转并关闭原始序列的当前窗口/缓冲区并开始一个新的。

Using Buffer + Scan to flip the switch 使用Buffer + Scan翻转开关

So far I've come up with a way to do this on a Buffer, with this. 到目前为止,我已经想出了一种在Buffer上做到这一点的方法。 However, the buffers are open too long, they are always 500 ms. 但是,缓冲区打开时间过长,始终为500毫秒。

        var buffered = alternatingTrueFalse
            .Buffer(TimeSpan.FromMilliseconds(500));
        var output = buffered
            .Subscribe(x => Console.WriteLine($"{TimeStamp} {string.Join(",", x)}"));

        var isFlipped = buffered.Scan(false, 
                (x, y) => 
                { 
                    if (y.Count == 0)
                    {
                        return x;
                    }
                    return y.Count == 1 && y.First();
                });

        isFlipped.DumpTimes("Flipped");

I'm trying to figure out how to use Window instead of Buffer to be able to flip the switch back on the first false after an isolated true. 我试图弄清楚如何使用Window而不是Buffer来在第一个false之后将交换机翻转回来。 But I can't seem to get it right, I'm not quite fluent in Rx yet and not sure how to use the windowOpening / Closing values for it. 但我似乎无法做到正确,我对Rx还不是很流利,也不确定如何使用windowOpening / Closing值。

Example output 示例输出

original 原版的

2017-10-07 20:21:39.302 True,False   // Rapid values should not flip the switch (actually they should flip a different switch)
2017-10-07 20:21:39.797 True         // Flip the switch here
2017-10-07 20:21:40.302 False        // Flip the switch back and close the current window
2017-10-07 20:21:40.797 True         // Flip the switch here
2017-10-07 20:21:41.297 
2017-10-07 20:21:41.798 False        // Etc...
...
2017-10-07 20:21:43.297 True
2017-10-07 20:21:43.800 False,True   // Example of a window that is open too long, because it is not closed immediately upon the false value
...

buffer + scan 缓冲+扫描

2017-10-07 20:47:15.154 True
2017-10-07 20:47:15.163 - Flipped-->True
2017-10-07 20:47:15.659 False,True   // Example of a window open too long
2017-10-07 20:47:15.661 - Flipped-->False

Here's a solution not using the Scan approach. 这是一个不使用Scan方法的解决方案。

The issue seems to be closing the buffer based on two conditions - maximum time or specific value. 问题似乎是基于两个条件关闭缓冲区 - 最大时间或特定值。 This one is based on an old answer 这是基于一个旧的答案

public static IObservable<IList<TSource>> BufferWithClosingValue<TSource>(
    this IObservable<TSource> source, 
    TimeSpan maxTime, 
    TSource closingValue)
{
    return source.GroupByUntil(_ => true,
                               g => g.Where(i => i.Equals(closingValue)).Select(_ => Unit.Default)
                                     .Merge(Observable.Timer(maxTime).Select(_ => Unit.Default)))
                 .SelectMany(i => i.ToList());
}

Example usage would be 示例用法是

alternatingTrueFalse.BufferWithClosingValue( TimeSpan.FromMilliseconds(500), false );

I would recommend to create custom operator not based on other operator as it will take much more CPU and memory. 我建议不要基于其他运算符创建自定义运算符,因为它将占用更多的CPU和内存。

Here is the clean version of the same method. 这是同一方法的干净版本。

public static IObservable<IEnumerable<TValue>> BufferWithThrottle<TValue>(this IObservable<TValue> @this, int maxAmount, TimeSpan threshold)
{
    var buffer = new List<TValue>();

    return Observable.Create<IEnumerable<TValue>>(observer =>
    {
        var aTimer = new Timer();
        void Clear()
        {
            aTimer.Stop();
            buffer.Clear();
        }
        void OnNext()
        {
            observer.OnNext(buffer);
            Clear();
        }
        aTimer.Interval = threshold.TotalMilliseconds;
        aTimer.Enabled = true;
        aTimer.Elapsed += (sender, args) => OnNext();
        var subscription = @this.Subscribe(value =>
        {
            buffer.Add(value);
            if (buffer.Count >= maxAmount)
                OnNext();
            else
            {
                aTimer.Stop();
                aTimer.Start();
            }
        });
        return Disposable.Create(() =>
        {
            Clear();
            subscription.Dispose();
        });
    });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM