如果我有一个包含任意数量列表的列表,如下所示:
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...
};
...有没有办法以某种方式将列表“压缩”或“旋转”成这样的东西?
{
{ "a", "1", "w", ... },
{ "b", "2", "x", ... },
{ "c", "3", "y", ... },
{ "d", "4", "z", ... }
}
显而易见的解决方案是做这样的事情:
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();
...但我想知道是否有更清洁的方法,使用linq或其他方式?
答案 0 :(得分:18)
您可以滚动自己的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();
}
}
答案 1 :(得分:12)
您可以使用Select
Func<T, int, TOut>
扩展名执行此操作
var rotatedList = myList.Select(inner => inner.Select((s, i) => new {s, i}))
.SelectMany(a => a)
.GroupBy(a => a.i, a => a.s)
.Select(a => a.ToList()).ToList();
这将为您提供另一个List<List<string>>
。
<强>击穿强>
.Select(inner => inner.Select((s, i) => new {s, i}))
对于每个内部列表,我们将列表的内容投影到具有两个属性的新匿名对象:s
,字符串值,以及i
原始列表中该值的索引。
.SelectMany(a => a)
我们将结果展平为单个列表
.GroupBy(a => a.i, a => a.s)
我们按匿名对象的i
属性进行分组(回想一下这是索引)并选择s
属性作为我们的值(仅限字符串)。
.Select(a => a.ToList()).ToList();
对于每个组,我们将可枚举更改为列表,并将所有组的另一个列表更改。
答案 2 :(得分:5)
如何将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:
答案 3 :(得分:3)
这是基于Matrix Transposition的低效变体:
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;
}
}
答案 4 :(得分:2)
看看linqlib project on codeplex,它有一个旋转功能,可以完全满足你的需要。
答案 5 :(得分:1)
您可以使用Range缩小for
循环:
var result = Enumerable.Range(0, myList.Min(l => l.Count))
.Select(i => myList.Select(l => l[i]).ToList()).ToList();
答案 6 :(得分:0)
(from count in Range(myList[0].Count)
select new List<string>(
from count2 in Range(myList.Count)
select myList[count2][count])
).ToList();
它不漂亮,但我认为它会起作用。
答案 7 :(得分:0)
这是一个ZipMany
的递归实现,它受an answer的@Enigmativity启发到另一个问题。
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));
}
}
}
优势:避免了与枚举器配置有关的问题。
劣势:扩展性很差。递归开销在大约3000枚举时变得很明显,并且此后呈指数增长。