什么是同时枚举两个列表的模式,没有索引?

时间:2022-06-12 14:01:44

I'm looking for a creative pattern to enumerate two IEnumerable<>'s synchronized.

我正在寻找一个创意模式来枚举两个IEnumerable <>的同步。

If I was making something up and adding to the C# syntax I might write:

如果我正在制作一些内容并添加到C#语法中,我可能会写:

foreach(var firstItem, var secondItem in this.ListOne, this.ListTwo)
{
   if (firstItem.Prop == secondItem.Prop)
      WorkSomeMagic(secondItem);

   DoSomethingElse(firstItem);
}

Now, obviously that doesn't exist. What patterns have people used to accomplish something similar when dealing with enumerations that aren't accessible by index? Keep in mind, what is inside my pseudo-foreach would be more complex; I simplified for the example.

现在,显然这不存在。在处理索引无法访问的枚举时,人们用什么模式来完成类似的事情?请记住,我的伪foreach中的内容会更复杂;我简化了这个例子。

5 个解决方案

#1


You're looking for Zip, which is new in .NET 4 or you can use the implementation here:

您正在寻找Zip,这是.NET 4中的新功能,或者您可以在此处使用实现:

Is there a zip-like method in .Net?

.Net中有类似拉链的方法吗?

#2


I usually do the following:

我通常会做以下事情:

using (IEnumerator<int> e1 = this.ListOne.GetEnumerator(), 
                        e2 = this.ListTwo.GetEnumerator()) {
  while (e1.MoveNext() && e2.MoveNext()) { 
    ...
  }
}

Or write an extension method:

或者写一个扩展方法:

public static void EnumerateWith<T>(this IEnumerable<T> left,
  IEnumerable<T> right, Action<T,T> del) {
  using (IEnumerator<T> l =  left.GetEnumerator(), 
                        r = right.GetEnumerator()) { 
    while (l.MoveNext() && r.MoveNext()) {
      del(l.Current,r.Current);
    }
  }
}

ListOne.EnumerateWith(ListTwo, (left, right) => {
  ...
});

#3


As with any general C# question, this will probably have 10 good answers posted before VS2008 even loads. Instead of that rat race, I'll come up with an offbeat "anti-pattern" you should never use. In fact, anyone writing mission critical code, please stop reading now.

与任何一般的C#问题一样,在VS2008甚至加载之前,这可能会有10个好的答案。而不是那场比赛,我会想出一个你不应该使用的另类“反模式”。事实上,任何人都在编写关键任务代码,请立即停止阅读。

using System;
using System.Collections.Generic;
using System.Linq;

class EnumTwoLists
{
    static void Main(string[] args)
    {
        var left = new List<int>();
        var right = new List<DateTime>();

        var demo = new LinqAbuse<int, DateTime>(left, right);
        demo.Populate(40, s => s * s, d => new DateTime(2009, d / 31 + 1, d % 31 + 1));
        demo.Enumerate( (s, d) => Console.WriteLine(String.Format("Executing arbitrary code with {0} and {1}", s, d)) );
    }
}

class LinqAbuse<T1, T2>
{
    public LinqAbuse(List<T1> l, List<T2> r)
    {
        left = l;
        right = r;
    }

    List<T1> left;
    List<T2> right;

    public void Populate(int size, Func<int, T1> leftGenerator, Func<int, T2> rightGenerator)
    {
        new int[size].Aggregate(0, (index, empty) => PopulateWrapper(left, right, leftGenerator, rightGenerator, index));
    }

    int PopulateWrapper(List<T1> left, List<T2> right, Func<int, T1> leftGenerator, Func<int, T2> rightGenerator, int index)
    {
        left.Add(leftGenerator(index));
        right.Add(rightGenerator(index));
        return ++index;
    }

    public void Enumerate(Action<T1, T2> loopBody)
    {
        left.Join(right, l => "", r => "",
                  (l, r) => ActionWrapper(l, r, loopBody),
                  new CartesianComparer<object>(right.Count))
            .ToList();
    }        

    object ActionWrapper(T1 x, T2 y, Action<T1, T2> action)
    {
        action(x, y);
        return null;
    }
}

class CartesianComparer<T> : IEqualityComparer<T>
{        
    public CartesianComparer(int _size)
    {
        size = _size;
        equalsCounter = (size * (size - 1) >> 1) + size; // Combinations(size, 2) + (size - trueCounter)
    }

    private int size;
    private int equalsCounter;
    private int trueCounter = 0;

    public bool Equals(T x, T y)
    {
        if (0 < --equalsCounter)
            return false;

        equalsCounter = size - ++trueCounter;
        return true;
    }

    public int GetHashCode(T obj)
    {            
        return 0;
    }       
}

Aww, isn't she cute? (alternate caption: Mommy, why is Anders crying?)

哇,她不可爱吗? (另一个标题:妈妈,安德斯为什么哭?)

#4


Ignoring checks for nulls and whatnot:

忽略检查空值和诸如此类的东西:

IEnumerable<T1> first;
IEnumerable<T2> second;
using (IEnumerator<T1> e1 = first.GetEnumerator()) {
    using (IEnumerator<T2> e2 = second.GetEnumerator()) {
        while (e1.MoveNext() && e2.MoveNext()) {
            // do something eith e1.Current and e2.Current
        } 
    }

}

#5


I know this question is old but for anyone coming to this question now you can build on Jason's answer and JaredPar's answer with C#7's ValueTuple, which will give you a syntax similar to the original question. You may need to install the nuget package System.ValueTuple.

我知道这个问题已经过时了,但是对于任何想出这个问题的人来说,现在你可以利用Jason的答案和JaredPar对C#7的ValueTuple的回答,它会给你一个类似于原始问题的语法。您可能需要安装nuget包System.ValueTuple。

If you declare an extension method something like this:

如果你声明一个像这样的扩展方法:

internal static class EnumerableExtensions
{
    internal static IEnumerable<(T1, T2)> EnumerateWith<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second)
    {
        using (var firstEnumerator = first.GetEnumerator())
        using (var secondEnumerator = second.GetEnumerator())
        {
            while(firstEnumerator.MoveNext() && secondEnumerator.MoveNext())
            {
                yield return (firstEnumerator.Current, secondEnumerator.Current);
            }
        }
    }
}

Then you can use it like this:

然后你可以像这样使用它:

List<Foo> foos = new List<Foo>()
{
    new Foo(),
    new Foo(),
    new Foo()
};

List<Bar> bars = new List<Bar>()
{
    new Bar(),
    new Bar(),
    new Bar()
};

foreach((Foo foo, Bar bar) in foos.EnumerateWith(bars))
{
    Console.WriteLine(foo.ID);
    Console.WriteLine(bar.ID);
}

#1


You're looking for Zip, which is new in .NET 4 or you can use the implementation here:

您正在寻找Zip,这是.NET 4中的新功能,或者您可以在此处使用实现:

Is there a zip-like method in .Net?

.Net中有类似拉链的方法吗?

#2


I usually do the following:

我通常会做以下事情:

using (IEnumerator<int> e1 = this.ListOne.GetEnumerator(), 
                        e2 = this.ListTwo.GetEnumerator()) {
  while (e1.MoveNext() && e2.MoveNext()) { 
    ...
  }
}

Or write an extension method:

或者写一个扩展方法:

public static void EnumerateWith<T>(this IEnumerable<T> left,
  IEnumerable<T> right, Action<T,T> del) {
  using (IEnumerator<T> l =  left.GetEnumerator(), 
                        r = right.GetEnumerator()) { 
    while (l.MoveNext() && r.MoveNext()) {
      del(l.Current,r.Current);
    }
  }
}

ListOne.EnumerateWith(ListTwo, (left, right) => {
  ...
});

#3


As with any general C# question, this will probably have 10 good answers posted before VS2008 even loads. Instead of that rat race, I'll come up with an offbeat "anti-pattern" you should never use. In fact, anyone writing mission critical code, please stop reading now.

与任何一般的C#问题一样,在VS2008甚至加载之前,这可能会有10个好的答案。而不是那场比赛,我会想出一个你不应该使用的另类“反模式”。事实上,任何人都在编写关键任务代码,请立即停止阅读。

using System;
using System.Collections.Generic;
using System.Linq;

class EnumTwoLists
{
    static void Main(string[] args)
    {
        var left = new List<int>();
        var right = new List<DateTime>();

        var demo = new LinqAbuse<int, DateTime>(left, right);
        demo.Populate(40, s => s * s, d => new DateTime(2009, d / 31 + 1, d % 31 + 1));
        demo.Enumerate( (s, d) => Console.WriteLine(String.Format("Executing arbitrary code with {0} and {1}", s, d)) );
    }
}

class LinqAbuse<T1, T2>
{
    public LinqAbuse(List<T1> l, List<T2> r)
    {
        left = l;
        right = r;
    }

    List<T1> left;
    List<T2> right;

    public void Populate(int size, Func<int, T1> leftGenerator, Func<int, T2> rightGenerator)
    {
        new int[size].Aggregate(0, (index, empty) => PopulateWrapper(left, right, leftGenerator, rightGenerator, index));
    }

    int PopulateWrapper(List<T1> left, List<T2> right, Func<int, T1> leftGenerator, Func<int, T2> rightGenerator, int index)
    {
        left.Add(leftGenerator(index));
        right.Add(rightGenerator(index));
        return ++index;
    }

    public void Enumerate(Action<T1, T2> loopBody)
    {
        left.Join(right, l => "", r => "",
                  (l, r) => ActionWrapper(l, r, loopBody),
                  new CartesianComparer<object>(right.Count))
            .ToList();
    }        

    object ActionWrapper(T1 x, T2 y, Action<T1, T2> action)
    {
        action(x, y);
        return null;
    }
}

class CartesianComparer<T> : IEqualityComparer<T>
{        
    public CartesianComparer(int _size)
    {
        size = _size;
        equalsCounter = (size * (size - 1) >> 1) + size; // Combinations(size, 2) + (size - trueCounter)
    }

    private int size;
    private int equalsCounter;
    private int trueCounter = 0;

    public bool Equals(T x, T y)
    {
        if (0 < --equalsCounter)
            return false;

        equalsCounter = size - ++trueCounter;
        return true;
    }

    public int GetHashCode(T obj)
    {            
        return 0;
    }       
}

Aww, isn't she cute? (alternate caption: Mommy, why is Anders crying?)

哇,她不可爱吗? (另一个标题:妈妈,安德斯为什么哭?)

#4


Ignoring checks for nulls and whatnot:

忽略检查空值和诸如此类的东西:

IEnumerable<T1> first;
IEnumerable<T2> second;
using (IEnumerator<T1> e1 = first.GetEnumerator()) {
    using (IEnumerator<T2> e2 = second.GetEnumerator()) {
        while (e1.MoveNext() && e2.MoveNext()) {
            // do something eith e1.Current and e2.Current
        } 
    }

}

#5


I know this question is old but for anyone coming to this question now you can build on Jason's answer and JaredPar's answer with C#7's ValueTuple, which will give you a syntax similar to the original question. You may need to install the nuget package System.ValueTuple.

我知道这个问题已经过时了,但是对于任何想出这个问题的人来说,现在你可以利用Jason的答案和JaredPar对C#7的ValueTuple的回答,它会给你一个类似于原始问题的语法。您可能需要安装nuget包System.ValueTuple。

If you declare an extension method something like this:

如果你声明一个像这样的扩展方法:

internal static class EnumerableExtensions
{
    internal static IEnumerable<(T1, T2)> EnumerateWith<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second)
    {
        using (var firstEnumerator = first.GetEnumerator())
        using (var secondEnumerator = second.GetEnumerator())
        {
            while(firstEnumerator.MoveNext() && secondEnumerator.MoveNext())
            {
                yield return (firstEnumerator.Current, secondEnumerator.Current);
            }
        }
    }
}

Then you can use it like this:

然后你可以像这样使用它:

List<Foo> foos = new List<Foo>()
{
    new Foo(),
    new Foo(),
    new Foo()
};

List<Bar> bars = new List<Bar>()
{
    new Bar(),
    new Bar(),
    new Bar()
};

foreach((Foo foo, Bar bar) in foos.EnumerateWith(bars))
{
    Console.WriteLine(foo.ID);
    Console.WriteLine(bar.ID);
}