C# 如何";zip";或;轮换;可变数量的列表?

C# 如何";zip";或;轮换;可变数量的列表?,c#,algorithm,linq,C#,Algorithm,Linq,如果我有一个包含任意数量列表的列表,如下所示: var myList = new List<List<string>>() { new List<string>() { "a", "b", "c", "d" }, new List<string>() { "1", "2", "3", "4" }, new List<string>() { "w", "x", "y", "z" }, // ...etc..

如果我有一个包含任意数量列表的列表,如下所示:

var myList = new List<List<string>>()
{
    new List<string>() { "a", "b", "c", "d" },
    new List<string>() { "1", "2", "3", "4" },
    new List<string>() { "w", "x", "y", "z" },
    // ...etc...
};
显而易见的解决办法是这样做:

public static IEnumerable<IEnumerable<T>> Rotate<T>(this IEnumerable<IEnumerable<T>> list)
{
    for (int i = 0; i < list.Min(x => x.Count()); i++)
    {
        yield return list.Select(x => x.ElementAt(i));
    }
}

// snip

var newList = myList.Rotate();
公共静态IEnumerable旋转(此IEnumerable列表)
{
对于(inti=0;ix.Count());i++)
{
收益率返回列表。选择(x=>x.ElementAt(i));
}
}
//剪断
var newList=myList.Rotate();

…但我想知道是否有更干净的方法,使用linq或其他方法?

您可以通过使用
选择
扩展名执行
函数来实现这一点:

这将为您提供另一个
列表

细分

.Select(inner => inner.Select((s, i) => new {s, i}))
对于每个内部列表,我们将列表的内容投影到一个新的匿名对象,该对象具有两个属性:
s
、字符串值和原始列表中该值的索引

.SelectMany(a => a)
我们将结果展平为一个列表

.GroupBy(a => a.i, a => a.s)
我们根据匿名对象的
i
属性进行分组(回想一下,这是索引),并选择
s
属性作为我们的值(仅字符串)


对于每个组,我们将所有组的可枚举列表更改为一个列表和另一个列表。

使用带有一些索引的
SelectMany
GroupBy
如何

// 1. Project inner lists to a single list (SelectMany)
// 2. Use "GroupBy" to aggregate the item's based on order in the lists
// 3. Strip away any ordering key in the final answer
var query = myList.SelectMany(
    xl => xl.Select((vv,ii) => new { Idx = ii, Value = vv }))
       .GroupBy(xx => xx.Idx)
       .OrderBy(gg => gg.Key)
       .Select(gg => gg.Select(xx => xx.Value));
来自LinqPad:

(from count in Range(myList[0].Count)
select new List<string>(
    from count2 in Range(myList.Count)
    select myList[count2][count])
    ).ToList();
(从范围内的计数(myList[0]。计数)
选择新列表(
从范围内的count2开始(myList.Count)
选择myList[计数2][计数])
).ToList();

这并不漂亮,但我认为它会起作用。

您可以使用以下方法压缩
循环的


下面是一个基于矩阵变换的低效变体:

public static class Ext
{
    public static IEnumerable<IEnumerable<T>> Rotate<T>(
        this IEnumerable<IEnumerable<T>> src)
    {
        var matrix = src.Select(subset => subset.ToArray()).ToArray();
        var height = matrix.Length;
        var width = matrix.Max(arr => arr.Length);

        T[][] transpose = Enumerable
            .Range(0, width)
            .Select(_ => new T[height]).ToArray();
        for(int i=0; i<height; i++)
        {        
            for(int j=0; j<width; j++)
            {            
                transpose[j][i] = matrix[i][j];            
            }
        }

        return transpose;
    }
}
公共静态类Ext
{
公共静态可数旋转(
这是IEnumerable(src)
{
var matrix=src.Select(子集=>subset.ToArray()).ToArray();
变量高度=矩阵长度;
变量宽度=矩阵最大值(arr=>arr.Length);
T[][]转置=可枚举
.范围(0,宽度)
.Select(=>newt[height]).ToArray();

对于(int i=0;i您可以滚动您自己的ZipMany实例,手动迭代每个枚举。在投影每个序列后,这在较大序列上的性能可能比使用
GroupBy
的序列更好:

public static IEnumerable<TResult> ZipMany<TSource, TResult>(
    IEnumerable<IEnumerable<TSource>> source,
    Func<IEnumerable<TSource>, TResult> selector)
{
   // ToList is necessary to avoid deferred execution
   var enumerators = source.Select(seq => seq.GetEnumerator()).ToList();
   try
   {
     while (true)
     {
       foreach (var e in enumerators)
       {
           bool b = e.MoveNext();
           if (!b) yield break;
       }
       // Again, ToList (or ToArray) is necessary to avoid deferred execution
       yield return selector(enumerators.Select(e => e.Current).ToList());
     }
   }
   finally
   {
       foreach (var e in enumerators) 
         e.Dispose();
   }
}
公共静态IEnumerable ZipMany(
IEnumerable来源,
Func选择器)
{
//ToList是避免延迟执行所必需的
var enumerators=source.Select(seq=>seq.GetEnumerator()).ToList();
尝试
{
while(true)
{
foreach(枚举数中的变量e)
{
bool b=e.MoveNext();
若(!b)屈服断裂;
}
//同样,ToList(或ToArray)是避免延迟执行所必需的
产生返回选择器(枚举数.Select(e=>e.Current.ToList());
}
}
最后
{
foreach(枚举数中的变量e)
e、 处置();
}
}

看看,它有一个旋转函数,可以完全满足您的需要。

这里是一个递归的
ZipMany
实现,灵感来自于一个不同的问题

public static IEnumerable<IEnumerable<T>> ZipMany<T>(params IEnumerable<T>[] sources)
{
    return ZipRecursive(sources);
    IEnumerable<IEnumerable<T>> ZipRecursive(IEnumerable<IEnumerable<T>> ss)
    {
        if (!ss.Skip(1).Any())
        {
            return ss.First().Select(i => Enumerable.Repeat(i, 1));
        }
        else
        {
            return ZipRecursive(ss.Skip(1).ToArray()).Zip(ss.First(), (x, y) => x.Prepend(y));
        }
    }
}
公共静态IEnumerable ZipMany(params IEnumerable[]源)
{
返回前兆(源);
IEnumerable-Zipursive(IEnumerable-ss)
{
如果(!ss.Skip(1).Any())
{
返回ss.First().Select(i=>Enumerable.Repeat(i,1));
}
其他的
{
返回ZipRecursive(ss.Skip(1.ToArray()).Zip(ss.First(),(x,y)=>x.Prepend(y));
}
}
}
优点:避免了与枚举数配置相关的问题。

缺点:可伸缩性差。递归开销在3000个可枚举数左右时变得明显,然后呈指数级增长。

为什么要专门使用LINQ来解决它?@Moo Juice——好的一点,我编辑了我的标题。我想我只是自动假设解决方案会以某种方式使用LINQ,但我想不一定是这样。如果序列不是随机访问,您的示例代码将非常低效。我对另一个问题给出了一般性的答案,该问题使用LINQ更受限制,并且是对您问题的更直接的回答。可能是+1的重复,但我在这里可能是少数认为OPs原始方法是正确的更具可读性。我也在船上,但仍然impressed@Moo-Juice我同意。而且它能产生更好的性能。我认为这是一个边缘案例,LINQ可以工作,但远不是最优的。+1答案很好。即使只是为了答案,而不是效率,这也是一个很好的解决方案。@SimonBelanger:我修改了我的评论,假设你有一个sorted键的顺序,它们将按该顺序返回。我认为这个解决方案只有在
mylist
中有三个列表时才有效。我更希望有一个解决方案,无论有多少个列表都有效。好的,我想我的编辑可以实现这一点。不是100%确定,但基本思想是存在的。+1。这实际上更符合实际问题中使用了方法的签名(
IEnumerable
)。在电话上键入该方法一定要花很长时间…:)我去帮你清理了它。我相信它抓住了你的意图。@JeffMercado你比我快了5秒。我有几乎相同的代码(我测试过,它工作正常)。我认为这是一个很好的编辑。这种方法会留下未处理的枚举数。@Oliver:啊,现在我理解了你问题的要旨。你完全正确;我忽略了处理枚举数。我会解决它。@Moo Juice效率不太高,尽管它可以改进。很多事情都需要安排。:)
var result = Enumerable.Range(0, myList.Min(l => l.Count))
    .Select(i => myList.Select(l => l[i]).ToList()).ToList();
public static class Ext
{
    public static IEnumerable<IEnumerable<T>> Rotate<T>(
        this IEnumerable<IEnumerable<T>> src)
    {
        var matrix = src.Select(subset => subset.ToArray()).ToArray();
        var height = matrix.Length;
        var width = matrix.Max(arr => arr.Length);

        T[][] transpose = Enumerable
            .Range(0, width)
            .Select(_ => new T[height]).ToArray();
        for(int i=0; i<height; i++)
        {        
            for(int j=0; j<width; j++)
            {            
                transpose[j][i] = matrix[i][j];            
            }
        }

        return transpose;
    }
}
public static IEnumerable<TResult> ZipMany<TSource, TResult>(
    IEnumerable<IEnumerable<TSource>> source,
    Func<IEnumerable<TSource>, TResult> selector)
{
   // ToList is necessary to avoid deferred execution
   var enumerators = source.Select(seq => seq.GetEnumerator()).ToList();
   try
   {
     while (true)
     {
       foreach (var e in enumerators)
       {
           bool b = e.MoveNext();
           if (!b) yield break;
       }
       // Again, ToList (or ToArray) is necessary to avoid deferred execution
       yield return selector(enumerators.Select(e => e.Current).ToList());
     }
   }
   finally
   {
       foreach (var e in enumerators) 
         e.Dispose();
   }
}
public static IEnumerable<IEnumerable<T>> ZipMany<T>(params IEnumerable<T>[] sources)
{
    return ZipRecursive(sources);
    IEnumerable<IEnumerable<T>> ZipRecursive(IEnumerable<IEnumerable<T>> ss)
    {
        if (!ss.Skip(1).Any())
        {
            return ss.First().Select(i => Enumerable.Repeat(i, 1));
        }
        else
        {
            return ZipRecursive(ss.Skip(1).ToArray()).Zip(ss.First(), (x, y) => x.Prepend(y));
        }
    }
}