Можно ли перебирать два объекта IEnumerable одновременно? - PullRequest
4 голосов
/ 11 июня 2009

Если у меня есть List (Of x) и List (Of y), можно ли выполнять итерацию по обоим одновременно?

Что-то вроде

for each _x as X, _y as Y in List(of x), List(of y) 
    if _x.item = _y.item then
        'do something
    end if
next

Эти списки могут иметь разные размеры.

Я использую .Net2.0, что, как я подозреваю, является моим недостатком, поскольку у меня есть ощущение, что LINQ решит что-то вроде простого, присоединив к спискам общий идентификатор.

Ответы [ 7 ]

5 голосов
/ 11 июня 2009

Вы можете использовать старомодный цикл for. Что-то вроде

For ii As Integer = 0 To Math.Min(list1.Count, list2.Count)
    If list1(ii) = list2(ii) Then
    End If
Next
5 голосов
/ 11 июня 2009

IIRC, .Net 4.0 будет иметь метод расширения .Zip() для IEnumerable, который уже делает это.

Между тем, не так сложно построить свой собственный. Удивительно, но в то время как несколько других ответов были очень близки, у всех них есть по крайней мере одна проблема. Надеюсь, они будут исправлены. Между тем, это должно делать то, что вы хотите, в VB.Net, со строго типизированными перечислителями, используя правильное сравнение для неизвестных типов и правильно распоряжаться перечислителями:

Using xe As IEnumerator(Of X) = List1.GetEnumerator(), _
      ye As IEnumerator(Of Y) = List2.GetEnumerator()

    While xe.MoveNext() AndAlso ye.MoveNext() 
        If xe.Current.Equals(ye.Current) Then
            ''// do something
        End If
    End While
End Using

А теперь давайте поместим это в функцию, которой вы можете передать своих собственных делегатов:

Public Shared Sub ZipAction(Of X, Y)(ByVal source1 As IEnumerable(Of X), ByVal source2 As IEnumerable(Of Y), _
                              ByVal compare As Func(Of X, Y, Boolean), Byval OnEquals As Action(Of X, Y))  

    Using xe As IEnumerator(Of X) = source1.GetEnumerator(), _
          ye As IEnumerator(Of Y) = source2.GetEnumerator()

        While xe.MoveNext() AndAlso ye.MoveNext() 
            If compare(xe.Current, ye.Current) Then
                OnEquals(xe.Current, ye.Current)
            End If
        End While
    End Using
End Sub

И, наконец, поскольку эти типы делегатов недоступны до .Net 3.5, вы можете легко объявить их в .Net 2.0 следующим образом:

Public Delegate Sub Action(Of T1, T2) ( _
    arg1 As T1, _
    arg2 As T2 _
)

Public Delegate Function Func(Of T1, T2, TResult) ( _
    arg1 As T1, _
    arg2 As T2, _
) As TResult

Чтобы использовать этот код, вы должны сделать что-то вроде этого:

Public Class X
    Public Item As String
    ''//...
End Class

Public Class Y
    Public Item As String
    ''//...
End Class

Public Class Test

    Private Function CompareXtoY(ByVal arg1 As X, ByVal arg2 As Y) As Boolean
        Return arg1.Item = arg2.Item
    End Function

    Private Sub OnList1ItemMatchesList2Item(ByVal arg1 As X, ByVal arg2 As Y)
        ''// Do something...
    End Sub

    Private list1 As List(Of X) = GetXList()
    Private list2 As List(Of Y) = GetYList()

    Public Sub TestZip()
        ZipAction(list1, list2, AddressOf CompareXtoY, AddressOf OnList1ItemMatchesList2Item)
    End Sub

End Class

Если бы это был C #, я бы имел функцию, являющуюся блоком итератора, и "возвращал бы результат" каждой соответствующей паре, вместо того, чтобы просить вас передать делегат Action.

2 голосов
/ 11 июня 2009

Вы должны были бы получить доступ к счетчикам вручную. В C #:

using (IEnumerator<X> xe = List1.GetEnumerator())
using (IEnumerator<Y> ye = List2.GetEnumerator()) {
    while (xe.MoveNext() && ye.MoveNext()) {
         if (xe.Current == ye.Current) {
             // do something
         }
    }
}
2 голосов
/ 11 июня 2009

Нет, не в vb.net с конструкциями цикла vb.

Однако вы можете сделать это сами с счетчиками:

    Sub MyOwnIENumeration()
    Dim a As List(Of String), b As List(Of String)
    Dim EnmA As System.Collections.Generic.IEnumerator(Of String) = a.GetEnumerator
    Dim EnmB As System.Collections.Generic.IEnumerator(Of String) = b.GetEnumerator

    If EnmA.MoveNext() And EnmB.MoveNext() Then
        Do
            If EnmA.Current = EnmB.Current Then
                Debug.Print("list matched on " & EnmA.Current)
                If Not EnmA.MoveNext() Then Exit Do
                If Not EnmB.MoveNext() Then Exit Do
            ElseIf EnmA.Current < EnmB.Current Then
                If Not EnmA.MoveNext() Then Exit Do
            Else
                If Not EnmB.MoveNext() Then Exit Do
            End If
        Loop
    End If

    EnmA.Dispose() : EnmB.Dispose()
End Sub
0 голосов
/ 11 июня 2009

Вы можете сделать это, используя функцию GetEnumerator () каждого IEnumerable.

Вы можете сделать что-то вроде:

 // Assume List1 and List2 are IEnumerable variables
 Dim list1 As IEnumerator = List1.GetEnumerator()
 Dim list2 As IEnumerator = List2.GetEnumerator();
 While list1.MoveNext() And list2.MoveNext()
      If list1.Current = list2.Current Then
          // Do Something
      End If
 End While

Вот еще немного информации от MSDN

0 голосов
/ 11 июня 2009

Я не очень опытный в VB.NET, но вот метод расширения в C #, который вы можете создать, чтобы сделать это. Я надеюсь, что вы можете портировать его!

public static class IEnumExtensions
{
    public static IEnumerable<KeyValuePair<T, U>> EnumTwoCollections<T, U>(this IEnumerable<T> list1, IEnumerable<U> list2)
    {
        var enumerator1 = list1.GetEnumerator();
        var enumerator2 = list2.GetEnumerator();

        bool moveNext1 = enumerator1.MoveNext();
        bool moveNext2 = enumerator2.MoveNext();

        while (moveNext1 || moveNext2)
        {

            T tItem = moveNext1 ? enumerator1.Current : default(T);
            U uItem = moveNext2 ? enumerator2.Current : default(U);

            yield return new KeyValuePair<T, U>(tItem, uItem);

            moveNext1 = enumerator1.MoveNext();
            moveNext2 = enumerator2.MoveNext();
        }
    }
}

Использование:

    List<int> intList = new List<int>();
    List<string> stringList = new List<string>();
    for (int i = 1; i <= 10; i++)
    {
        intList.Add(i);
        stringList.Add((i * i).ToString());
    }

    foreach (var items in intList.EnumTwoCollections(stringList))
    {
        Console.WriteLine(items.Key + " , " + items.Value);
    }

Теперь вы заметили, что вы не используете .NET 3.5, поэтому вы, вероятно, тоже не можете использовать методы расширения. Просто поместите это в некоторый вспомогательный класс, и вы можете назвать его так:

foreach(KeyValuePair<int,string> kvp in Helper.EnumTwoCollections(intList,stringList))
{
...
}
0 голосов
/ 11 июня 2009

Вы имеете в виду без выполнения вложенного цикла?

foreach _x as X in List(of x)
     foreach _y as Y in List(of y)
          if _x.item = _y.item then
               'do something
          end if
 next

Не программист на VB, поэтому мой синтаксис может быть неправильным, но вы поняли.

...