【问题标题】:How to convert an IGroupedObservable to IGrouping?如何将 IGroupedObservable 转换为 IGrouping?
【发布时间】:2020-11-26 04:00:26
【问题描述】:

我有一个具有char Key 属性的可观察元素序列,其值范围从'A''E'。我想根据这个键对这些元素进行分组。在对它们进行分组后,我希望通过一组可观察的结果来获得结果,以便我可以分别处理每个组。我的问题是我找不到一个很好的方法来保存最终观察到的每个组的密钥。这是我正在尝试做的一个示例:

var observable = Observable
    .Interval(TimeSpan.FromMilliseconds(100))
    .Take(42)
    .GroupBy(n => (char)(65 + n % 5))
    .Select(grouped => grouped.ToArray())
    .Merge();

observable.Subscribe(group =>
    Console.WriteLine($"Group: {String.Join(", ", group)}"));

输出:

Group: 0, 5, 10, 15, 20, 25, 30, 35, 40
Group: 1, 6, 11, 16, 21, 26, 31, 36, 41
Group: 2, 7, 12, 17, 22, 27, 32, 37
Group: 3, 8, 13, 18, 23, 28, 33, 38
Group: 4, 9, 14, 19, 24, 29, 34, 39

组正确,但密钥 ('A' - 'E') 丢失。 observable 的类型是 IObservable<long[]>。我想要什么 相反,是IObservable<IGrouping<char, long>>。这样group.Key 将在最终订阅代码中可用。但据我所见 没有将IGroupedObservableGroupBy 运算符的结果)转换为IGrouping 的内置方法。我可以看到运营商ToArrayToListToLookupToDictionary 等,但不是 ToGrouping 运算符。我的问题是,我该如何实现这个操作符?

这是我实现它的不完整尝试:

public static IObservable<IGrouping<TKey, TSource>> ToGrouping<TKey, TSource>(
    this IGroupedObservable<TKey, TSource> source)
{
    return Observable.Create<IGrouping<TKey, TSource>>(observer =>
    {
        // What to do?
        return source.Subscribe();
    });
}

我的意图是在原始示例中使用它而不是 ToArray,如下所示:

.Select(grouped => grouped.ToGrouping())

【问题讨论】:

    标签: c# linq system.reactive rx.net


    【解决方案1】:

    这可以满足您的大部分需求:

    var observable = Observable
        .Interval(TimeSpan.FromMilliseconds(100))
        .Take(42)
        .GroupBy(n => (char)(65 + n % 5))
        .SelectMany(grouped => grouped.ToArray().Select(a => (key: grouped.Key, results: a)));
    

    那是IObservable&lt;ValueTuple&lt;TKey, TResult[]&gt;。如果您想要IGrouping 接口,则必须创建一个对象,因为我认为您没有可用的对象:

    public static class Grouping
    {
        // Because I'm too lazy to code types
        public static Grouping<TKey, TResult> Create<TKey, TResult>(TKey key, IEnumerable<TResult> results)
        {
            return new Grouping<TKey, TResult>(key, results);
        }
    }
    
    public class Grouping<TKey, TResult> : IGrouping<TKey, TResult>
    {
        public Grouping(TKey key, IEnumerable<TResult> results)
        {
            this.Key = key;
            this.Results = results;
        }
        
        public TKey Key { get; }
        public IEnumerable<TResult> Results { get; }
    
        public IEnumerator<TResult> GetEnumerator()
        {
            return Results.GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return Results.GetEnumerator();
        }
    }
    

    那么你的 observable 变成:

    var o2 = Observable.Interval(TimeSpan.FromMilliseconds(100))
        .Take(42)
        .GroupBy(n => (char)(65 + n % 5))
        .SelectMany(grouped => grouped.ToArray().Select(a => Grouping.Create(grouped.Key, a)));
    

    【讨论】:

      【解决方案2】:

      这似乎是你想要的:

      IObservable<(char Key, long[] Values)> observable =
          Observable
              .Interval(TimeSpan.FromMilliseconds(100))
              .Take(42)
              .GroupBy(n => (char)(65 + n % 5))
              .Select(grouped => new { Key = grouped.Key, Values = grouped.ToArray() })
              .SelectMany(x => x.Values, (k, v) => (Key: k.Key, Values: v));
      
      observable.Subscribe(group =>
          Console.WriteLine($"Group {group.Key}: {String.Join(", ", group.Values)}"));
      

      我明白了:

      Group A: 0, 5, 10, 15, 20, 25, 30, 35, 40
      Group B: 1, 6, 11, 16, 21, 26, 31, 36, 41
      Group C: 2, 7, 12, 17, 22, 27, 32, 37
      Group D: 3, 8, 13, 18, 23, 28, 33, 38
      Group E: 4, 9, 14, 19, 24, 29, 34, 39
      

      【讨论】:

      • 是的,这就是工作。但是必须使用通用的ValueTuple&lt;TKey, TSource[] 不如使用具有语义意义的IGrouping&lt;TKey, TSource&gt; 令人满意! ?
      • @TheodorZoulias - 好的...(在此处插入困惑的表情符号)。
      • 说实话,我没有问这个问题,因为我有一个真正的问题要解决。我问它是因为以一种很好的方式(标题中要求的方式)解决这个假设的问题有点挑战性。 ?
      • @TheodorZoulias - 很公平,我没看清楚标题。不过,这应该很容易解决。
      【解决方案3】:

      我找到了一种方法来实现ToGrouping 运算符,而无需创建实现IGrouping 接口的自定义类。它比 Shlomo 的 solution 更简洁但效率更低。

      /// <summary>
      /// Creates an observable sequence containing a single 'IGrouping' that has the same
      /// key with the source 'IGroupedObservable', and contains all of its elements.
      /// </summary>
      public static IObservable<IGrouping<TKey, TSource>> ToGrouping<TKey, TSource>(
          this IGroupedObservable<TKey, TSource> source)
      {
          return source
              .ToList()
              .Select(list => list.GroupBy(_ => source.Key).Single());
      }
      

      此实现假定TKey 类型没有以某种疯狂的方式实现IEquatable 接口,即为相同的值返回不同的哈希码,或者认为值不等于自身。如果发生这种情况,Single LINQ 运算符将抛出异常。

      【讨论】:

      • 其实我是故意不这样做的。 IObservable&lt;IGrouping&lt;TKey, TResult&gt;&gt; 隐含地暗示您将在准备好/完成后获得每个分组。此解决方案仅在所有分组都准备好时才提供所有分组。
      • @Shlomo 我不确定我是否理解。这个答案的ToGrouping 运算符的返回值是一个可观察到的,它发出一个IGrouping。它在源 IGroupedObservable 完成时发出。您能否详细说明两种解决方案之间的区别?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-26
      • 2023-03-22
      • 2014-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多