只有当值已经改变了一定的余量时,Rx IObservable才会产生值

时间:2012-11-21 15:09:26

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

我有一种感觉,这可能是一个非常简单的扩展方法,我已经错过但我看不到它...

我基本上想要一个生成流的流,其中值在每个新值上缓慢递增。我希望节流/采样,而不是时间,而是“容忍”。 e.g。

var ob = Enumerable.Range(0, 30).ToObservable(); // 0, 1, 2, 3, 4, 5,....., 30
var largeMovingOb = ob.WhenChangedBy(10); // 0, 10, 20, 30

当我有[1,4,20,33]等序列时,我希望在值改变超过最后一个的15时输出 - 这将导致:[1,20]。如果值变化为12将导致:[1,20,33]

这是否有内置的Rx扩展?理想情况下,它可以在所有数字类型上工作,而无需为每个数字类型编写重载。

4 个答案:

答案 0 :(得分:7)

我认为这非常适合Observable.Scan

var ob = Enumerable.Range(0, 30).ToObservable();
var largeMovingOb = ob.Scan((acc, i) => acc + 10 > i ? acc : i)
  .DistinctUntilChanged();

答案 1 :(得分:0)

您可以重新调整用途的另一个内置运算符是DistinctUntilChanged,它将跟踪您的最后一个值。这里最大的“黑客”是IEqualityComparer可能不遵循标准的平等期望(a == b && b == c并不意味着a == c取决于函数)

public static IObservable<T> DistinctUntilChangedBy<T>(
    this IObservable<T> source, Func<T, T, bool> isChanged)
{
    //check arguments
    return source.DistinctUntilChanged(new MarginEqualityComparer<T>(isChanged));
}

class MarginEqualityComparer<T> : IEqualityComparer<T>
{
    MarginEqualityComparer(Func<T, T, bool> comparer)
    {
        _comparer = comparer;
    }

    private readonly Func<T, T, bool> _comparer;

    public bool Equals(T x, T y)
    {
        return _comparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        throw new NotSupportedException("This comparer does not support hashing.");
    }
}

答案 2 :(得分:0)

有一个内置的操作符可以完成你想要的操作。

试试这个:

var ob = Observable.Range(0, 30);
var largeMovingOb = ob.DistinctUntilChanged(x => x / 10);

它可以用于任何类型,而不仅仅是数字类型,因为签名如下所示:

IObservable<TSource> DistinctUntilChanged<TSource, TKey>(
    this IObservable<TSource> source, Func<TSource, TKey> keySelector)

简单。

答案 3 :(得分:-1)

您可以使用Where extension method上的IObservable<T>,跟踪上次产生的内容,并且只有当值超出最后一个产生的公差值时,谓词才会返回true。

这可以包含在一个扩展方法中,利用闭包来执行此操作,如下所示:

public static IObservable<int> WhenLastObservedChangesByMoreThan(
    this IObservable<int> observable, int tolerance)
{
    // Validate parameters.
    if (observable == null) throw new ArgumentNullException("observable");

    // Tolerance must be positive, so comparisons are correct after
    // addition/subtraction.
    if (tolerance < 0) 
        throw new ArgumentOutOfRangeExeption("tolerance", tolerance,
            "The tolerance parameter must be a non-negative number.");

    // Shortcut: If tolerance is 0, then every value is returned, just
    // return the observable.
    if (tolerance == 0) return observable;

    // The last value yielded.
    int? lastYielded = null;

    // Filter.
    observable = observable.Where(i => {
        // If there is a previous value
        // that was yielded.
        if (lastYielded != null)
        {
            // Is the last value within
            // tolerance?
            if (i - tolerance < i && i < i + tolerance)
            {
                // Do not process.
                return false;
            }
        }

        // This is being yielded, store the value.
        lastYielded = i;

        // Yield the value.
        return true;
    });
}

请注意,上述内容不是线程安全的,如果IObservable<T>从多个线程调用OnNext,那么您必须锁定对lastYielded变量的访问权限(这很容易足以使用lock statement)。