Extreme VBA challenge for Reconciler workbook

Nothnless

Board Regular
Joined
Apr 28, 2016
Messages
142
Hello, below I have two modules for a workbook I created that reconciles dollar amounts. The idea behind it is let’s say you have an account with 20 open invoices. The client sends you a payment which if applied correctly will close 10 invoices. But the client doesn’t bother to send you any remittance advice. If you take the payment amount and enter it into the workbook along with all 20 invoices the workbook will highlight which amounts equal the payment. So in this case 10 invoices (or there amounts) will be highlighted. My problem with the workbook is it’ll only except about 20 invoices, around 25 it’ll take a full 4 minutes to compute. Anymore and it will just crash. If anyone here is willing, can you please take a look at my code to see if it can be optimized in anyway. I’d like for to take 100 invoices, or at least not crash. Maybe if it’s taking more than 2 minutes it can stop and say “memory exhausted, try again with a better computer.” I really hope someone can help, thanks!

Code:
'Code:1
'----------------------------------------------------------------------

Option Explicit

Function RealEqual(A, B, Optional Epsilon As Double = 0.00000001)
    RealEqual = Abs(A - B) <= Epsilon
    End Function
Function ExtendRslt(CurrRslt, NewVal, Separator)
    If CurrRslt = "" Then ExtendRslt = NewVal _
    Else ExtendRslt = CurrRslt & Separator & NewVal
    End Function
Sub recursiveMatch(ByVal MaxSoln As Integer, ByVal TargetVal, InArr(), _
        ByVal HaveRandomNegatives As Boolean, _
        ByVal CurrIdx As Integer, _
        ByVal CurrTotal, ByVal Epsilon As Double, _
        ByRef Rslt(), ByVal CurrRslt As String, ByVal Separator As String)
    Dim i As Integer
    For i = CurrIdx To UBound(InArr)
        If RealEqual(CurrTotal + InArr(i), TargetVal, Epsilon) Then
            Rslt(UBound(Rslt)) = (CurrTotal + InArr(i)) _
                & Separator & Format(Now(), "hh:mm:ss") _
                & Separator & ExtendRslt(CurrRslt, i, Separator)
            If MaxSoln = 0 Then
                'If UBound(Rslt) Mod 100 = 0 Then Debug.Print '"Rslt(" & UBound(Rslt) & ")=" & Rslt(UBound(Rslt))
            Else
                If UBound(Rslt) >= MaxSoln Then Exit Sub
                End If
            ReDim Preserve Rslt(UBound(Rslt) + 1)
        ElseIf IIf(HaveRandomNegatives, False, CurrTotal + InArr(i) > TargetVal + Epsilon) Then
        ElseIf CurrIdx < UBound(InArr) Then
            recursiveMatch MaxSoln, TargetVal, InArr(), HaveRandomNegatives, _
                i + 1, _
                CurrTotal + InArr(i), Epsilon, Rslt(), _
                ExtendRslt(CurrRslt, i, Separator), _
                Separator
            If MaxSoln <> 0 Then If UBound(Rslt) >= MaxSoln Then Exit Sub
        Else
            'we've run out of possible elements and we _
             still don't have a match
            End If
        Next i
    End Sub
Function ArrLen(Arr()) As Long
    'On Error Resume Next
    ArrLen = UBound(Arr) - LBound(Arr) + 1
    End Function
Function checkRandomNegatives(Arr) As Boolean
    Dim i As Long
    i = LBound(Arr)
    Do While Arr(i) < 0 And i < UBound(Arr): i = i + 1: Loop
    If i = UBound(Arr) Then Exit Function
    Do While Arr(i) >= 0 And i < UBound(Arr): i = i + 1: Loop
    checkRandomNegatives = Arr(i) < 0
    End Function
Sub FindInvoice()
    'The selection should be a single contiguous range in a single column. _
     The first cell indicates the number of solutions wanted.  Specify zero for all. _
     The 2nd cell is the target value. _
     The rest of the cells are the values available for matching. _
     The output is in the column adjacent to the one containing the input data.
    
    Dim lr As Long
    
    Call sortData
    lr = Sheet1.Range("C2").End(xlDown).Row
    
    If lr < 2 Then
        MsgBox "No Existing Invoice Found.Task cancelled", vbCritical
        Exit Sub
        
    Else
    
        Sheet1.Range("C2:C" & lr).Select
        
    End If
    
    If Len(Sheet1.Range("C3")) < 1 Then
    
        MsgBox "Please provide Total Amout to be matched.", vbExclamation
        Sheet1.Range("C3").Select
        Exit Sub
    End If
    
    If Not TypeOf Selection Is Range Then GoTo ErrXIT
    If Selection.Areas.Count > 1 Or Selection.Columns.Count > 1 Then GoTo ErrXIT
    If Selection.Rows.Count < 3 Then GoTo ErrXIT
    
    Debug.Print Now
    Dim TargetVal, Rslt(), InArr(), StartTime As Date, MaxSoln As Integer, _
        HaveRandomNegatives As Boolean
    StartTime = Now()
    MaxSoln = Selection.Cells(1).Value
    TargetVal = Selection.Cells(2).Value
    InArr = Application.WorksheetFunction.Transpose( _
        Selection.Offset(2, 0).Resize(Selection.Rows.Count - 2).Value)
    HaveRandomNegatives = checkRandomNegatives(InArr)
    If Not HaveRandomNegatives Then
    ElseIf MsgBox("At least 1 negative number is present between positive numbers" _
                & vbNewLine _
            & "It may take a lot longer to search for matches." & vbNewLine _
            & "OK to continue else Cancel", vbOKCancel) = vbCancel Then
        Exit Sub
        End If
    ReDim Rslt(0)
    recursiveMatch MaxSoln, TargetVal, InArr, HaveRandomNegatives, _
        LBound(InArr), 0, 0.00000001, _
        Rslt, "", ", "
    Rslt(UBound(Rslt)) = Format(Now, "hh:mm:ss")
    ReDim Preserve Rslt(UBound(Rslt) + 1)
    Rslt(UBound(Rslt)) = Format(StartTime, "hh:mm:ss")
    
    Sheets("Reconciler").Range("F3") = UBound(Rslt) - 1
    
    Debug.Print Now
    Sheet1.Range("H1").Resize(ArrLen(Rslt), 1).Value = _
        Application.WorksheetFunction.Transpose(Rslt)
                
      Sheet1.Range("C4:C100000").Interior.Color = 15853019
      If Sheet1.Range("F3") > 0 Then Sheet1.Range("F4") = 0
      Sheet1.Range("B3").Select
      Call showMatch
        
    Exit Sub
ErrXIT:
    MsgBox "Please select cells in a single column before using this macro" & vbNewLine _
        & "The selection should be a single contiguous range in a single column." & vbNewLine _
        & "The first cell indicates the number of solutions wanted.  Specify zero for all." & vbNewLine _
        & "The 2nd cell is the target value." & vbNewLine _
        & "The rest of the cells are the values available for matching." & vbNewLine _
        & "The output is in the column adjacent to the one containing the input data."
    End Sub
Sub reset()

Sheet1.Range("C4:C100000").Interior.Color = 15853019
Sheet1.Range("F10") = 0.99999

End Sub


Sub showMatch()


Dim Sc, i As Long, Mc As Long, r As Long

Sheet1.Range("C4:C100000").Interior.Color = 15853019






r = 1

If Sheet1.Range("F4") < Sheet1.Range("F3") Then

    Sheet1.Range("F4") = Sheet1.Range("F4") + 1
    
Findmatch:
    Mc = CLng(Sheet1.Range("F4"))

        Sc = Split(Sheet1.Range("H" & Mc))
    
        For i = 2 To UBound(Sc)
        
          If IsNumeric(Sc(i)) = True Then Sheet1.Range("C" & Sc(i) + 3).Interior.Color = vbGreen
        
        Next
    
        r = r + 1
    
Else

    If Sheet1.Range("F3") > 0 Then Sheet1.Range("F4") = 1
    GoTo Findmatch

End If


End Sub


'--------------------------------------------------------------------------------
 
Sub sortData()

Dim lr As Long, prow As Long, i As Long
    
    lr = Sheet1.Range("C2").End(xlDown).Row
    
    Range("C4:C" & lr).Select
    ActiveWorkbook.Worksheets("Reconciler").Sort.SortFields.Clear

    
  ActiveWorkbook.Worksheets("Reconciler").Sort.SortFields.Add Key:=Range( _
        "C4"), SortOn:=xlSortOnValues, Order:=1, DataOption:= _
        xlSortNormal

  With ActiveWorkbook.Worksheets("Reconciler").Sort
        .SetRange Range("C4:C" & lr)
        .Header = xlNo
        .MatchCase = False
        .Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
    End With
    
    For i = 4 To lr
    
        If Worksheets("Reconciler").Cells(i, 3) >= 0 Then
            prow = i
            Exit For
        End If
                
    Next
    
    
    Range("C" & prow & ":C" & lr).Select
    ActiveWorkbook.Worksheets("Reconciler").Sort.SortFields.Clear

    
  ActiveWorkbook.Worksheets("Reconciler").Sort.SortFields.Add Key:=Range( _
        "C" & prow), SortOn:=xlSortOnValues, Order:=2, DataOption:= _
        xlSortNormal

  With ActiveWorkbook.Worksheets("Reconciler").Sort
        .SetRange Range("C" & prow & ":C" & lr)
        .Header = xlNo
        .MatchCase = False
        .Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
  End With
    
 prow = prow - 1
 If prow > 4 Then
    
  Range("C4:C" & prow).Select
    ActiveWorkbook.Worksheets("Reconciler").Sort.SortFields.Clear

    
  ActiveWorkbook.Worksheets("Reconciler").Sort.SortFields.Add Key:=Range( _
        "C4"), SortOn:=xlSortOnValues, Order:=2, DataOption:= _
        xlSortNormal

  With ActiveWorkbook.Worksheets("Reconciler").Sort
        .SetRange Range("C4:C" & prow)
        .Header = xlNo
        .MatchCase = False
        .Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
    End With
     
End If

End Sub


Code:
Sub FindCloseInvoice()
    'The selection should be a single contiguous range in a single column. _
     The first cell indicates the number of solutions wanted.  Specify zero for all. _
     The 2nd cell is the target value. _
     The rest of the cells are the values available for matching. _
     The output is in the column adjacent to the one containing the input data.
    Debug.Print Now
    Dim lr As Long
    
    
    lr = Sheet1.Range("C2").End(xlDown).Row
    
    If lr < 2 Then
        MsgBox "No Existing Invoice Found.Task cancelled", vbCritical
        Exit Sub
        
    Else
    
        Sheet1.Range("C2:C" & lr).Select
        
    End If
    
    If Len(Sheet1.Range("C3")) < 1 Then
    
        MsgBox "Please provide Total Amout to be matched.", vbExclamation
        Sheet1.Range("C3").Select
        Exit Sub
    End If
    
    If Not TypeOf Selection Is Range Then GoTo ErrXIT
    If Selection.Areas.Count > 1 Or Selection.Columns.Count > 1 Then GoTo ErrXIT
    If Selection.Rows.Count < 3 Then GoTo ErrXIT
    
    Dim TargetVal, Rslt(), InArr(), StartTime As Date, MaxSoln As Integer, _
        HaveRandomNegatives As Boolean
    StartTime = Now()
    MaxSoln = Selection.Cells(1).Value
    TargetVal = Selection.Cells(2).Value
    InArr = Application.WorksheetFunction.Transpose( _
        Selection.Offset(2, 0).Resize(Selection.Rows.Count - 2).Value)
    HaveRandomNegatives = checkRandomNegatives(InArr)
    If Not HaveRandomNegatives Then
    ElseIf MsgBox("At least 1 negative number is present between positive numbers" _
                & vbNewLine _
            & "It may take a lot longer to search for matches." & vbNewLine _
            & "OK to continue else Cancel", vbOKCancel) = vbCancel Then
        Exit Sub
        End If
    ReDim Rslt(0)
    recursiveMatch MaxSoln, TargetVal, InArr, HaveRandomNegatives, _
        LBound(InArr), 0, CDbl(Sheet1.Range("F10")), _
        Rslt, "", ", "
    Rslt(UBound(Rslt)) = Format(Now, "hh:mm:ss")
    ReDim Preserve Rslt(UBound(Rslt) + 1)
    Rslt(UBound(Rslt)) = Format(StartTime, "hh:mm:ss")
    
    Sheets("Reconciler").Range("F3") = UBound(Rslt) - 1
    
    Debug.Print Now
    'MsgBox ArrLen(Rslt)
    Sheet1.Columns(8).ClearContents
    Sheet1.Range("H1:H" & CLng((UBound(Rslt)))) = Rslt
    'Sheet1.Range(.Offset(0, 0), .Offset(rNum - 1, cNum - 1)) = Results
    Sheet1.Range("H1") = Application.WorksheetFunction.Transpose(Rslt)
    'Sheet1.Range("H1").Resize(ArrLen(Rslt), 1).Value = _
        Application.WorksheetFunction.Transpose(Rslt)
      Sheet1.Range("H1").Resize(ArrLen(Rslt), 1).Value = Application.WorksheetFunction.Transpose(Rslt)
      'Sheet1.Range("C4:C100000").Interior.Color = 15853019
      If Sheet1.Range("F3") > 0 Then Sheet1.Range("F4") = 0
      Sheet1.Range("B3").Select
      Call showMatch
      'Debug.Print Now
      
      MsgBox "Done"
    Exit Sub
ErrXIT:
    MsgBox "Please select cells in a single column before using this macro" & vbNewLine _
        & "The selection should be a single contiguous range in a single column." & vbNewLine _
        & "The first cell indicates the number of solutions wanted.  Specify zero for all." & vbNewLine _
        & "The 2nd cell is the target value." & vbNewLine _
        & "The rest of the cells are the values available for matching." & vbNewLine _
        & "The output is in the column adjacent to the one containing the input data."
    End Sub
 

Excel Facts

Test for Multiple Conditions in IF?
Use AND(test, test, test, test) or OR(test, test, test, ...) as the logical_test argument of IF.
No macro should ever take that long. Here's my first advice (since I don't see any code optimization basics in here):

Code:
Application.ScreenUpdating = False
'CODE HERE

Application.ScreenUpdating = True </pre>

This will pause any updates so it doesn't have to represent it visually until the end.

Code:
[COLOR=#003366][FONT=Courier New]Application.Calculation = xlCalculationManual
'CODE GOES HERE
[/FONT][/COLOR][COLOR=#003366][FONT=Courier New]Application.Calculation = xlCalculationAutomatic[/FONT][/COLOR][COLOR=#003366][FONT=Courier New]
[/FONT][/COLOR]

Again, I don't know if there any formulas activating during your code, but this can help speed it up as well.
 
Upvote 0
Some combinatorial routines can easily take minutes, hours, days. This would easily qualify as such a routine. Coming up with an efficient algorithm to do this is not an easy task. I don't have time to analyze your code for validity, let alone efficiency. And NRS pointed out a couple basic things you can try, but I doubt they'd make a significant difference.

However, several years back, this forum had a contest which was designed to do exactly what you're asking. The winning code was posted here:

Past Challenges | MREXCEL

It might be worth your while to take a look at it. Good luck!
 
Upvote 0

Forum statistics

Threads
1,215,734
Messages
6,126,542
Members
449,316
Latest member
sravya

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top