【问题标题】:LINQ: Group by index and value [duplicate]LINQ:按索引和值分组[重复]
【发布时间】:2018-08-24 10:38:27
【问题描述】:

假设我有一个包含以下值的字符串列表:

["a","a","b","a","a","a","c","c"]

我想执行一个 linq 查询,它将分为 4 个组:

第 1 组:["a","a"] 第 2 组:["b"] 第 3 组:["a","a","a"] 第 4 组: ["c","c"]

基本上我想为值“a”创建 2 个不同的组,因为它们不是来自同一个“索引序列”。

谁有这方面的 LINQ 解决方案?

【问题讨论】:

  • MoreLINQGroupAdjacent,这似乎正是您要找的。​​span>
  • 我不明白你提供的输出。当我阅读您的问题时,在我看来您想要Group 1: ["a","a"] Group 2: ["b"] Group 3: ["a","a", "a"] Group 4: ["c","c"],对吗?
  • 谢谢@MarcinJuraszek。我正在寻找没有额外库的东西......无论如何,如果我深入研究回购代码会有所帮助
  • 您可以使用foreach 遍历您的数组,并根据这些要求构建一个新的数据结构(可能是List<string>[])。
  • @juanora 纯 LINQ 解决方案(使用内置 LINQ 方法)将比简单的 foreach 更难看,可读性差,而且效率可能更低。在这里使用 LINQ 毫无意义。如果您需要 LINQ 解决方案,请使用 foreach 编写您自己的 LINQ 扩展(例如,IEnumerable<List<T>> GroupAdjacent(this IEnumerable<T> source))方法并使用它:)

标签: c# linq c#-7.2


【解决方案1】:

你只需要数组项以外的键

var x = new string[] { "a", "a", "a", "b", "a", "a", "c" };


int groupId = -1;
var result = x.Select((s, i) => new
{
    value = s,
    groupId = (i > 0 && x[i - 1] == s) ? groupId : ++groupId
}).GroupBy(u => new { groupId });


foreach (var item in result)
{
    Console.WriteLine(item.Key);
    foreach (var inner in item)
    {
        Console.WriteLine(" => " + inner.value);
    }
}

结果如下:Link

【讨论】:

    【解决方案2】:

    先计算“索引序列”,然后做你的组。

    private class IndexedData
    {
        public int Sequence;
        public string Text;
    } 
    
    string[] data = [ "a", "a", "b" ... ]
    
    // Calculate "index sequence" for each data element.
    List<IndexedData> indexes = new List<IndexedData>();
    
    foreach (string s in data)
    {
        IndexedData last = indexes.LastOrDefault() ?? new IndexedData();
    
        indexes.Add(new IndexedData
        {
            Text = s,
            Sequence = (last.Text == s
                          ? last.Sequence 
                          : last.Sequence + 1)
        });
    }
    
    // Group by "index sequence"
    var grouped = indexes.GroupBy(i => i.Sequence)
                         .Select(g => g.Select(i => i.Text));
    

    【讨论】:

      【解决方案3】:

      这是一个幼稚的foreach 实现,其中整个数据集最终都在内存中(可能对您来说不是问题,因为您这样做了GroupBy):

      public static IEnumerable<List<string>> Split(IEnumerable<string> values)
      {
          var result = new List<List<string>>();
          foreach (var value in values)
          {
              var currentGroup = result.LastOrDefault();
              if (currentGroup?.FirstOrDefault()?.Equals(value) == true)
              {
                  currentGroup.Add(value);
              }
              else
              {
                  result.Add(new List<string> { value });
              }
          }
      
          return result;
      }
      

      这里有一个稍微复杂的实现,带有foreachyield return 枚举器状态机,它只将当前组保存在内存中——这可能是在框架级别上实现的方式:

      编辑:这显然也是 MoreLINQ 的做法。

      public static IEnumerable<List<string>> Split(IEnumerable<string> values)
      {
          var currentValue = default(string);
          var group = (List<string>)null;
      
          foreach (var value in values)
          {
              if (group == null)
              {
                  currentValue = value;
                  group = new List<string> { value };
              }
              else if (currentValue.Equals(value))
              {
                  group.Add(value);
              }
              else
              {
                  yield return group;
                  currentValue = value;
                  group = new List<string> { value };
              }
          }
      
          if (group != null)
          {
              yield return group;
          }
      }
      

      这是一个只使用LINQ的笑话版本,它与第一个基本相同,但有点难以理解(特别是因为Aggregate不是最常用的LINQ方法):

      public static IEnumerable<List<string>> Split(IEnumerable<string> values)
      {
          return values.Aggregate(
              new List<List<string>>(),
              (lists, str) =>
              {
                  var currentGroup = lists.LastOrDefault();
                  if (currentGroup?.FirstOrDefault()?.Equals(str) == true)
                  {
                      currentGroup.Add(str);
                  }
                  else
                  {
                      lists.Add(new List<string> { str });
                  }
      
                  return lists;
              },
              lists => lists);
      }
      

      【讨论】:

        【解决方案4】:

        使用基于 APL 扫描运算符的扩展方法,类似于 Aggregate,但返回与源值配对的中间结果:

        public static IEnumerable<KeyValuePair<TKey, T>> ScanPair<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<KeyValuePair<TKey, T>, T, TKey> combine) {
            using (var srce = src.GetEnumerator()) {
                if (srce.MoveNext()) {
                    var prevkv = new KeyValuePair<TKey, T>(seedKey, srce.Current);
        
                    while (srce.MoveNext()) {
                        yield return prevkv;
                        prevkv = new KeyValuePair<TKey, T>(combine(prevkv, srce.Current), srce.Current);
                    }
                    yield return prevkv;
                }
            }
        }
        

        您可以创建扩展方法以按一致运行进行分组:

        public static IEnumerable<IGrouping<int, TResult>> GroupByRuns<TElement, TKey, TResult>(this IEnumerable<TElement> src, Func<TElement, TKey> key, Func<TElement, TResult> result, IEqualityComparer<TKey> cmp = null) {
            cmp = cmp ?? EqualityComparer<TKey>.Default;
            return src.ScanPair(0,
                                (kvp, cur) => cmp.Equals(key(kvp.Value), key(cur)) ? kvp.Key : kvp.Key + 1)
                      .GroupBy(kvp => kvp.Key, kvp => result(kvp.Value));
        }
        
        public static IEnumerable<IGrouping<int, TElement>> GroupByRuns<TElement, TKey>(this IEnumerable<TElement> src, Func<TElement, TKey> key) => src.GroupByRuns(key, e => e);
        public static IEnumerable<IGrouping<int, TElement>> GroupByRuns<TElement>(this IEnumerable<TElement> src) => src.GroupByRuns(e => e, e => e);
        
        public static IEnumerable<IEnumerable<TResult>> Runs<TElement, TKey, TResult>(this IEnumerable<TElement> src, Func<TElement, TKey> key, Func<TElement, TResult> result, IEqualityComparer<TKey> cmp = null) =>
            src.GroupByRuns(key, result).Select(g => g.Select(s => s));
        public static IEnumerable<IEnumerable<TElement>> Runs<TElement, TKey>(this IEnumerable<TElement> src, Func<TElement, TKey> key) => src.Runs(key, e => e);
        public static IEnumerable<IEnumerable<TElement>> Runs<TElement>(this IEnumerable<TElement> src) => src.Runs(e => e, e => e);
        

        使用最简单的版本,您可以获得IEnumerable&lt;IGrouping&gt;&gt;

        var ans1 = src.GroupByRuns();
        

        或者将IGrouping(及其Key)转储为IEnumerable的版本:

        var ans2 = src.Runs();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-02-27
          • 2012-11-25
          • 2019-03-11
          • 2016-03-20
          • 2020-12-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多