问题
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. 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
So far I've come up with a way to do this on a Buffer, with this. However, the buffers are open too long, they are always 500 ms.
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. 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.
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
回答1:
Here's a solution not using the Scan
approach.
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 );
回答2:
I would recommend to create custom operator not based on other operator as it will take much more CPU and memory.
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();
});
});
}
来源:https://stackoverflow.com/questions/46625166/learning-rx-how-to-use-scan-on-the-output-of-window-for-an-observable-sequenc