【问题标题】:Merge multiple lists with variable length "popping" elements from each合并多个列表,每个列表具有可变长度“弹出”元素
【发布时间】:2017-04-07 16:34:10
【问题描述】:

我想将多个列表(数量不定)排序为单个列表,但保持特定顺序。例如:

List A: { 1,2,3,4,5 }
List B: { 6,7,8 }
List C: { 9,10,11,12 }

Result List: { 1,6,9,2,7,10,3,8,11,4,12,5 }

我得到的唯一想法是从每个列表中删除第一个元素并将其放入结果集中(并重复直到所有列表为空),但也许有一种更好的方法不需要创建每个列表的副本列表并且不会影响原始列表?

【问题讨论】:

  • 从 0 到 length 遍历每个列表并将每个对应元素放入新列表中?我的意思是 A[0]、B[0]、C[0]、A[1]、(...)。
  • 我怎么知道什么时候停止循环?某种标志“hasElements”,在循环开始时设置为 false,如果任何列表仍有更大的计数,则修改为 true?
  • 我喜欢 Zip 的想法!会试一试的。
  • Zip 希望所有列表相等 Lengths

标签: c# sorting merge


【解决方案1】:

我建议使用IEnumerator<T> 来枚举列表,而他们有项目:

private static IEnumerable<T> Merge<T>(params IEnumerable<T>[] sources) {
  List<IEnumerator<T>> enums = sources
    .Select(source => source.GetEnumerator())
    .ToList();

  try {
    while (enums.Any()) {
      for (int i = 0; i < enums.Count;)
        if (enums[i].MoveNext()) {
          yield return enums[i].Current;

          i += 1;
        }
        else {
          // exhausted, let's remove enumerator
          enums[i].Dispose();
          enums.RemoveAt(i);
        }
    }
  }
  finally {
    foreach (var en in enums)
      en.Dispose();
  }
}

测试

List<int> A = new List<int>() { 1, 2, 3, 4, 5 };
List<int> B = new List<int>() { 6, 7, 8 };
List<int> C = new List<int>() { 9, 10, 11, 12 };

var result = Merge(A, B, C)
  .ToList();

Console.Write(string.Join(", ", result));

结果是

1, 6, 9, 2, 7, 10, 3, 8, 11, 4, 12, 5

【讨论】:

    【解决方案2】:

    为了更灵活的使用

     public static string MergeArrays(params IList<int>[] items)
        {
    
            var result = new List<int>();
            for (var i = 0; i < items.Max(x => x.Count); i++)
                result.AddRange(from rowList in items where rowList.Count > i select rowList[i]);
    
            return string.Join(",", result);
        }
    

    .

            var a = new List<int>() { 1, 2, 3, 4, 5 };
            var b = new List<int>() { 6, 7, 8 };
            var c = new List<int>() { 9, 10, 11, 12, 0, 2, 1 };
    
            var r = MergeArrays(a, b, c);
    

    【讨论】:

    • 你能把你的 Foo2 函数的名字改成有意义的名字吗?
    • 发现你正在寻找毛茸茸的:)
    • 是的,MergeArray 看起来几乎很理想。 (但MergeArrays 会更好:P)
    • 除其他外,这个解决方案看起来很简单,其他人也可以理解,即使它不是最快的。谢谢!
    【解决方案3】:

    在我看来,把它复杂化是没有意义的,为什么不使用一个简单的 for 循环来完成你需要的事情呢?

    List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
    List<int> list2 = new List<int> { 6, 7, 8 };
    List<int> list3 = new List<int> { 9, 10, 11, 12 };
    List<int> resultList = new List<int>();
    
    for (int i = 0; i < list1.Count || i < list2.Count || i < list3.Count; i++)
    {
        if (i < list1.Count) resultList.Add(list1[i]);
        if (i < list2.Count) resultList.Add(list2[i]);
        if (i < list3.Count) resultList.Add(list3[i]);
    }
    

    结果:1​​,6,9,2,7,10,3,8,11,4,12,5

    【讨论】:

      【解决方案4】:

      这是一个相当简单的方法。无论如何,写起来很有趣。
      不,它不是最好的,但它可以工作,您可以扩展它以满足您使用List&lt;List&lt;int&gt;&gt; 的需要非常容易。

      //Using arrays for simplicity, you get the idea.
      int[] A = { 1, 2, 3, 4, 5 };
      int[] B = { 6, 7, 8 };
      int[] C = { 9, 10, 11, 12 };
      
      List<int> ResultSet = new List<int>();
      
      //Determine this somehow. I'm doing this for simplicity.
      int longest = 5; 
      
      for (int i = 0; i < longest; i++)
      {
          if (i < A.Length)
              ResultSet.Add(A[i]);
          if (i < B.Length)
              ResultSet.Add(B[i]);
          if (i < C.Length)
              ResultSet.Add(C[i]);
      }
      
      //ResultSet contains: { 1, 6, 9, 2, 7, 10, 3, 8, 11, 4, 12, 5 }
      

      如您所见,只需将其弹出到一个方法中并循环遍历您的列表列表,正确确定所有列表的最大长度。

      【讨论】:

      • int longest = Math.Max(A.Length, Math.Max(B.Length, C.Length)); 或 int longest = new[] {A.Length, B.Length, C.Length}.Max();
      • 您当然可以实现@tym32167 的最大长度代码!第二个可能会更容易,因为你提到了List&lt;List&lt;int&gt;&gt;。
      • 你能用任何解决方案更新你的答案吗?
      【解决方案5】:

      我会选择:

      static void Main(string[] args)
      {
          var a = new List<int>() { 1, 2, 3, 4, 5 };
          var b = new List<int>() { 6, 7, 8 };
          var c = new List<int>() { 9, 10, 11, 12 };
      
          var abc = XYZ<int>(new[] { a, b, c }).ToList();
      }
      
      static IEnumerable<T> XYZ<T>(IEnumerable<IList<T>> lists)
      {
          if (lists == null)
              throw new ArgumentNullException();
          var finished = false;
          for (int index = 0; !finished; index++)
          {
              finished = true;
              foreach (var list in lists)
                  if (list.Count > index) // list != null (prior checking for count)
                  {
                      finished = false;
                      yield return list[index];
                  }
          }
      }
      

      我必须使用 IList 来拥有索引器和 Count。它不会创建任何东西(没有枚举器、没有列​​表等),纯粹是yield return。

      【讨论】:

      • 伙计,这太复杂了。至少对我来说很难阅读这段代码。
      • @tym32167,这个想法是为了补充 linq 延迟执行。 XYZ&lt;T&gt; 在您真正开始使用结果之前不会产生任何结果(例如,调用 ToList())。它是防弹的(注释注释)并且可以与任何类型(又名通用)一起使用。我同意,逻辑有点不清楚,但很简单:外部foreach 枚举您传递的所有列表,内部for 按当前索引返回所有元素; finished 用于确定何时......完成。
      • 是的,我已经看懂了代码,但我相信你可以让它更具可读性。带有标志finished 的for 循环.. 不知道,我只是个人不喜欢它。但是当然你的解决方案应该很好用。
      • 我更新了我的答案,我重构了你的代码。请看一下,您对重构代码有何看法?它几乎相同,但从我的角度来看,它看起来更好只是因为它更易于阅读。
      【解决方案6】:

      针对您的问题,我创建了静态方法,它可以根据需要合并任何集合:

      public static class CollectionsHandling
      {
          /// <summary>
          /// Merge collections to one by index
          /// </summary>
          /// <typeparam name="T">Type of collection elements</typeparam>
          /// <param name="collections">Merging Collections</param>
          /// <returns>New collection {firsts items, second items...}</returns>
          public static IEnumerable<T> Merge<T>(params IEnumerable<T>[] collections)
          {
              // Max length of sent collections
              var maxLength = 0;
      
              // Enumerators of all collections
              var enumerators = new List<IEnumerator<T>>();
      
              foreach (var item in collections)
              {
                  maxLength = Math.Max(item.Count(), maxLength);
                  if(collections.Any())
                      enumerators.Add(item.GetEnumerator());
              }
              // Set enumerators to first item
              enumerators.ForEach(e => e.MoveNext());
      
              var result = new List<T>();
              for (int i = 0; i < maxLength; i++)
              {
                  // Add elements to result collection
                  enumerators.ForEach(e => result.Add(e.Current));
      
                  // Remobve enumerators, in which no longer have elements
                  enumerators = enumerators.Where(e => e.MoveNext()).ToList();
              }
      
              return result;
          }
      }
      

      使用示例:

      static void Main(string[] args)
      {
          var a = new List<int> { 1, 2, 3, 4, 5 };
          var b = new List<int> { 6, 7, 8 };
          var c = new List<int> { 9, 10, 11, 12 };
      
          var result= CollectionsHandling.Merge(a, b, c);
      }
      

      当你了解它的工作原理后,将有可能将方法缩小到更小。

      【讨论】:

      • 哦,我的.. 这个解决方案比我的更糟糕。枚举器、for 循环和 Linq 在一种方法中。太无效了。你能算出你在这个方法中创建了多少集合吗?
      • 首先,这个方法比眨眼还快。
      • 其次,阅读最后一个字符串“当你了解它的工作原理后,将有可能减少更小的方法。”。
      • 为什么你认为它更快?是的,我把最后一根绳子弄红了。
      • 使用秒表并记下上班时间。请不要把我的时间浪费在愚蠢的问题上......
      【解决方案7】:

      最短且可能最慢的解决方案

      int[] A = { 1, 2, 3, 4, 5 };
      int[] B = { 6, 7, 8 };
      int[] C = { 9, 10, 11, 12 };
      
      
      var arrs = new[] { A, B, C };
      var merged = Enumerable.Range(0, arrs.Max(a => a.Length))
          .Select(x => arrs.Where(a=>a.Length>x).Select(a=>a[x]))
          .SelectMany(x=>x)
          .ToArray();
      

      更新。

      另一种解决方法 - 我刚刚重构了@Sinatr 答案。

      static IEnumerable<T> XYZ<T>(IEnumerable<IList<T>> lists)
      {
          if (lists == null)  
              throw new ArgumentNullException();
          var index = 0;
      
          while (lists.Any(l => l.Count > index))
          {
              foreach (var list in lists)     
                  if (list.Count > index)         
                      yield return list[index];       
              index++;
          }
      }
      

      【讨论】:

      • 可以进一步缩短 for (int index = 0; lists.Any(l =&gt; l.Count &gt; index); index++) {...} 而不是 var index = 0; while(...) {... index++}
      • @DmitryBychenko aa,差不多,3行可以合并为1行,差别不大。不过谢谢!
      猜你喜欢
      • 2018-11-27
      • 2020-05-22
      • 2015-07-15
      • 2019-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-03
      • 2018-05-15
      相关资源
      最近更新 更多