代码之家  ›  专栏  ›  技术社区  ›  Iain Galloway

将n IEnumerable<t>s压缩在一起?同时迭代它们?

  •  7
  • Iain Galloway  · 技术社区  · 14 年前

    我有:

    IEnumerable<IEnumerable<T>> items;
    

    我想创造:

    IEnumerable<IEnumerable<T>> results;
    

    如果“结果”中的第一项是“项”中每个IEnumerable的第一项的IEnumerable,“结果”中的第二项是每个“项”的第二项的IEnumerable等。

    IEnumerable的长度不一定相同。如果项中的某些IEnumerable在特定索引处没有元素,那么我希望结果中匹配的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个序列,每个序列中有1000000个元素,我可能只需要前10个元素,所以如果可以的话,我想使用源序列的(惰性)枚举。特别是,如果我可以帮助的话,我不想创建新的数据结构。

    是否有内置方法(类似于ienumerable.zip)可以做到这一点?

    还有别的办法吗?

    6 回复  |  直到 14 年前
        1
  •  7
  •   Amy B    14 年前

    现在进行了轻微的测试,并进行了处理。

    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();
        }
      }
    }
    
        2
  •  4
  •   Jon Skeet    14 年前

    它是 合理地 如果你能保证结果的使用方法,那就很简单了。但是,如果结果可以任意顺序使用,则可能需要缓冲所有内容。考虑一下:

    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
    }
    

    为了获得“second”中的项,您必须遍历所有子序列, 过去的 第一项。如果您希望它在 first 任何一个 需要记住物品 准备重新评估子序列。

    同样,您也需要缓冲子序列 IEnumerable<T> 每次都要看重或重读整批。

    基本上,它是一整罐蠕虫,很难以一种适合所有情况的方式优雅地工作:(如果你有一个 具体的 在有适当约束的情况下,我们也许能提供更多的帮助。

        3
  •  1
  •   Community Anvaka    7 年前

    基于 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());
        }
    }
    

    不同的是,枚举器变量并非总是被重新创建。

        4
  •  0
  •   diceguyd30    14 年前

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

    Enumerable.Range(0,items.Select(x => x.Count()).Max())
        .Select(x => items.SelectMany(y => y.Skip(x).Take(1)));
    
        5
  •  0
  •   as-cii    14 年前

    这个怎么样?

            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];
    
        6
  •  0
  •   Pragmateek    10 年前

    您可以这样组合现有的操作符,

    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));