VBA – Avoid nested loops with recursion (Part 2)

About two years after the “Nested loops with recursion” today I was looking for a way to avoid nested loops with recursion in google and found my article 🙂 . Lots has changed in my coding style since then as far as I do not use “Call”, “Integer”, variables with names like “c” or “n”, I do not put “_” in the names of the Subs/Functions. Still, using the article from 2015 I have managed to achieve what I wanted.

So, what did I want to do? I wanted to simulate a brute force solution of this NP-complete problem:

Picture: https://xkcd.com/287/


At first I have thought about a solution with 6 nested for loops and I have managed to achieve it in a way pretty easily:

Option Explicit

Sub TestMe()

    Dim myArr           As Variant
    Dim myLoop          As Variant
    Dim targetValue     As Long
    Dim currentSum      As Long

    myArr = Array(215, 275, 335, 355, 420, 580)
    targetValue = 1505

    Dim cnt0&, cnt1&, cnt2&, cnt3&, cnt4&, cnt5&, cnt6&
    Dim cnt As Long


    For cnt0 = 0 To 5
        For cnt1 = 0 To 5
            For cnt2 = 0 To 5
                For cnt3 = 0 To 5
                    For cnt4 = 0 To 5
                        For cnt5 = 0 To 5
                            currentSum = 0

                            Dim printableArray As Variant
                            printableArray = Array(cnt0, cnt1, cnt2, cnt3, cnt4, cnt5)

                            For cnt = LBound(myArr) To UBound(myArr)
                                IncrementSum printableArray(cnt), myArr(cnt), currentSum
                            Next cnt

                            If currentSum = targetValue Then
                                PrintValuesOfArray printableArray, myArr
                            End If
    Next: Next: Next: Next: Next: Next

End Sub

Public Sub PrintValuesOfArray(myArr As Variant, initialArr As Variant)

    Dim cnt             As Long
    Dim printVal        As String

    For cnt = LBound(myArr) To UBound(myArr)
        If myArr(cnt) Then
            printVal = printVal & myArr(cnt) & " * " & initialArr(cnt) & vbCrLf
        End If
    Next cnt

    Debug.Print printVal

End Sub

Public Sub IncrementSum(ByVal multiplicator As Long, _
    ByVal arrVal As Long, ByRef currentSum As Long)

    currentSum = currentSum + arrVal * multiplicator

End Sub

Then, I have remembered that I actually think of myself as a high level VBA developer and 6 nested loops is a bit “ugly”. Thus, I have googled “nested loops recursion vba” and the vitoshacademy.com was one of the top results 🙂 Ironic, I thought! 🙂 Anyhow, although the code I have written some years ago was a bit “bad”, I have managed to use it and to build this:

Option Explicit

Sub Main()

    Dim posArr                  As Variant
    Dim iniArr                  As Variant
    Dim tryArr                  As Variant
    Dim cnt                     As Long
    Dim targetVal               As Long: targetVal = 1505

    iniArr = Array(215, 275, 335, 355, 420, 580)
    ReDim posArr(UBound(iniArr))
    ReDim tryArr(UBound(iniArr))

    For cnt = LBound(posArr) To UBound(posArr)
        posArr(cnt) = cnt
    Next cnt
    EmbeddedLoops 0, posArr, tryArr, iniArr, targetVal

End Sub

Function EmbeddedLoops(index As Long, posArr As Variant, tryArr As Variant, _
                                      iniArr As Variant, targetVal As Long)

    Dim myUnit              As Variant
    Dim cnt                 As Long

    If index >= UBound(posArr) + 1 Then
        If CheckSum(tryArr, iniArr, targetVal) Then
            For cnt = LBound(tryArr) To UBound(tryArr)
                If tryArr(cnt) Then Debug.Print tryArr(cnt) & " x " & iniArr(cnt)
            Next cnt
        End If
    Else
        For Each myUnit In posArr
            tryArr(index) = myUnit
            EmbeddedLoops index + 1, posArr, tryArr, iniArr, targetVal
        Next myUnit
    End If

End Function

Public Function CheckSum(posArr, iniArr, targetVal) As Boolean

    Dim cnt         As Long
    Dim compareVal  As Long

    For cnt = LBound(posArr) To UBound(posArr)
        compareVal = posArr(cnt) * iniArr(cnt) + compareVal
    Next cnt
    CheckSum = CBool(compareVal = targetVal)

End Function

If you are wondering for the choice, this is what the waiter should bring:

1 * Mixed Fruit (2.15)
2 * Hot Wings (3.55)
1 * Sampler Plate (5.80)

The solution is not quite full, as far as I have assumed that the positions cannot be more than the squared number of the listed items. Thus, in our case a solution with more than 6×6 units is not considered. Thus, in case that there was a dessert that costs 5 cents, the obvious solution of 301 such desserts will not appear.

Cheers!


Edit:

Or you can order 7 * Mixed Fruit, if you use the good old Sover. Credit to IvenBach for the solution:

Data>Analysis>Solver>Model=GRG Nonlinear