压缩N IEnumerable <t>一起?同时迭代它们?</t>

时间:2010-10-21 15:48:07

标签: c# .net linq

我有: -

IEnumerable<IEnumerable<T>> items;

我想创建: -

IEnumerable<IEnumerable<T>> results;

其中“results”中的第一项是“items”的每个IEnumebles的第一项的IEnumerable,“results”中的第二项是每个“items”的第二项的IEnumerable等。

IEnumerables的长度不一定相同。如果项目中的某些IEnumerables在特定索引中没有元素,那么我希望结果中匹配的IEnumerable中包含更少的项目。

例如: -

items = { "1", "2", "3", "4" } , { "a", "b", "c" };
results = { "1", "a" } , { "2", "b" }, { "3", "c" }, { "4" };

编辑:另一个例子(评论中要求): -

items = { "1", "2", "3", "4" } , { "a", "b", "c" }, { "p", "q", "r", "s", "t" };
results = { "1", "a", "p" } , { "2", "b", "q" }, { "3", "c", "r" }, { "4", "s" }, { "t" };

我事先并不知道有多少序列,也不知道每个序列中有多少元素。我可能有1000个序列,每个序列有1,000,000个元素,我可能只需要第一个~10个,所以我想使用源序列的(惰性)枚举,如果可以的话。特别是如果我能帮助它,我不想创建新的数据结构。

是否有可以执行此操作的内置方法(类似于IEnumerable.Zip)?

还有其他办法吗?

6 个答案:

答案 0 :(得分:7)

现在进行了轻度测试并进行了处理。

public static class Extensions
{
  public static IEnumerable<IEnumerable<T>> JaggedPivot<T>(
    this IEnumerable<IEnumerable<T>> source)
  {
    List<IEnumerator<T>> originalEnumerators = source
      .Select(x => x.GetEnumerator())
      .ToList();

    try
    {
      List<IEnumerator<T>> enumerators = originalEnumerators
        .Where(x => x.MoveNext()).ToList();

      while (enumerators.Any())
      {
        List<T> result = enumerators.Select(x => x.Current).ToList();
        yield return result;
        enumerators = enumerators.Where(x => x.MoveNext()).ToList();
      }
    }
    finally
    {
      originalEnumerators.ForEach(x => x.Dispose());
    }
  } 
}

public class TestExtensions
{
  public void Test1()
  {
    IEnumerable<IEnumerable<int>> myInts = new List<IEnumerable<int>>()
    {
      Enumerable.Range(1, 20).ToList(),
      Enumerable.Range(21, 5).ToList(),
      Enumerable.Range(26, 15).ToList()
    };

    foreach(IEnumerable<int> x in myInts.JaggedPivot().Take(10))
    {
      foreach(int i in x)
      {
        Console.Write("{0} ", i);
      }
      Console.WriteLine();
    }
  }
}

答案 1 :(得分:4)

如果您可以保证结果将如何使用,那么可以合理地直接进行。但是,如果结果可能以任意顺序使用,则可能需要缓冲所有内容。考虑一下:

var results = MethodToBeImplemented(sequences);
var iterator = results.GetEnumerator();
iterator.MoveNext();
var first = iterator.Current;
iterator.MoveNext();
var second = iterator.Current;
foreach (var x in second)
{
    // Do something
}
foreach (var x in first)
{
    // Do something
}

为了获得“秒”中的项目,您必须遍历所有子序列,过去第一项。如果您希望它有效迭代first中的项目,则

同样,您需要将子序列缓冲为IEnumerable<T>值,或者每次重读整个批次。

基本上它是一整套蠕虫,很难以优雅的方式在所有情况下都能很好地工作:(如果你有适当的约束条件的特定的情况,我们可能会能够提供更多帮助。

答案 2 :(得分:1)

基于David B's answer,此代码的效果应该更好:

public static IEnumerable<IEnumerable<T>> JaggedPivot<T>(
    this IEnumerable<IEnumerable<T>> source)
{
    var originalEnumerators = source.Select(x => x.GetEnumerator()).ToList();
    try
    {
        var enumerators =
            new List<IEnumerator<T>>(originalEnumerators.Where(x => x.MoveNext()));

        while (enumerators.Any())
        {
            yield return enumerators.Select(x => x.Current).ToList();
            enumerators.RemoveAll(x => !x.MoveNext());
        }
    }
    finally
    {
        originalEnumerators.ForEach(x => x.Dispose());
    }
}

不同之处在于,枚举器变量不会一直重新创建。

答案 3 :(得分:0)

这是一个有点短,但无疑效率较低的一个:

Enumerable.Range(0,items.Select(x => x.Count()).Max())
    .Select(x => items.SelectMany(y => y.Skip(x).Take(1)));

答案 4 :(得分:0)

这个怎么样?

        List<string[]> items = new List<string[]>()
        {
            new string[] { "a", "b", "c" },
            new string[] { "1", "2", "3" },
            new string[] { "x", "y" },
            new string[] { "y", "z", "w" }
        };

        var x = from i in Enumerable.Range(0, items.Max(a => a.Length))
                select from z in items
                       where z.Length > i
                       select z[i];

答案 5 :(得分:0)

你可以组合像这样的现有运营商,

IEnumerable<IEnumerable<int>> myInts = new List<IEnumerable<int>>()
    {
        Enumerable.Range(1, 20).ToList(),
        Enumerable.Range(21, 5).ToList(),
        Enumerable.Range(26, 15).ToList()
    };

myInts.SelectMany(item => item.Select((number, index) => Tuple.Create(index, number)))
      .GroupBy(item => item.Item1)
      .Select(group => group.Select(tuple => tuple.Item2));