是否有 LINQ 运算符可以执行此操作?

Is there a LINQ operator to do this?

我想知道是否有 LINQ 运算符可以执行此操作:

var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var combined = new [] { one, two };

var result = Operator(combined);

Console.WriteLine(result.Should().BeEquivalentTo(new [] { "A", "A", "B", "B", "C", "C", null, "D" }));

如果很短,它应该就像每个序列都是矩阵中的一行一样。之后,它应该:

  1. 转置矩阵(旋转)
  2. 它应该推送矩阵中的每个“单元格”,返回相应的项目,或者如果单元格为空则默认。

我的意思是,图形化:

A, B, C
A, B, C, D

<Transpose>

A, A
B, B
C, C
D

result => A, A, B, B, C, C, D, null

通知

Operator 应该在 IEnumerable<IEnumerable<T>>

上工作

如您所见,我感兴趣的 Operator 使用 combined,因此它接受应该 IEnumerable<IEnumerable<T>>(如 SelectMany)。

要跟上不断变化的规格有点困难。本来是一对字符串数组。我在回答中将其更改为一对 T 数组。

然后你在评论中写道“哦,不,我的意思是 N 个序列”。最后,在阅读之后,我注意到您更新了您的问题以询问表示为 IEnumerable<T>.

的 N 个集合

与此同时,我指出我的原始答案适用于 N 个数组,变化很小。所以,这里是:

对于 N 个数组

我使用 params 关键字删除了对 combined 变量的需要。 params 关键字将 将方法的部分或全部参数组合 到一个数组中。

这里有一个可以接受 N 个数组的方法:

public static IEnumerable<T> KnitArrays<T>(params T[][] arrays) 
{
    var maxLen = (from array in arrays select array.Length).Max();
    for (var i = 0; i < maxLen; i++)
    {
        foreach( var array in arrays)
        {
            yield return array.Length > i ? array[i] : default(T);
        }
    }
}

和原回答的逻辑差不多。测试代码看起来也一样:

var one = new[] { "A1", "B1", "C1" };
var two = new[] { "A2", "B2", "C2", "D2" };
var three = new[] { "A3", "B3" };

var knittedArray = KnitArrays(one, two, three);
List<string> result = knittedArray.ToList();
WriteCollectionContents(result);

其中WriteCollectionContents吐出合集内容。在这种情况下:

"A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", null, null, "D2", null,

对于 N 个列表 and/or 数组

事实证明,相同的基本代码可以与 IList<T> 一起使用,即,对于 List<T>T[]

public static IEnumerable<T> KnitILists<T>(params IList<T>[] ilists)
{
    var maxLen = (from ilist in ilists select ilist.Count).Max();
    for (var i = 0; i < maxLen; i++)
    {
        foreach (var ilist in ilists)
        {
            yield return ilist.Count > i ? ilist[i] : default(T);
        }
    }
}

此测试代码看起来也非常相似 - 但请注意数组和列表的混合:

var one = new[] { "A1", "B1", "C1" };
var two = new[] { "A2", "B2", "C2", "D2" };
var list3 = new List<string> { "A3", "B3" };

var knittedLists = KnitILists(one, two, list3);
result = knittedLists.ToList();
WriteCollectionContents(result);

结果完全相同:

"A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", null, null, "D2", null,

它与 IList<T> 一起工作的原因是该接口有一个 Count 属性 和一个 Item 索引器。如果您转到 ICollection<T>Count 属性 会保留,但您会丢失 Item 索引器。

一旦你到达 IEnumerable<T>Count 属性 和 Item 索引器都消失了。您可以对 IEnumerable 做的唯一事情就是遍历它。因此,逻辑需要非常不同。

我可能会想出一个解决方案。但是,它可能看起来与@gertarnold 的回答非常相似。

我正在寻找你即将发表的评论的前言,内容是关于你真正打算如何将其与 multi-dimensional 数组一起使用。

原回答如下

这样的事情怎么样:

public static IEnumerable<T> KnitArrays<T>(T[] first, T[] second) 
{
    var maxLen = Math.Max(first.Length, second.Length);
    for (var i = 0; i < maxLen; i++)
    {
        yield return first.Length > i ? first[i] : default(T);
        yield return second.Length > i ? second[i] : default(T);
    }
}

用以下方法测试:

var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };

var knittedArray = KnitArrays(one, two);
List<string> result = knittedArray.ToList();

生成一个看起来像您所要求的列表。请注意,我只是 return 一个 non-materialized IEnumerable,因为你问的是 LINQ。

试试这个

var result = Enumerable.Range(0, Math.Max(one.Count(), two.Count()))
.SelectMany(n => new[] { one.ElementAtOrDefault(n), two.ElementAtOrDefault(n) });

结果

["A","A","B","B","C","C",null,"D"]

为了使结果独立于数组的数量,函数应该遍历所有数组并继续返回,直到所有枚举都用完:

public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> source)
{
    var enumerators = source.Select(e => e.GetEnumerator()).ToArray();
    try
    {
        var next = false;
        do
        {
            var results = enumerators.Select(enr =>
            {
                if (enr.MoveNext())
                {
                    return enr.Current;
                }
                return default;
            }).ToList();
            next = results.Any(e => !Equals(default, e));
            if (next)
            {
                yield return results;
            }
        }
        while (next);
    }
    finally
    {
        Array.ForEach(enumerators, e => e.Dispose());
    }
}

现在您可以使用任意数量的数组:

var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var three = new[] { "U", "V","W", "X", "Y", "Z" };
var combined = new[] { one, three, two };
var result = combined.Transpose().SelectMany(e => e).ToList();

这将导致

"A","U","A","B","V","B","C","W","C",null,"X","D",null,"Y",null,null,"Z",null

过滤空值很简单。

(基本思想由 提供,但仅适用于等长数组)。