有效地将许多IObservable <bool>流与布尔运算符组合在一起

时间:2017-06-16 08:36:15

标签: system.reactive reactive-programming

我希望结合许多IObservable<bool>个流,这样当所有这些流的最新值为真时,会发出一个true,否则会发出false。

CombinedLast允许我轻松地为两个流构建类似的东西,但是a)我不确定API是否容易允许组合数千个流b)我不确定它的效率如何即便可能。

All有点类似于我想要的东西,除了我假设它适用于单个序列而且一旦错误就不能动态地变回真。

此外,我需要将值设置为“直到更改”,尽管DistintUntilChanged运算符对此可能效率不高?

我希望有一个O(1)算法。

2 个答案:

答案 0 :(得分:1)

我不知道如何以经典功能的方式做到这一点,仍然实现O(1)。这使用了可变状态,并且O(1)用于观察每条消息,但O(n)用于记忆:

public IObservable<bool> CombineBooleans(this IObservable<bool>[] source)
{
    return source.Select((o, i) => o.Select(b => (value: b, index: i)))
        .Merge()
        .Scan((array: new bool[source.Length], countFalse: source.Length), (state, item) =>
        {
            var countFalse = state.countFalse;

            if (state.array[item.index] == item.value)
                return (state.array, countFalse);           //nothing to change, emit same state
            else if (state.array[item.index])               //previous/current state is true, becoming false
            {
                countFalse++;
                state.array[item.index] = false;
            }
            else                                            //previous/current state is false, becoming true
            {
                countFalse--;
                state.array[item.index] = true;
            }
            return (state.array, countFalse);
        })
        .Scan((countFalse: source.Length, oldCountFalse: source.Length), (state, item) => (countFalse: item.countFalse, oldCountFalse: state.countFalse))
        .SelectMany(state =>
            state.countFalse == 1 && state.oldCountFalse == 0
                ? Observable.Return(false)
                : state.countFalse == 0 && state.oldCountFalse == 1
                    ? Observable.Return(true)
                    : Observable.Empty<bool>()
        )
        .Publish()
        .RefCount();
}

编辑:添加.Publish().Refcount()以消除多用户错误。

答案 1 :(得分:1)

组合最新版本的好方法是从IObservable<IObservable<T>>开始,然后将其转换为IObservable<T[]>。这将成为一种非常动态的方式,可以组合您需要的多个值。

这是执行此操作的扩展方法:

public static IObservable<T[]> CombineLatest<T>(this IObservable<IObservable<T>> sources)
{
    return
        sources.Publish(ss =>
            Observable.Create<T[]>(o =>
            {
                var composite = new CompositeDisposable();
                var list = new List<T>();
                composite.Add(
                    ss.Subscribe(source =>
                    {
                        var index = list.Count;
                        list.Add(default(T));
                        composite.Add(source.Subscribe(x => list[index] = x));
                    }));
                composite.Add(ss.Merge().Select(x => list.ToArray()).Subscribe(o));
                return composite;
            }));
}

这很好地创建并跟踪所有订阅,并使用闭包来定义每个订阅需要用来更新其用于输出的index中的值的list

如果你这样使用它:

var sources = new Subject<IObservable<bool>>();

var output = sources.CombineLatest();

output.Subscribe(x => Console.WriteLine(x));

var s1 = new Subject<bool>();
sources.OnNext(s1);
s1.OnNext(true);
var s2 = new Subject<bool>();
sources.OnNext(s2);
s2.OnNext(false);
var s3 = new Subject<bool>();
sources.OnNext(s3);
s3.OnNext(true);
s2.OnNext(true);
s1.OnNext(false);

然后你得到这个输出:

output

如果您将output的定义更改为var output = sources.CombineLatest().Select(xs => xs.Aggregate((x, y) => x & y));,那么您会得到我认为您正在追求的输出:

True
False
False
True
False