15

シーケンス (Reactive Extensions for .NET) で、IObservable以前の要素と現在の要素の値を取得して、それらを比較できるようにしたいと考えています。以下のようなタスクを実行する例をオンラインで見つけました。

sequence.Zip(sequence.Skip(1), (prev, cur) => new { Previous = prev, Current = cur })

シーケンスを2回評価することを除いて、うまく機能しますが、これは避けたいと思います。次のコードで 2 回評価されていることがわかります。

var debugSequence = sequence.Do(item => Debug.WriteLine("Retrieved an element from sequence"));
debugSequence.Zip(debugSequence.Skip(1), (prev, cur) => new { Previous = prev, Current = cur }).Subscribe();

出力には、シーケンス内の要素の 2 倍のデバッグ行が表示されます。

なぜこれが起こるのか理解していますが、これまでのところ、シーケンスを 2 回評価しない代替手段は見つかりませんでした。以前と現在を 1 つのシーケンス評価だけで組み合わせるにはどうすればよいですか?

4

5 に答える 5

30

Observable.Scan を使用し、二重サブスクリプションを回避する、これに対するより良い解決策があると思います。

public static IObservable<Tuple<TSource, TSource>>
    PairWithPrevious<TSource>(this IObservable<TSource> source)
{
    return source.Scan(
        Tuple.Create(default(TSource), default(TSource)),
        (acc, current) => Tuple.Create(acc.Item2, current));
}

私はこれをブログに書いています: http://www.zerobugbuild.com/?p=213

補遺

さらに変更を加えると、結果セレクターを使用して、任意の型をよりクリーンに操作できます。

public static IObservable<TResult> CombineWithPrevious<TSource,TResult>(
    this IObservable<TSource> source,
    Func<TSource, TSource, TResult> resultSelector)
{
    return source.Scan(
        Tuple.Create(default(TSource), default(TSource)),
        (previous, current) => Tuple.Create(previous.Item2, current))
        .Select(t => resultSelector(t.Item1, t.Item2));
}
于 2013-05-16T07:35:08.040 に答える
4

@James Worldの補遺は、Tuple<>私がほとんどいつも嫌うものではないにしても、私には素晴らしく見えます:前のアイテム?」。

その部分については、 @dcstraw の専用の定義が気に入りましたItemWithPrevious<T>。では、名前の変更と機能を使用して、この 2 つをまとめます (以前のものと現在のものを混同していないことを願っています)。

public static class ObservableExtensions
{
    public static IObservable<SortedPair<TSource>> CombineWithPrevious<TSource>(
        this IObservable<TSource> source, 
        TSource initialValue = default(TSource))
    {
        var seed = SortedPair.Create(initialValue, initialValue);

        return source.Scan(seed,
            (acc, current) => SortedPair.Create(current, acc.Current));
    }

    public static IObservable<TResult> CombineWithPrevious<TSource, TResult>(
        this IObservable<TSource> source,
        Func<SortedPair<TSource>, TResult> resultSelector,
        TSource initialValue = default(TSource))
    {
        var seed = SortedPair.Create(initialValue, initialValue);

        return source
            .Scan(seed,
                (acc, current) => SortedPair.Create(current, acc.Current))
            .Select(p => resultSelector(p));
    }
}

public class SortedPair<T>
{
    public SortedPair(T current, T previous)
    {
        Current = current;
        Previous = previous;
    }

    public SortedPair(T current) : this(current, default(T)) { }

    public SortedPair() : this(default(T), default(T)) { }

    public T Current;
    public T Previous;
}

public class SortedPair
{
    public static SortedPair<T> Create<T>(T current, T previous)
    {
        return new SortedPair<T>(current, previous);
    }

    public static SortedPair<T> Create<T>(T current)
    {
        return new SortedPair<T>(current);
    }

    public static SortedPair<T> Create<T>()
    {
        return new SortedPair<T>();
    }
}
于 2015-08-21T10:22:18.517 に答える
3

2 回評価することは、 Cold observableの指標です。.Publish() を使用してホットなものに変えることができます。

var pub = sequence.Publish();
pub.Zip(pub.Skip(1), (...
pub.Connect();
于 2010-05-13T01:19:05.417 に答える
0

サブスクリプション中に前の要素にアクセスするだけでよい場合は、おそらくこれが最も簡単に機能します。(IObservable のバッファ演算子など、もっと良い方法があると思いますか?現時点ではドキュメントがかなりまばらなので、はっきりとは言えません。)

    EventArgs prev = null;

    sequence.Subscribe(curr => 
    {
        if (prev != null)
        {
            // Previous and current element available here
        }

        prev = curr;                              

    });

EventArgs は、イベントの引数の型の単なる代用です。

于 2010-05-12T17:13:41.507 に答える
-1

変数を使用して以前の値を保持し、それを参照して、IObservable拡張のチェーン内で再割り当てできることがわかりました。これは、ヘルパー メソッド内でも機能します。以下のコードを使用すると、シーケンスを再評価することなく、CombineWithPrevious()myを呼び出して前の値への参照を取得できます。IObservable

public class ItemWithPrevious<T>
{
    public T Previous;
    public T Current;
}

public static class MyExtensions
{
    public static IObservable<ItemWithPrevious<T>> CombineWithPrevious<T>(this IObservable<T> source)
    {
        var previous = default(T);

        return source
            .Select(t => new ItemWithPrevious<T> { Previous = previous, Current = t })
            .Do(items => previous = items.Current);
    }
}
于 2010-05-12T19:18:26.013 に答える