在C#中对可观察对象进行分区

时间:2017-09-30 17:11:50

标签: c# system.reactive

我正在寻找一种将可观察序列分成单独序列的方法,我可以根据给定的谓词独立处理这些序列。这样的事情是理想的:

var (evens, odds) = observable.Partition(x => x % 2 == 0);
var strings = evens.Select(x => x.ToString());
var floats = odds.Select(x => x / 2.0);

我能够提出的最接近的是做两个where过滤器,但这需要评估条件并处理源序列两次,我并不狂热。< / p>

observable = observable.Publish().RefCount();
var strings = observable.Where(x => x % 2 == 0).Select(x => x.ToString());
var floats = observable.Where(x => x % 2 != 0).Select(x => x / 2.0);

对于Observable.partition<'T>Observable.split<'T,'U1,'U2>,F#似乎对此有很好的支持,但我找不到与C#等效的内容。

3 个答案:

答案 0 :(得分:2)

GroupBy可以删除&#34;观察两次&#34;限制,尽管你仍然会以Where条款结束:

public static class X
{
    public static (IObservable<T> trues, IObservable<T> falsies) Partition<T>(this IObservable<T> source, Func<T, bool> partitioner)
    {
        var x = source.GroupBy(partitioner).Publish().RefCount();
        var trues = x.Where(g => g.Key == true).Merge();
        var falsies = x.Where(g => g.Key == false).Merge();
        return (trues, falsies);
    }
}

答案 1 :(得分:0)

这样的东西
var (odds,evens) = (collection.Where(a=> a % 2 == 1), collection.Where(a=> a % 2 == 0));?

或者如果您想根据一个条件进行分区

Func<int,bool> predicate = a => a%2==0;

var (odds,evens) = (collection.Where(a=> !predicate(a)), collection.Where(a=> predicate(a)));

我认为没有解决这个问题,你用这种方式迭代两次这个事实,还有什么办法可以让一个方法接受一个谓词并传入2个sepatate集合并在一次迭代中填充它们foreach或for。

这样的事情:

var collection = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9};

Func<int,bool> predicate = a => a%2==0;
var odds = new List<int>();
var evens = new List<int>();

Action<List<int>, List<int>, Func<int, bool>> partition = (collection1, collection2, pred) =>
{
    foreach (int element in collection)
    {
        if (pred(element))
        {
            collection1.Add(element);
        }
        else 
        {
            collection2.Add(element);
        }
    }
};

partition(evens, odds, predicate);

扩展最后一个想法,你在找这样的东西吗?

public static (ObservableCollection<T>, ObservableCollection<T>) Partition<T>(this ObservableCollection<T> collection, Func<T, bool> predicate)
{
    var collection1 = new ObservableCollection<T>();
    var collection2 = new ObservableCollection<T>();

    foreach (T element in collection)
    {
        if (predicate(element))
        {
            collection1.Add(element);
        }
        else
        {
            collection2.Add(element);
        }
    }

    return (collection1, collection2);
}

答案 2 :(得分:0)

使用 RefCount 操作符加热源序列不是一个好主意,因为源序列可能在派生序列的所有订阅到位之前就开始发射元素。在这种情况下,一些发射的元素可能会丢失。更安全的方法是推迟加热源序列,直到所有观察者都已订阅。以下是如何执行此操作的示例:

var published = observable.Publish(); // Make sure not to warm it too early

var strings = published.Where(x => x % 2 == 0).Select(x => x.ToString());
var floats = published.Where(x => x % 2 != 0).Select(x => x / 2.0);

strings.Subscribe(x => Console.WriteLine(x));
floats.Subscribe(x => Console.WriteLine(x));

published.Connect(); // Now that all subscriptions are in place, it's time to warm it

await published; // Wait for the completion of the source sequence

您可以通过使用 an answer of a relevant question 中包含的 LookupObservable<TSource, TKey> 类来减少上述代码的重复。实现这个类是因为创建多个 Where 子序列可能会非常低效,以防子序列总数很大(因为源发出的每个元素都将检查多个条件)。在您的情况下,您只有两个子序列,一个用于键 true,另一个用于键 false,因此使用 LookupObservable 类不太引人注目。无论如何,这是一个用法示例:

var published = observable.Publish(); // Make sure not to warm it too early

var lookup = new LookupObservable<int, bool>(published, x => x % 2 == 0);

var strings = lookup[true].Select(x => x.ToString());
var floats = lookup[false].Select(x => x / 2.0);

strings.Subscribe(x => Console.WriteLine(x));
floats.Subscribe(x => Console.WriteLine(x));

published.Connect(); // Now that all subscriptions are in place, it's time to warm it

await published; // Wait for the completion of the source sequence