Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Solution for limitations of For Each loops?

When cycling through a list of items in VBA, I use this code pattern:

For Each Item In ListOfItems
    ' Do something with Item
Next Item

However, in practical applications, I often need access to an iteration counter, the previous or next item, or I have to perform special steps for the first or last item in the list. For instance:

For Each Item In ListOfItems
    If Item.IsFirst Then
        ...
    End If
    Debug.Print Item.Index, Item.SomeProperty
Next Item

However, .Index and .IsFirst are not properties of the class of Item. Therefore, this does not work. So, my question to the community is: How can I get as close as possible to my desired code pattern?

I know that I could implement a counter with some additional code. Checking for the first item could as well be performed with some additional code. But that's not what I want. I have seen many bugs where the developer forgot to increment the counter. I want to have this out of the hands of the developer.

Likewise, I also know that I could use other types of loops, such as Do While or For i=1 To .... That's also not what I want. I want to use THE straight-forward code pattern, which is made for cycling through a list, which in my opinion is For Each ....

My own attempts to solve this led to this pattern:

For Each IterationItem In toIterationItems(ListOfItems)
    If IterationItem.IsFirst Then
        ...
    End If
    Debug.Print IterationItem.Index, IterationItem.Item.SomeProperty
Next IterationItem

This works. The function toIterationItems takes a Collection or Dictionary and for each element, it puts an IterationItem into an output Collection, which is handed back to the For Each. So, For Each cycles through a Collection of IterationItems instead of the original ListOfItems.

While this works (I can post the code here, if desired), I am unsure, if there aren't any better approaches. My approach has some disadvantages...

like image 592
Segner Avatar asked Nov 07 '25 23:11

Segner


2 Answers

Your approach has the merit of abstracting away the need for counter management, which indeed can be a source of bugs. However, as you mention, it comes with some downsides. It introduces another level of indirection and complexity, which might confuse less experienced developers or make the code harder to maintain. Additionally, it involves creating a whole new collection just to iterate over an existing one, which is inefficient especially for large collections.

The primary alternatives in VBA would be:

  1. As you mentioned, managing the counter manually. Yes, it can lead to bugs if not done carefully, but if the loop is simple and well-documented, the risks are minimal. For complex loops, an abstraction like your IterationItem might indeed be useful.

  2. Using a different language feature or construct. You mentioned you want to use For Each, but it's worth noting that the more traditional For loop inherently supports accessing the index, the previous item, and the next item, and it also makes it easy to perform specific actions on the first or last item.

Here's an example of how you could use a For loop to do all these things:

Dim ListCount As Long
ListCount = ListOfItems.Count

For i = 1 To ListCount
    Dim CurrentItem As Variant
    CurrentItem = ListOfItems(i)
    
    If i = 1 Then
        ' First item
    ElseIf i = ListCount Then
        ' Last item
    Else
        ' Middle items
        Dim PreviousItem As Variant
        Dim NextItem As Variant
        PreviousItem = ListOfItems(i - 1)
        NextItem = ListOfItems(i + 1)
    End If
    
    Debug.Print i, CurrentItem.SomeProperty
Next i

If you prefer the readability of For Each and don't mind the added complexity and inefficiency, then your IterationItem approach might be the best for you. But if you want something simpler and more efficient, a For loop is the most natural fit for the requirements you described. It's just a matter of trade-offs between readability, simplicity, and efficiency.

like image 113
Shri Avatar answered Nov 09 '25 19:11

Shri


As a response to @VBasic2008, I post my intermediate solution, here. This is not meant as THE solution, but only as an add-on to my question:

Class IterationItem:

Option Explicit

Private Type TThis
    dicParent As Dictionary
    lngIndex As Long
End Type
Private This As TThis

Public Sub Init(ByVal adicParent As Dictionary, ByVal alngIndex As Long)
    Set This.dicParent = adicParent
    This.lngIndex = alngIndex
End Sub

Public Property Get Item() As Variant
    VariantLet Item, This.dicParent.Items(This.lngIndex - 1)
End Property

Public Property Get Key() As String
    Key = This.dicParent.Keys(This.lngIndex - 1)
End Property

Public Property Get Index() As Long
    Index = This.lngIndex
End Property

Public Property Get IsFirstItem() As Boolean
    IsFirstItem = CBool(This.lngIndex = 1)
End Property
Public Property Get IsLastItem() As Boolean
    IsLastItem = CBool(This.lngIndex = This.dicParent.Count)
End Property

Public Property Get NextItem() As Variant
    VariantLet NextItem, This.dicParent.Items(This.lngIndex)
End Property

Public Property Get PrevItem() As Variant
    VariantLet PrevItem, This.dicParent.Items(This.lngIndex - 2)
End Property

Private Sub VariantLet(ByRef Destination As Variant, ByVal Source As Variant)
    If IsObject(Source) Then
        Set Destination = Source
    Else
        Destination = Source
    End If
End Sub

Function toIterationItems:

Public Function toIterationItems(ByVal InputData As Variant) As Collection
    Dim varValue As Variant, IterItem As IterationItem, Counter&
    Set toIterationItems = New Collection
    For Each varValue In InputData
        Set IterItem = New IterationItem
        Counter = Counter + 1
        IterItem.Init InputData, Counter
        toIterationItems.Add IterItem
    Next varValue
End Function

I don't think that is the optimal solution. I post this here only to demonstrate my direction of thinking. One disadvantage is that my solution deviates from the goal, which I described above. It also creates additional computational load.

like image 27
Segner Avatar answered Nov 09 '25 17:11

Segner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!