如何从包含多个列表的列表中创建新列表,其中新列表中的元素按交替顺序排列?

4
假设我有一个列表,其中包含多个子列表。我想要从给定的列表中创建一个新列表,使其元素按照下面的示例顺序排列。
输入:-
List<List<int>> l = new List<List<int>>();

List<int> a = new List<int>();
a.Add(1);
a.Add(2);
a.Add(3);
a.Add(4);
List<int> b = new List<int>();
b.Add(11);
b.Add(12);
b.Add(13);
b.Add(14);
b.Add(15);
b.Add(16);
b.Add(17);
b.Add(18);

l.Add(a);
l.Add(b);

输出(列表):

1
11
2
12
3
13
4
14
15
16 

输出列表必须不超过10个元素。

我目前是使用while内的foreach来实现这一点,但我想知道如何使用LINQ来实现。

int loopCounter = 0,index=0;
List<int> o=new List<int>();
while(o.Count<10)
{
    foreach(List<int> x in l)
    {
        if(o.Count<10)
           o.Add(x[index]);
    }
    index++;
}

谢谢。

使用Zip()Take()函数。 - Dido
@Dido - zip 不会包括那些只在一个列表中而不在另一个列表中的项目。 - Gilad Green
你必须使用LINQ的原因是什么? - Caius Jard
1
考虑使用 ZipLongest - https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/ZipLongest.cs 。 - mjwills
3个回答

3
使用接收项目索引的SelectManySelect重载来应用所需的排序。使用SelectMany以展开嵌套集合层级。最后,应用Take来仅检索所需数量的项目:
var result = l.SelectMany((nested, index) => 
                  nested.Select((item, nestedIndex) => (index, nestedIndex, item)))
              .OrderBy(i => i.nestedIndex)
              .ThenBy(i => i.index)
              .Select(i => i.item)
              .Take(10);

或者使用查询语法:

var result = (from c in l.Select((nestedCollection, index) => (nestedCollection, index))
              from i in c.nestedCollection.Select((item, index) => (item, index))
              orderby i.index, c.index
              select i.item).Take(10);

如果使用C# 6.0及以前的项目,请使用匿名类型:
var result = l.SelectMany((nested, index) => 
                  nested.Select((item, nestedIndex) => new {index, nestedIndex, item}))
              .OrderBy(i => i.nestedIndex)
              .ThenBy(i => i.index)
              .Select(i => i.item)
              .Take(10);

为什么仅使用 Zip 不够: Zip 相当于在第一个集合上执行 join 操作,其中联接的属性是索引。因此,只有在第二个集合中存在匹配项的第一个集合中的项才会出现在结果中。
下一个选项是考虑使用 left join,它将返回第一个集合中所有与第二个集合匹配(如果存在)的项。在 OP 描述的情况下,他需要实现 full outer join 的功能 - 获取两个集合的所有项并匹配可能的项。

@Caius Jard - 请查看有关使用 Zip 的问题的解释。 - Gilad Green
在第一个例子中,您使用的是哪个C#版本?因为它无法编译。它说“index”与方法参数名称相同。 - elirandav
@KernelMode - C# 7.0。在此之前的版本(6及以下)在其下方描述。 - Gilad Green
我明白了。谢谢! - elirandav

2

我知道你要求使用LINQ,但我经常感觉LINQ就像一把锤子,只要开发者找到它,每个问题都是钉子。从可读性/可维护性的角度来看,我不会用LINQ来完成这个任务,因为我认为以下代码更简单、更易于理解/更自文档化:

List<int> r = new List<int>(10);
for(int i = 0; i < 10; i++){
  if(i < a.Count)
    r.Add(a[i]);
  if(i < b.Count)
    r.Add(b[i]);
}

你不需要提前停止循环,即使a和b只有8个项目,但是你可以通过扩展for循环的测试来实现。
我认为这种情况比LINQ更高效,因为它做的要少得多。
如果您的使用LINQ是出于学术目的(这是一项必须使用LINQ的作业),则请继续,但是如果它是某个可怜家伙将来必须维护的正常日常系统,我恳求您考虑是否这适用于LINQ的好应用。

如果输入不是IList<T>,您可以利用枚举器而不是List<T>。 - qxg
仅适用于2个内部列表 - 不适用于要求的列表列表 :) - Patrick Artner
这是在哪里要求的?将其转换为两个for循环很简单,但我的答案更多地是为了说明考虑Lina是否合适,而不是解决可能隐含而非陈述的问题。如果您参考问题中的循环代码,则应该知道在发布此答案时该代码并不存在。 - Caius Jard

0

这将处理2个或更多内部的List<List<int>> - 它通过yield返回一个IEnumerable<int>,因此您必须调用.ToList()将其转换为列表。Linq.Any用于中断条件。

如果任何列表为空,则会抛出异常。根据您的喜好添加检查。

static IEnumerable<int> FlattenZip (List<List<int>> ienum, int maxLength = int.MaxValue)
{
  int done = 0;
  int index = 0;
  int yielded = 0;

  while (yielded <= maxLength && ienum.Any (list => index < list.Count))
    foreach (var l in ienum)
    {
      done++;

      if (index < l.Count)
      {
        // this list is big enough, we will take one out
        yielded++;
        yield return l[index];
      }

      if (yielded > maxLength)
        break; // we are done

      if (done % (ienum.Count) == 0)
        index += 1; // checked all lists, advancing index
    }
}

public static void Main ()
{
  // other testcases to consider: 
  //   in total too few elememts
  //   one list empty (but not null)
  //   too many lists (11 for 10 elements) 

  var l1 = new List<int> { 1, 2, 3, 4 };
  var l2 = new List<int> { 11, 12, 13, 14, 15, 16 };
  var l3 = new List<int> { 21, 22, 23, 24, 25, 26 };

  var l = new List<List<int>> { l1, l2, l3 };

  var zipped = FlattenZip (l, 10);

  Console.WriteLine (string.Join (", ", zipped));
  Console.ReadLine ();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接