views:

631

answers:

7

If I have a List(Of x) and a List(Of y) is it possible to iterate over both at the same time?

Something like

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

These lists may be of differing sizes.

I am using .Net2.0 which I suspect is my downfall here as I have a feeling LINQ would solve something like easily by joining the lists on there common id.

+1  A: 

No, not in vb.net with the vb loop constructs.

You can however do it yourself with the enumerators:

    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
RBarryYoung
A: 

You mean without doing a nested loop?

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

Not a VB programmer so my syntax might be wrong but you get the idea.

Matthew Jones
It's not the syntax: it's that it will re-iterate every item in Y for every item in X. It's O(n^2) rather than O(n).
Joel Coehoorn
A: 

I'm not very proficient in VB.NET, but here's an extension method in C# you can create to do this. I hope you can port it!

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

Usage:

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

Noticed now that you're not using .NET 3.5 so you probably can't use extension methods either. Just put this in some helper class and you can call it like this:

foreach(KeyValuePair<int,string> kvp in Helper.EnumTwoCollections(intList,stringList))
{
...
}
BFree
A: 

You can do it using the GetEnumerator() function of each IEnumerable.

You could do something like:

 // 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

Here is some more info from MSDN

Chris Thompson
Generally, you shouldn't use CompareTo to test for equality - more information is at http://blogs.msdn.com/ericlippert/archive/2009/03/30/every-problem-looks-like-a-nail.aspx
thecoop
Getting closer, but you still need to remember to dispose of the enumerables.
Joel Coehoorn
Also, I see no reason for do/while construct. IT's possible to have empty lists, you know.
Joel Coehoorn
And, it's not type safe :(
Joel Coehoorn
I just checked and IEnumerator<T> implements IDisposable but IEnumerator does not. So do you dispose of it just by setting it null since there's not Dispose() method?
Chris Thompson
I didn't realize IEnumerator was not an IDisposable. In that case you're okay there, but the other two complaints still stand (and fixing the last one makes this an issue again).
Joel Coehoorn
The logic is still sound. He now has a basis for implementing it in his own circumstances.
Chris Thompson
+2  A: 

You could use an old-fashioned for loop. Something like

For ii As Integer = 0 To Math.Min(list1.Count, list2.Count)
    If list1(ii) = list2(ii) Then
    End If
Next
Scott Weinstein
i like this answer, its clever.
Stan R.
Unless I'm missing something, this does not work for IEnumerable because IEnumerable does not define () or .Count
dss539
But list does, and the OP specified lists.
Joel Coehoorn
+1  A: 

You would have to access the enumerators manually. In 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
         }
    }
}
thecoop
+4  A: 

IIRC, .Net 4.0 will have a .Zip() extension method for IEnumerable that already does this.

In the meantime, it's not that hard to build your own. Surprisingly, while several other answers were very close they all have a least one problem. Hopefully they will be corrected. In the meantime, this should do what you want it to, in VB.Net, with strongly-typed enumerators, using a correct comparison for the unknown types, and correctly dispose of the enumerators:

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

And now let's put this into a function that you can pass your own delegates to:

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

And finally, since those delegate types aren't available until .Net 3.5 you can easily declare them in .Net 2.0 like this:

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

To use this code, you'd do something like this:

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

If this were C#, I would have the function be an iterator block and "yield return" each matching pair rather than asking you to pass in an Action delegate.

Joel Coehoorn
He did mention that the lists may be of differing sizes. I believe in your example it would just stop as soon as one of them reaches it's end. So Ha! my way is better :-P
BFree
If the lists are different sizes, the items left over in the longer list will not match anything in the shorter list and so nothing would happen anyway.
Joel Coehoorn
Assuming inner-join is probably safer than assuming outer-join.
dss539
Eh: there's something to be said for outer join. In C# 3.0, BFree's code makes a lot of sense because you can just put a .Where() on the end of his very generic function (or even just call .Intersect() in the first place). In VB.Net for .Net 2.0 we can't build iterator blocks or extensions and so I think the more-specific 'inner join' approach makes more sense here.
Joel Coehoorn