【问题标题】:Zip N IEnumerable<T>s together? Iterate over them simultaneously?将 N 个 IEnumerable<T> 压缩在一起?同时迭代它们?
【发布时间】:2011-04-28 16:58:55
【问题描述】:

我有:-

IEnumerable<IEnumerable<T>> items;

我想创建:-

IEnumerable<IEnumerable<T>> results;

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

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

例如:-

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

编辑:另一个示例(在 cmets 中请求):-

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" };

我事先不知道有多少个序列,也不知道每个序列有多少个元素。我可能有 1,000 个序列,每个序列有 1,000,000 个元素,我可能只需要前 10 个,所以如果可以的话,我想使用源序列的(惰性)枚举。如果可以的话,我尤其不想创建新的数据结构。

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

还有其他方法吗?

【问题讨论】:

  • 如果项目包含三个序列会怎样?
  • 这类似于"How to iterate over two arrays at once?"(涵盖N = 2的情况)
  • 查看 Eric Lippert 的博客(该博客从一个 SO 问题的答案扩展而来)关于在任意多个序列上计算笛卡尔积。 blogs.msdn.com/b/ericlippert/archive/2010/06/28/…
  • 除了对不同长度的要求外,这听起来很像矩阵的转置。我在这里为 LINQ 写了一个 Transpose 方法:stackoverflow.com/questions/2070356/…
  • @Richard:添加了一个示例,@James:是的,但是如果您事先知道有多少个序列,这很容易 :(,@Anthony:唉,Eric 可以使用累加器,所以没有t 需要枢轴,@dtb:您的转置方法看起来是一个好的开始。

标签: c# .net linq


【解决方案1】:

现在经过轻微测试并可以正常处理。

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

【讨论】:

  • 请注意,您不会处理任何迭代器,这可能会很糟糕,具体取决于它们正在迭代的内容。
  • 很有趣。当发生惰性时,我不知道最终会在这些迭代器方法中被激活。不过是有道理的,因为调用者最终会在惰性 IEnumerator 上调用 Dispose。
  • 我已经接受了这个答案,但请确保您也阅读了 Jon Skeet 的答案。
【解决方案2】:

合理如果你能保证结果将如何被使用的话,这很容易做到。但是,如果结果可能以任意顺序使用,您可能需要缓冲所有内容。考虑一下:

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 中的项目,您要么需要记住这些项目准备重新评估子序列。 p>

同样,您要么需要将子序列缓冲为 IEnumerable&lt;T&gt; 值,要么每次都重新读取整个批次。

基本上这是一整罐蠕虫,很难优雅地以​​一种适用于所有情况的方式优雅地完成:(如果您有一个特定的情况并有适当的限制,我们可能会能够提供更多帮助。

【讨论】:

  • 谢谢!谢天谢地,我可以保证结果将按顺序使用。这是否减轻了在 DavidB 的回答中调用“ToList”的需要? (现在去测试)
  • @Iain:我怀疑是这样,尽管正确处理可能相当复杂。您仍然需要将子序列引用本身缓冲为IEnumerable&lt;T&gt;。嗯。除了返回IEnumerable&lt;IEnumerable&lt;T&gt;&gt;,你可以传入一个对每个值执行的操作,或者类似的东西吗?它会让事情变得更简单!
  • 我不认为我可以传递一个动作(虽然好主意,我会记住它以应对未来的类似问题!)。在这个阶段,我在想,如果让它变得优雅会让它变得超级复杂,我会继续把它全部内联。
【解决方案3】:

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

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

【讨论】:

  • 与 AS-CII 的回答类似,这在源上调用 GetEnumerator() 过于频繁。
【解决方案4】:

这个怎么样?

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

【讨论】:

  • 如果内存中的数据已经以一种可以随机访问的形式存在,那就这么简单。不幸的是,我需要源 IEnumerables 的惰性枚举。调用 z[i] - 或 z.ElementAt(i) - 会破坏这一点。
【解决方案5】:

你可以像这样组合现有的运算符,

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

【讨论】:

    【解决方案6】:

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

    不同之处在于 enumerators 变量不会一直重新创建。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-15
      • 2020-08-22
      • 2011-06-20
      • 2019-08-12
      • 1970-01-01
      • 2014-06-11
      相关资源
      最近更新 更多