Make the number of nested for loops a variable - VBA - vba

I have developed a code like this
For i = 1 To 50
For j = i + 1 To 50
For k = j + 1 To 50
..........................
'Here I have someother code
..........................
Next k
Next j
Next i
So this is a nested for loop. And there are 3 for loops in this code.
Now what I want is, I want to make the number of for loops a variable. For an example I have variable call NumberOfForLoops. So if NumberOfForLoops=3 then there should be 3 for loops. If NumberOfForLoops=8 then there should be 8 for loops. I can't find how to do this.
I think it is better to explain what I was trying to accomplish.
I'm trying to find possible combinations
For 3 combinations I wrote this code
Dim WS_Data As Worksheet
Dim WS_Result As Worksheet
Set WS_Data = Worksheets("Data")
Set WS_Result = Worksheets("Result")
Dim i As Long
Dim j As Long
Dim k As Long
Dim EnteringRow As Long
EnteringRow = 1
For i = 1 To 5
For j = i + 1 To 5
For k = j + 1 To 5
WS_Result.Range("A" & EnteringRow).Value = WS_Data.Range("A" & i).Value
WS_Result.Range("B" & EnteringRow).Value = WS_Data.Range("A" & j).Value
WS_Result.Range("C" & EnteringRow).Value = WS_Data.Range("A" & k).Value
EnteringRow = EnteringRow + 1
Next k
Next j
Next i
It gave me below result
Input and Outcome
But now what I need is I want to make the number of items I select from the list dynamic. According to the anwers I understood that I need to use a recursion concept.
So I modified the code according to h2so4's answer.
This is the modified code
Sub test()
Dim WS_Data As Worksheet
Dim WS_Result As Worksheet
Dim WS_Temp As Worksheet
Set WS_Data = Worksheets("Data")
Set WS_Result = Worksheets("Result")
Set WS_Temp = Worksheets("Temp")
ResultRow = 1
NofL = 3
Nestedloop WS_Data, WS_Result, WS_Temp, ResultRow, NofL, 1, 5, 1
End Sub
Sub Nestedloop(WS_Data, WS_Result, WS_Temp, ResultRow, NofL, jmin, jmax, level)
For j = jmin To jmax
WS_Temp.Cells(1, level) = j
'your code when a value of j is set
If level < NofL Then
Nestedloop WS_Data, WS_Result, WS_Temp, ResultRow, NofL, jmin + 1, jmax, level + 1
Else
'your code when the number of loops is reached
For i = 1 To NofL
WS_Result.Cells(ResultRow, 0 + i).Value = WS_Data.Range("A" & WS_Temp.Cells(1, i).Value).Value
Next i
ResultRow = ResultRow + 1
End If
Next j
End Sub
I used a tempory sheet. This is the result I got.
Result got
Difficult to understand what is wrong.

another proposal with recursive sub that simulates nested loops
Sub test()
NofL = 4
Nestedloop NofL, 1, 50, 1
End Sub
Sub Nestedloop(NofL, jmin, jmax, level)
For j = jmin To jmax
Cells(1, level) = j
'your code when a value of j is set
If level < NofL Then
Nestedloop NofL, jmin + 1, jmax, level + 1
Else
'your code when the number of loops is reached
End If
Next j
End Sub

Your question can be read in more than one way. If you are looking for a tool to help in generating boiler-plate code, perhaps something like this:
'In the following code, if vars is missing, successive loop indices are "i", "j", "k", etc
'otherwise, vars is treated as the loop vars and should be passed as a 0-based array with
'depth strings, where depth is how deeply nested the loops are
Function NestedFors(lim As Long, depth As Long, Optional vars As Variant) As String
Dim i As Long, n As Long
Dim codeShell As String
If IsMissing(vars) Then
vars = Split("i j k l m n o p q r s t u v w x y z") 'should be overkill -- if not, you deserve a runtime error!
End If
codeShell = "For " & vars(0) & " = 1 To " & lim & vbCrLf
For i = 1 To depth - 1
codeShell = codeShell & String(i, vbTab)
codeShell = codeShell & "For " & vars(i) & " = " & vars(i - 1) & " + 1 To " & lim & vbCrLf
Next i
codeShell = codeShell & String(depth, vbTab) & "'----- Insert code here ------" & vbCrLf
For i = depth - 1 To 1 Step -1
codeShell = codeShell & String(i, vbTab) & "Next " & vars(i) & vbCrLf
Next i
codeShell = codeShell & "Next " & vars(0) & vbCrLf
NestedFors = codeShell
End Function
Then, for example if you type
?nestedfors(50,4)
in the Immediate Window you get the following (which can be copy-pasted to a code window above:
For i = 1 To 50
For j = i + 1 To 50
For k = j + 1 To 50
For l = k + 1 To 50
'----- Insert code here ------
Next l
Next k
Next j
Next i

edited to produce nested loops
Option Explicit
Sub main()
Dim NumberOfForLoops As Long
NumberOfForLoops = 3
ForLoops NumberOfForLoops, 1, 50
End Sub
Sub ForLoops(nLoops As Long, jMin As Long, jMax As Long, Optional level As Long)
Dim j As Long
If level = 0 Then level = 1
For j = jMin To jMax
If level < nLoops Then
ForLoops nLoops, jMin + 1, jMax, level + 1
Else
'your "someother" code
End If
Next j
End Sub

If you want to run the first nested loop "NumberOfForLoops" times, just change the "To" part to NumberOfForLoops.
For i = 1 To 50
For j = 1 To NumberOfForLoops
For k = j + 1 To 50
..........................
'Here I have someother code
..........................
Next k
Next j
Next i

Related

Word VBA Selection.TypeText and Selection.InsertParagraph inserting text in the wrong order

I am trying to print certain columns from an Excel file with the Selection.TypeText function.
Unfortunately, the following code first prints all the results, and after that all the paragraphs. I want the result:
result (tab) result (tab) result (tab) result (tab) result (paragraph)
The order of the results is correct, but the macro jumps to the end of the data to insert a paragraph, and then "jumps back" to fill in more data.
noInt and noData are two variables that are filled beforehand and are working.
For i = 2 To noInt
For k = 2 To noData
If exWb.Sheets("Table1").Cells(k, 1) = exWb.Sheets("Table2").Cells(i, 1) Then
For j = 5 to 9
Selection.TypeText exWb.Sheets("Table1").Cells(k, j) & vbTab
Next j
Selection.InsertParagraph
End If
Next k
Next i
Instead of using Selection, use a Range. Then you can reliably append text using Range.InsertAfter. The below examples are in Word VBA; I can't put them in your Excel context since I don't have all the details.
MCVE of your code (not working)
Option Explicit
Option Base 0
' A function just to display i, k, and j in a comprehensible manner
Public Function ijk(i As Long, j As Long, k As Long)
ijk = "[I: " & CStr(i) & "; K: " & CStr(k) & "; J: " & CStr(j) & "]"
End Function
Public Sub NotWorking()
Dim noInt As Long
noInt = 3
Dim noData As Long
noData = 4
Dim i As Long, k As Long, j As Long
For i = 2 To noInt
For k = 2 To noData
For j = 5 To 9
Selection.TypeText ijk(i, j, k) & vbTab
Next j
Selection.InsertParagraph
Next k
Next i
End Sub
A working version
Replace Sub NotWorking above with this:
Public Sub Working()
Dim noInt As Long
noInt = 3
Dim noData As Long
noData = 4
Dim i As Long, k As Long, j As Long
' *** Create a Range to refer to wherever the Selection is now
Dim rDest As Range
Set rDest = Selection.Range.Duplicate
For i = 2 To noInt
For k = 2 To noData
For j = 5 To 9
rDest.InsertAfter ijk(i, j, k) & vbTab ' ***
Next j
rDest.InsertAfter Chr(13) ' ***
Next k
Next i
End Sub
Chr(13) is a paragraph marker, so inserting it creates a new paragraph.
Results:

Excel VBA Converting seconds to HH:MM:SS

Trying to convert specific data in a specific column through the whole worksheet.
This is my current code, it does not error out, however it doesn't display the first result correctly (Shows 00:00:00), and only stops at the first record.
Sub CleanEntry()
Dim i As Integer
Dim Seconds As Integer
Dim j As Long
Dim c As Long
j = 2
For i = Sheet1.UsedRange.Rows.Count To 1 Step -1
c = Range("B" & j).Value
c = c / 86400
Range("B" & j).Value = Format(c, "hh:mm:ss")
j = j + 1
Next
End Sub
Though I do not understand why you are using two counters, with one going backwards and the other forward, this can be done with one line:
Sub CleanEntry()
Dim i As Long
Dim j As Long
j = 2
For i = Sheet1.UsedRange.Rows.Count To 1 Step -1
Range("B" & j).Value = Format(TimeSerial(0, 0, Range("B" & j).Value), "hh:mm:ss")
j = j + 1
Next
End Sub

Excel: Split ; separated cell values into columns and then shift in consecutive rows

I'm in the situation described by fig.1 where I have a cell with the reference name and a cell with one or more semicolon separated emails associated to the same reference. I'd like to split the cells contaning more than one email stacking them consecutively and copying the refence name. Is it possible to do this with a VBA Macro in Excel 2007? I know the existence of the "Split in columns" command, but I don't know how to automatically shift the columns in rows and copying the reference name. Thanks in advance.
Here you go:
Sub SplitColumnB()
Dim r As Range
Set r = [B2]
Do While r.Value <> ""
res = Split(r.Value, " ; ")
i = 0
For Each resStr In res
If i > 0 Then r.Offset(1).EntireRow.Insert xlDown
r.Offset(IIf(i > 0, 1, 0)).Value = resStr
r.Offset(IIf(i > 0, 1, 0), -1).Value = Right(resStr, Len(resStr) - InStr(resStr, "#"))
i = i + 1
Next
Set r = r.Offset(IIf(i > 0, i, 1))
Loop
End Sub
Try with the below code. Replace all instances of Sheet1 with the name of your worksheet.
Sub test()
Dim Ref As String
Dim Eid As String
Dim RefR()
Dim EidR()
Rcnt = Sheets("Sheet1").Range("A65000").End(xlUp).Row
K = 0
L = 0
For i = 2 To Rcnt
Ref = Sheets("Sheet1").Range("A" & i).Value
Temp = Split(Sheets("Sheet1").Range("B" & i).Value, ";")
K = K + 1
ReDim Preserve RefR(1 To K)
RefR(K) = Ref
For j = LBound(Temp) To UBound(Temp)
If L <= UBound(Temp) Then
ReDim Preserve EidR(Rcnt, L)
L = UBound(Temp)
End If
EidR(K, j) = Temp(j)
Next j
Next i
RowValue = 2
For i = 1 To UBound(RefR)
For j = 0 To L
Sheets("Sheet1").Range("A" & RowValue).Value = RefR(i)
Sheets("Sheet1").Range("B" & RowValue).Value = Trim(EidR(i, j))
RowValue = RowValue + 1
Next j
Next i
End Sub

implementing bloomberg time delays with a large amount of BDH cell references

I have already looked at a few examples of how to use Application.OnTime,
to check for progress within the cell before updating and wrote up an implementation but I'm not sure why it wont work.
I dont want to paste the whole thing here, because it may be more confusing than just looking at the subs within the workbook.
I was wondering if someone with experience with this type of thing would be willing to look at my code. I can pm the file I'm working on.
Here is the method that loads data into the shell sheet. Ideally the data will all load before the pattern_recogADR sub is run... otherwise there is an error.
Sub build_singleEquity()
'x As Long
Dim x As Long
x = 6
'Dim x As Long
'x = 4
Application.ScreenUpdating = False
Call DefineTixCollection 'creates table of inputs
'check
'Debug.Print TixCollection(4).ORD
'set up data points - from "Input" sheet
'Dim x As Long
'Dim path As String
'path = Sheets("Input").Range("V1").value
'For x = 1 To TixCollection.Count
Sheets("SingleEquityHistoryHedge").Activate
'clear inputs
Range("B2:B8").Clear
Dim Inputs() As Variant
Dim name As String
name = "SingleEquityHistoryHedge"
'insert new inputs
Inputs = Array(TixCollection(x).ADR, TixCollection(x).ORD, TixCollection(x).ratio, _
TixCollection(x).crrncy, TixCollection(x).hedge_index, TixCollection(x).hedge_ord, _
TixCollection(x).hedge_ratio)
Call PrintArray(2, 2, Inputs, name, "yes") ' prints inputs
Dim last_row As Long
last_row = Range("A" & Rows.count).End(xlUp).Row
Range("AN11") = "USD" & TixCollection(x).crrncy
Range("AA11") = "USD" & TixCollection(x).crrncy
' Dim sht_name As String
'Application.Run "RefreshAllStaticData"
BloombergUI.ThisWorkbook.RefreshAll
' sht_name = TixCollection(x).ADR
' Call Sheet_SaveAs(path, sht_name, "SingleEquityHistoryHedge") 'save collection of sheets
'Next x
'Call TriggerCalc
'check this out
Call pattern_recogADR(x + 4, 5, 13)
End Sub
Here is the pattern_recogADR sub.... as you can see I have tried a ton of different thing which are commented out.
Sub pattern_recogADR(pos As Long, pat_days As Long, sht_start As Long)
'
'Application.Wait Now + "00:00:20"
'Dim pat As pattern
'Dim tix As clsTix
Dim newTime As Date
newTime = Now + TimeValue("00:00:30")
Do While Not Now >= newTime
'add back in as parameters
'Dim pos As Long
Dim x As Long
'Dim pat_days As Long
'Dim sht_start As Long
'************************
'pos = 5
'pat_days = 5
'sht_start = 13
Sheets("SingleEquityHistoryHedge").Activate
'Sleep 20000 'sleeps
Dim st As Long
Dim st_num As Long
Dim st_end As Long
Dim count As Long
Dim patrn As Long
count = sht_start
Dim i As Long
Dim j As Long
Dim patPLUSret() As Variant
Dim k As Long
Dim z As Long
k = 2
z = 3
For j = 8 To 12
'**************************************
count = sht_start
st_num = sht_start
st_end = 13
If IsNumeric(Cells(count, j).value) Then
'sets default pattern to beginning cell value
' Debug.Print st_num
If Cells(st_num, j).value < 0 Then
For i = count + 1 To count + 1 + pat_days
If IsNumeric(Cells(i, j).value) Then
If Cells(i, j).value < 0 Then
st_end = i
'Debug.Print st_end
End If
Else
Exit For
End If
Next i
patrn = st_end - st_num
' Debug.Print count
' Debug.Print patrn
ReDim Preserve patPLUSret(k * 2 + 1)
patPLUSret(0) = Range("B2").value 'ADR
patPLUSret(1) = Range("B3").value 'ORD
patPLUSret(k) = patrn
patPLUSret(z) = Application.WorksheetFunction.Average(Range(Cells(st_num, j), Cells(st_end, j)))
' Debug.Print patPLUSret(j)
' Debug.Print patPLUSret(j + 1)
st_num = sht_start 'resets starting point to initial
st_end = sht_start
' For x = 4 To 6
' If Range("L" & x).value = "x" Then
' ReDim Preserve mac_array(x - 4)
' mac_array(x - 4) = Range("N" & x).value
' End If
' Next x
' check this out
'tix.arbPnl = patrn
'save to separate class for patterns
'TixCollection.Add tix, tix.ADR
'******************************
ElseIf Cells(st_num, j).value > 0 Then
For i = count + 1 To count + 1 + pat_days
If IsNumeric(Cells(i, j).value) Then
If Cells(i, j).value > 0 Then
st_end = i
End If
Else
st_end = st_num
Exit For
End If
Next i
patrn = st_end - st_num
ReDim Preserve patPLUSret(k * 2 + 1)
patPLUSret(0) = Range("B2").value 'ADR
patPLUSret(1) = Range("B3").value 'ORD
patPLUSret(k) = patrn
patPLUSret(z) = Application.WorksheetFunction.Average(Range(Cells(st_num, j), Cells(st_end, j)))
' Debug.Print patPLUSret(j)
' Debug.Print patPLUSret(j + 1)
st_num = sht_start 'resets starting point to initial
st_end = sht_start
' Debug.Print patrn
'pat.arbPnl = patrn
'save to separate class for patterns
End If
k = k + 2
z = z + 2
Else
count = count + 1
st_num = count
End If
'
' k = k + 1
'new_array = patPLUSret
Next j
' Debug.Print patPLUSret
Sheets("PatternADR_ORD").Activate
Range(Cells(pos, 1), Cells(pos, 10)) = patPLUSret
Loop
End Sub
If you wait or loop to simulate a wait in your second sub, it won't give the control back to the spreadsheet and your formulae won't update.
Instead of
Call pattern_recogADR(x + 4, 5, 13)
why don't you call:
Application.onTime "'pattern_recogADR ""x + 4"", ""5"", ""13""'"

how to find largest positive or negative value pattern from a given standpoint

I'm currently writing a sub but I'm unclear of how to approach the problem programatically
Essentially I have a sheet with a column of data values starting at "A1"
How do I write a sub that will check through the column to find the largest pattern starting from the first cell and the direction of the pattern?
ie. if A1 is 2, A2 is 5, A3 is -2 ... the sub should return 2 (positive 2 days in a row)
if A1 is -2, A2 -1, A3 is -5, A4 is -2, A5 -1, A6 2 ... the sub should return -5 (negative 5 days in a row)
What I want is to somehow gather this number but in the process also save the last row in the pattern so I can compute averages, std variation etc. to store to a collection
Here is the code to check for patterns.... the j is a column counter... I need to figure out how to make the loop go back up to right before the for loop instead of iterating the j variable and then going back down....
but in any case here is the check pattern sub
<i> Sub pattern_recogADR()
'add back in as parameters
x As Long
pat_days As Long
sht_start As Long
x = 1
pat_days = 5
sht_start = 13
Dim st As Long
Dim st_num As Long
Dim st_end As Long
Dim count As Long
Dim patrn As Long
count = sht_start
Dim i As Long
Set pat = New pattern
For j = 8 To 12
st_num = 0
If IsNumeric(Cells(count, j).value) Then
st_num = count 'sets default pattern to beginning cell value
If Cells(st_num, j).value < 0 Then
For i = count + 1 To count + 1 + pat_days
If IsNumeric(Cells(i, j).value) And Cells(i, j).value < 0 Then
st_end = i
Else
Exit For
End If
Next i
patrn = st_end - st_num
tix.arbPnl = patrn
'**CONFUSION HERE WANT TO SAVE PATTERN TO AN EXISTING COLLECTION STARTING `
'AT THE FIRST ITEM **
ElseIf Cells(st_num, j).value > 0 Then
For i = count + 1 To count + 1 + pat_days
If IsNumeric(Cells(i, j).value) And Cells(i, j).value < 0 Then
st_end = i
Else
Exit For
End If
Next i
patrn = st_end - st_num
TIX.arbPnl = patrn
'save to separate class for patterns
Else
count = count + 1
End If
Next j
End Sub
Here is where I previously define the object. Basically I want to get this pattern and then add it to the respective attribute (? I dont know coding vocab) in this collection which is already define so the pattern matches with the respective item in the collection.
Option Explicit
Public TixCollection As New Collection
Sub DefineTixCollection()
Application.ScreenUpdating = False
Sheets("Input").Activate
Set TixCollection = Nothing
Dim tix As clsTix
Dim i As Long
Dim last_row As Long
last_row = Range("A" & Rows.count).End(xlUp).Row
'Add tix properties
For i = 3 To last_row
Set tix = New clsTix
'only adds active tickers to collection
If Range("A" & i).value = "x" Then
'Random data
tix.ORD = Range("B" & i).value
tix.ADR = Range("C" & i).value
tix.ratio = Range("D" & i).value
tix.crrncy = Range("E" & i).value
tix.hedge_index = Range("F" & i).value
tix.hedge_ord = Range("G" & i).value
tix.hedge_ratio = Range("H" & i).value
' ADR is the id key
TixCollection.Add tix, tix.ADR
End If
Next i
' Error Check
' For i = 1 To 5
' 'retrieve by collection index
' Debug.Print TixCollection(i).ORD
' Debug.Print TixCollection(5).ADR
' Debug.Print TixCollection(5).ratio
' Debug.Print TixCollection(i).crrncy
' Debug.Print TixCollection(i).hedge_index
' Debug.Print TixCollection(i).hedge_ord
' Debug.Print TixCollection(i).hedge_ratio
' Next i
End Sub
Any help would be much appreciated getting frustrated now... ugh
Sub Button1_Click()
Dim patrn() As Long
ReDim patrn(0 To 4)
Dim count As Long
Dim posCount As Integer
Dim negCount As Integer
Dim sign As Boolean
posCount = 0
negCount = 0
count = 0
Dim i As Long
Dim j As Integer
Dim lastRow As Long
For j = 8 To 12
lastRow = ActiveSheet.Cells(ActiveSheet.Rows.count, j).End(xlUp).Row
For i = 1 To lastRow
If IsNumeric(Cells(i, j).Value) Then
If count = 0 Then
If Cells(i, j).Value > 0 Then
sign = True
posCount = posCount + 1
ElseIf Cells(i, j).Value < 0 Then
sign = False
negCount = negCount + 1
End If
ElseIf count > 0 And count <= 4 Then
If Cells(i, j).Value > 0 And sign = True Then
sign = True
posCount = posCount + 1
ElseIf Cells(i, j).Value > 0 And sign = False Then
sign = True
posCount = 1
ElseIf Cells(i, j).Value < 0 And sign = True Then
sign = False
negCount = 1
ElseIf Cells(i, j).Value < 0 And sign = False Then
sign = False
negCount = negCount + 1
End If
ElseIf count = 5 Then
Exit For
End If
count = count + 1
End If
Next i
If posCount > negCount Then
patrn(j - 8) = posCount
Else
patrn(j - 8) = negCount - (negCount * 2)
End If
negCount = 0
posCount = 0
count = 0
Next j
'Do your other calculations here.
For i = LBound(patrn) To UBound(patrn)
Debug.Print patrn(i)
Next
End Sub