【问题标题】:Merging multiple lists合并多个列表
【发布时间】:2013-08-23 18:46:13
【问题描述】:

鉴于我有一个列表列表,List<List<T>>,其中所有列表都包含 0 个或多个项目,可能但不一定都是相同的数字。而且我希望有一个包含列表中所有项目的列表......但我希望顺序如下:首先是所有列表中的第一个项目,以便它们出现在“超级列表”中。

例如

List[0] = { 'Apple', 'Blueberry', 'Cranberry' }
List[1] = { 'Anteater', 'Baboon', 'Camel', 'Dodo'}
List[2] = { 'Albatross', 'Blackbird', 'Chicken'}

result = { 'Apple', 'Anteater', 'Albatross', 'Blueberry', 'Baboon', 
           'Blackbird', 'Cranberry', 'Camel', 'Chicken', 'Dodo' }

(注意这不是字母顺序,蓝莓在狒狒之前)

当然,只要有一个不为空的列表,我就可以使用计数器循环遍历“超级列表”,将项目逐个添加到结果列表中:

int i = 0;
bool done = false;

while (!done)
{
    bool found = false;

    foreach (list l in superlist)
    {
        if (l.Count() > i)
        {
           found = true;
           result.Add(l[i]);
        }
    }

    i++;

    if (!found)
        done = true;
}

但是使用一些优化的 LINQ 函数来执行此操作会更好。我一直在研究 ZipGroupByAggregate,但无法让它们工作。

那么:是否有一个漂亮的 LINQ 函数或多个函数的组合可以将其变成漂亮的代码,或者我应该坚持(并可能优化)我当前的函数?

编辑:一个简单的 SelectMany(x => x) 也不起作用,因为它保留了列表的顺序,而不是像我的算法那样折叠它们。有关详细信息,请参阅我最初的问题。

【问题讨论】:

标签: c# .net linq list


【解决方案1】:

你需要SelectMany

var result = lists.SelectMany(x => x.Select((s, inx) => new { s, inx }))
                .GroupBy(x => x.inx)
                .SelectMany(x => x.Select(y => y.s))
                .ToList();

编辑

对于那些想尝试的人来说,初始化代码。

List<List<string>> lists = new List<List<string>>()
        {
            new List<string>(){ "Apple", "Blueberry", "Cranberry" },
            new List<string>(){ "Anteater", "Baboon", "Camel", "Dodo"},
            new List<string>(){ "Albatross", "Blackbird", "Chicken"},
        };

编辑 2

输出: Apple,Anteater,Albatross,Blueberry,Baboon,Blackbird,Cranberry,Camel,Chicken,Dodo

【讨论】:

  • 完全有效!永远不会想出这个,现在关于这是否是更具可读性的代码的问题?我将把它作为练习留给读者。
  • @Amy 这在任何情况下都有效(即使列表中有重复的项目,...)但如果您的列表没有重复的项目,我的代码也可以。
  • @I4V a1,a2,a3 是什么?我们在谈论List&lt;List&lt;string&gt;&gt;
  • @KingKing 不用担心,我删除了我的评论。
  • @I4V 我做了 - 没有关于编辑的通知。但我现在已经用downvote 换成了upvote ;)
【解决方案2】:

只需使用MyListOfLists.SelectMany(x =&gt; x).OrderByDescending(x =&gt; x).ToList()

SelectMany 会将您的列表合并为一个。 OrderByDescending 对该结果进行操作,并将结果按字母顺序排列(我认为您想要)。然后调用ToList强制执行,得到List&lt;string&gt;而不是IEnumerable&lt;string&gt;

【讨论】:

    【解决方案3】:

    我知道我参加聚会迟到了,但由于这篇文章是从我正在处理的另一篇文章中引用的,作为解决问题的基础,我觉得有必要为这个主题添加一些东西。

    问题中有两个相互矛盾的陈述:

    但是使用一些优化的 LINQ 函数来执行此操作会更好。

    然后

    那么:是否有一个漂亮的 LINQ 函数,或者多个函数的组合,可以将它变成漂亮的代码,或者我应该坚持(并可能优化)我当前的函数?

    优化漂亮之间有很大区别。当然,这取决于“优化”是什么意思。漂亮、可读、更短的代码可能被认为是为了维护而优化的,但大多数时候不是为了性能。所以让我们谈谈性能。接受的答案可能看起来很酷,但在时间和空间上效率低下(由于分组),甚至不提供类似“标准 LINQ”Concat 函数的延迟执行行为。

    在这方面,OP提供的原始功能要好得多。但是我们可以更进一步,创建一个更通用的函数(不需要列表),它可以有效地完成所有这些工作,同时遵循 LINQ 实现精神:

    static class Extensions
    {
        public static IEnumerable<T> Merge<T>(this IEnumerable<IEnumerable<T>> source)
        {
            var queue = new Queue<IEnumerator<T>>();
            IEnumerator<T> itemEnumerator = null;
            try
            {
                // First pass: yield the first element (if any) and schedule the next (if any)
                foreach (var list in source)
                {
                    itemEnumerator = list.GetEnumerator();
                    if (!itemEnumerator.MoveNext())
                        itemEnumerator.Dispose();
                    else
                    {
                        yield return itemEnumerator.Current;
                        if (itemEnumerator.MoveNext())
                            queue.Enqueue(itemEnumerator);
                        else
                            itemEnumerator.Dispose();
                    }
                }
                // Second pass: yield the current element and schedule the next (if any)
                while (queue.Count > 0)
                {
                    itemEnumerator = queue.Dequeue();
                    yield return itemEnumerator.Current;
                    if (itemEnumerator.MoveNext())
                        queue.Enqueue(itemEnumerator);
                    else
                        itemEnumerator.Dispose();
                }
            }
            finally
            {
                if (itemEnumerator != null) itemEnumerator.Dispose();
                while (queue.Count > 0) queue.Dequeue().Dispose();
            }
        }
    }
    

    请注意,我可以通过消除重复部分并合并两个通道来轻松缩短它,但这会降低它的可读性并且不会更快(事实上,由于需要一些额外的检查,它会有点慢)。

    总结:库(可重用)代码不必(并且在大多数情况下)很酷或很短。如果可以,那很好,但那不应该是驱动程序。

    【讨论】:

      【解决方案4】:

      使用Enumerable.SelectMany 将列表中的项目展平并从中创建一个新列表。

      var result = superlist.SelectMany(r=> r).ToList();
      

      【讨论】:

      • 这不会产生所需的顺序。
      【解决方案5】:

      如果列表包含不同的项目并且列表中没有重复的项目,则此方法有效。

      var result = list.SelectMany(x=>x)
                       .OrderBy(x=>list.First(a=>a.Contains(x))
                                       .IndexOf(x));
      

      【讨论】:

      • @Amy 哈哈?我已经对其进行了测试,如明确说明的那样,如果要求正常,则输出正常(顺序正确)。
      • @Amy 我的代码中的listList&lt;List&lt;string&gt;&gt; 类型,而不是List&lt;string&gt;
      【解决方案6】:

      没有简单或最佳的方式(通过性能和代码可读性)如何从所有列表中选择第一个元素,然后是第二个,等等。内置 linq 扩展。但是,您可以从代码构建自己的扩展,或者如果您不喜欢 while 循环,如下所示:

      public static class MyLinqExtensions
      {
          public static void MySelect<T>(this IEnumerable<IEnumerable<T>> superlist)
          {
              int index = 0;
      
              foreach (IEnumerable<T> list in superlist)
              {
                  if (index < list.Count())
                  {
                      yield return list.ElementAt(index);
                  }
      
                  index++;
              }
          }
      }
      

      最后,您可以在结果上调用Enumerable.Distinct() 以获得唯一值。

      【讨论】:

        【解决方案7】:

        我明白你在做什么:

        var result = input.SelectMany(l => l.Select((o, i) => new { o, i })).GroupBy(o => o.i).OrderBy(g => g.Key).SelectMany(g => g.Select(o => o.o);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-18
          • 1970-01-01
          • 2018-03-28
          • 1970-01-01
          • 2017-12-30
          相关资源
          最近更新 更多