如何同时迭代两个 IEnumerable?

发布于 2024-08-30 09:51:39 字数 242 浏览 4 评论 0原文

我有两个枚举: IEnumerable; list1IEnumerable列表2。我想同时迭代它们,例如:

foreach((a, b) in (list1, list2))
{
    // use a and b
}

如果它们不包含相同数量的元素,则应抛出异常。

最好的方法是什么?

I have two enumerables: IEnumerable<A> list1 and IEnumerable<B> list2. I would like to iterate through them simultaneously like:

foreach((a, b) in (list1, list2))
{
    // use a and b
}

If they don't contain the same number of elements, an exception should be thrown.

What is the best way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

孤蝉 2024-09-06 09:51:39

您需要类似 Zip< /a> LINQ 运算符 - 但 .NET 4 中的版本总是在任一序列完成时截断。

MoreLINQ 实现 有一个 EquiZip方法将抛出 InvalidOperationException 相反。

var zipped = list1.EquiZip(list2, (a, b) => new { a, b });

foreach (var element in zipped)
{
    // use element.a and element.b
}

You want something like the Zip LINQ operator - but the version in .NET 4 always just truncates when either sequence finishes.

The MoreLINQ implementation has an EquiZip method which will throw an InvalidOperationException instead.

var zipped = list1.EquiZip(list2, (a, b) => new { a, b });

foreach (var element in zipped)
{
    // use element.a and element.b
}
余厌 2024-09-06 09:51:39

下面是此操作的一种实现,通常称为 Zip:

using System;
using System.Collections.Generic;

namespace SO2721939
{
    public sealed class ZipEntry<T1, T2>
    {
        public ZipEntry(int index, T1 value1, T2 value2)
        {
            Index = index;
            Value1 = value1;
            Value2 = value2;
        }

        public int Index { get; private set; }
        public T1 Value1 { get; private set; }
        public T2 Value2 { get; private set; }
    }

    public static class EnumerableExtensions
    {
        public static IEnumerable<ZipEntry<T1, T2>> Zip<T1, T2>(
            this IEnumerable<T1> collection1, IEnumerable<T2> collection2)
        {
            if (collection1 == null)
                throw new ArgumentNullException("collection1");
            if (collection2 == null)
                throw new ArgumentNullException("collection2");

            int index = 0;
            using (IEnumerator<T1> enumerator1 = collection1.GetEnumerator())
            using (IEnumerator<T2> enumerator2 = collection2.GetEnumerator())
            {
                while (enumerator1.MoveNext() && enumerator2.MoveNext())
                {
                    yield return new ZipEntry<T1, T2>(
                        index, enumerator1.Current, enumerator2.Current);
                    index++;
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[] numbers = new[] { 1, 2, 3, 4, 5 };
            string[] names = new[] { "Bob", "Alice", "Mark", "John", "Mary" };

            foreach (var entry in numbers.Zip(names))
            {
                Console.Out.WriteLine(entry.Index + ": "
                    + entry.Value1 + "-" + entry.Value2);
            }
        }
    }
}

要使其在其中一个序列用完值时抛出异常,请更改 while 循环,如下所示:

while (true)
{
    bool hasNext1 = enumerator1.MoveNext();
    bool hasNext2 = enumerator2.MoveNext();
    if (hasNext1 != hasNext2)
        throw new InvalidOperationException("One of the collections ran " +
            "out of values before the other");
    if (!hasNext1)
        break;

    yield return new ZipEntry<T1, T2>(
        index, enumerator1.Current, enumerator2.Current);
    index++;
}

Here's an implementation of this operation, typically called Zip:

using System;
using System.Collections.Generic;

namespace SO2721939
{
    public sealed class ZipEntry<T1, T2>
    {
        public ZipEntry(int index, T1 value1, T2 value2)
        {
            Index = index;
            Value1 = value1;
            Value2 = value2;
        }

        public int Index { get; private set; }
        public T1 Value1 { get; private set; }
        public T2 Value2 { get; private set; }
    }

    public static class EnumerableExtensions
    {
        public static IEnumerable<ZipEntry<T1, T2>> Zip<T1, T2>(
            this IEnumerable<T1> collection1, IEnumerable<T2> collection2)
        {
            if (collection1 == null)
                throw new ArgumentNullException("collection1");
            if (collection2 == null)
                throw new ArgumentNullException("collection2");

            int index = 0;
            using (IEnumerator<T1> enumerator1 = collection1.GetEnumerator())
            using (IEnumerator<T2> enumerator2 = collection2.GetEnumerator())
            {
                while (enumerator1.MoveNext() && enumerator2.MoveNext())
                {
                    yield return new ZipEntry<T1, T2>(
                        index, enumerator1.Current, enumerator2.Current);
                    index++;
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[] numbers = new[] { 1, 2, 3, 4, 5 };
            string[] names = new[] { "Bob", "Alice", "Mark", "John", "Mary" };

            foreach (var entry in numbers.Zip(names))
            {
                Console.Out.WriteLine(entry.Index + ": "
                    + entry.Value1 + "-" + entry.Value2);
            }
        }
    }
}

To make it throw an exception if just one of the sequences run out of values, change the while-loop so:

while (true)
{
    bool hasNext1 = enumerator1.MoveNext();
    bool hasNext2 = enumerator2.MoveNext();
    if (hasNext1 != hasNext2)
        throw new InvalidOperationException("One of the collections ran " +
            "out of values before the other");
    if (!hasNext1)
        break;

    yield return new ZipEntry<T1, T2>(
        index, enumerator1.Current, enumerator2.Current);
    index++;
}
命比纸薄 2024-09-06 09:51:39

简而言之,该语言没有提供干净的方法来做到这一点。枚举被设计为一次对一个枚举进行。您可以很容易地模仿 foreach 为您所做的事情:

using(IEnumerator<A> list1enum = list1.GetEnumerator())
using(IEnumerator<B> list2enum = list2.GetEnumerator())    
while(list1enum.MoveNext() && list2enum.MoveNext()) {
        // list1enum.Current and list2enum.Current point to each current item
    }

如果它们的长度不同该怎么办取决于您。也许在 while 循环完成后找出哪一个仍然有元素并继续处理那个元素,如果它们应该具有相同的长度则抛出异常,等等。

In short, the language offers no clean way to do this. Enumeration was designed to be done over one enumerable at a time. You can mimic what foreach does for you pretty easily:

using(IEnumerator<A> list1enum = list1.GetEnumerator())
using(IEnumerator<B> list2enum = list2.GetEnumerator())    
while(list1enum.MoveNext() && list2enum.MoveNext()) {
        // list1enum.Current and list2enum.Current point to each current item
    }

What to do if they are of different length is up to you. Perhaps find out which one still has elements after the while loop is done and keep working with that one, throw an exception if they should be the same length, etc.

太阳哥哥 2024-09-06 09:51:39

在 .NET 4 中,您可以在 IEnumerable 上使用 .Zip 扩展方法,

IEnumerable<int> list1 = Enumerable.Range(0, 100);
IEnumerable<int> list2 = Enumerable.Range(100, 100);

foreach (var item in list1.Zip(list2, (a, b) => new { a, b }))
{
    // use item.a and item.b
}

但是它不会抛出不相等的长度。不过,你总是可以测试一下。

In .NET 4, you can use the .Zip extension method on IEnumerable<T>

IEnumerable<int> list1 = Enumerable.Range(0, 100);
IEnumerable<int> list2 = Enumerable.Range(100, 100);

foreach (var item in list1.Zip(list2, (a, b) => new { a, b }))
{
    // use item.a and item.b
}

It won't throw on unequal lengths, however. You can always test that, though.

西瓜 2024-09-06 09:51:39

使用 IEnumerable.GetEnumerator,这样您就可以在枚举中移动。请注意,这可能会产生一些非常令人讨厌的行为,您必须小心。如果你想让它工作,就用这个,如果你想要有可维护的代码,请使用两个 foreach。

如果您要在代码中多次使用此功能,您可以创建一个包装类或使用一个库(如 Jon Skeet 建议的那样)以更通用的方式处理此功能。

我建议的代码:

var firstEnum = aIEnumerable.GetEnumerator();
var secondEnum = bIEnumerable.GetEnumerator();

var firstEnumMoreItems = firstEnum.MoveNext();
var secondEnumMoreItems = secondEnum.MoveNext();    

while (firstEnumMoreItems && secondEnumMoreItems)
{
      // Do whatever.  
      firstEnumMoreItems = firstEnum.MoveNext();
      secondEnumMoreItems = secondEnum.MoveNext();   
}

if (firstEnumMoreItems || secondEnumMoreItems)
{
     Throw new Exception("One Enum is bigger");
}

// IEnumerator does not have a Dispose method, but IEnumerator<T> has.
if (firstEnum is IDisposable) { ((IDisposable)firstEnum).Dispose(); }
if (secondEnum is IDisposable) { ((IDisposable)secondEnum).Dispose(); }

Go with IEnumerable.GetEnumerator, so you can move around the enumerable. Note that this might have some really nasty behavior, and you must be careful. If you want to get it working, go with this, if you want to have maintainable code, use two foreach.

You could create a wrapping class or use a library (as Jon Skeet suggests) to handle this functionality in a more generic way if you are going to use it more than once thru your code.

The code for what I suggest:

var firstEnum = aIEnumerable.GetEnumerator();
var secondEnum = bIEnumerable.GetEnumerator();

var firstEnumMoreItems = firstEnum.MoveNext();
var secondEnumMoreItems = secondEnum.MoveNext();    

while (firstEnumMoreItems && secondEnumMoreItems)
{
      // Do whatever.  
      firstEnumMoreItems = firstEnum.MoveNext();
      secondEnumMoreItems = secondEnum.MoveNext();   
}

if (firstEnumMoreItems || secondEnumMoreItems)
{
     Throw new Exception("One Enum is bigger");
}

// IEnumerator does not have a Dispose method, but IEnumerator<T> has.
if (firstEnum is IDisposable) { ((IDisposable)firstEnum).Dispose(); }
if (secondEnum is IDisposable) { ((IDisposable)secondEnum).Dispose(); }
吃不饱 2024-09-06 09:51:39
using(var enum1 = list1.GetEnumerator())
using(var enum2 = list2.GetEnumerator())
{
    while(true)
    {
        bool moveNext1 = enum1.MoveNext();
        bool moveNext2 = enum2.MoveNext();
        if (moveNext1 != moveNext2)
            throw new InvalidOperationException();
        if (!moveNext1)
            break;
        var a = enum1.Current;
        var b = enum2.Current;
        // use a and b
    }
}
using(var enum1 = list1.GetEnumerator())
using(var enum2 = list2.GetEnumerator())
{
    while(true)
    {
        bool moveNext1 = enum1.MoveNext();
        bool moveNext2 = enum2.MoveNext();
        if (moveNext1 != moveNext2)
            throw new InvalidOperationException();
        if (!moveNext1)
            break;
        var a = enum1.Current;
        var b = enum2.Current;
        // use a and b
    }
}
梦在深巷 2024-09-06 09:51:39

使用 Zip 函数,例如

foreach (var entry in list1.Zip(list2, (a,b)=>new {First=a, Second=b}) {
    // use entry.First und entry.Second
}

This 不会引发异常,但是......

Use the Zip function like

foreach (var entry in list1.Zip(list2, (a,b)=>new {First=a, Second=b}) {
    // use entry.First und entry.Second
}

This doesn't throw an exception, though ...

书间行客 2024-09-06 09:51:39

你可以做这样的事情。

IEnumerator enuma = a.GetEnumerator();
IEnumerator enumb = b.GetEnumerator();
while (enuma.MoveNext() && enumb.MoveNext())
{
    string vala = enuma.Current as string;
    string valb = enumb.Current as string;
}

C# 没有 foreach 可以按照你想要的方式执行(据我所知)。

You can do something like this.

IEnumerator enuma = a.GetEnumerator();
IEnumerator enumb = b.GetEnumerator();
while (enuma.MoveNext() && enumb.MoveNext())
{
    string vala = enuma.Current as string;
    string valb = enumb.Current as string;
}

C# has no foreach that can do it how you want (that I am aware of).

阿楠 2024-09-06 09:51:39

由于 C# 7.0 引入了元组,您可以创建一个返回 IEnumerable<(T1, T2)> 的通用 Lockstep 函数:

public static IEnumerable<(T1, T2)> Lockstep<T1, T2>(IEnumerable<T1> t1s, IEnumerable<T2> t2s)
{
    using IEnumerator<T1> enum1 = t1s.GetEnumerator();
    using IEnumerator<T2> enum2 = t2s.GetEnumerator();
    while (enum1.MoveNext() && enum2.MoveNext())
        yield return (enum1.Current, enum2.Current);
}

并像这样使用它:

void LockstepDemo(IEnumerable<A> xs, IEnumerable<B> ys)
{
    foreach (var (x, y) in Lockstep(xs, ys))
        Consume(x, y);
}

如果您需要三个或更多枚举,您可以使用三个或更多参数重载Lockstep

Since C# 7.0 introduced tuples, you can create a generic Lockstep function that returns an IEnumerable<(T1, T2)>:

public static IEnumerable<(T1, T2)> Lockstep<T1, T2>(IEnumerable<T1> t1s, IEnumerable<T2> t2s)
{
    using IEnumerator<T1> enum1 = t1s.GetEnumerator();
    using IEnumerator<T2> enum2 = t2s.GetEnumerator();
    while (enum1.MoveNext() && enum2.MoveNext())
        yield return (enum1.Current, enum2.Current);
}

And use it like this:

void LockstepDemo(IEnumerable<A> xs, IEnumerable<B> ys)
{
    foreach (var (x, y) in Lockstep(xs, ys))
        Consume(x, y);
}

If you need three or more enumerations, you can overload Lockstep with three or more parameters.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文