Not quite sure how to write the formula mathematically - excel

So I am a student currently studying VBA. I'm doing ok in the class but still kind of iffy on what how to program. For the question I'm on I have to take numbers in a column and multiply them unless they are less than or equal to zero. Here is an example done by hand of what the result should look like (which are not the same numbers as the actual problem, these are much simpler).
Here is what I have written so far. I'm kinda treating the column as a 1 x 10 array.
Function multpos(C As Variant)
Dim i As Integer
Dim MD As Long
For i = 1 To 9
MD = C(1, i) * C(1, i + 1)
Next i
If C(i, 1) > 0 Then C(i, 1) = C(i, 1) Else C(i, 1) = 1
If C(i + 1, 1) > 0 Then C(i + 1, 1) = C(i + 1, 1) Else C(i + 1, 1) = 1
multpos = MD
End Function
While MD satisfies the equation, it only works for the first two and then doesn't. Intuitively I want to do something like this
MD = C(1, i) * C(1, i)
Next i
Etc but this is also not mathematically correct. So if I had
MD = C(1, i)
how can I get it to multiply by the next value from here? Feel free to look at my other code and correct me as well since that could just as easily be wrong. Thank you for help in advance.

Something like this should work for you. I tried to comment the code for clarity:
Public Function PRODUCTIF(ByVal vValues As Variant, ByVal sCriteria As String) As Double
Dim vVal As Variant
Dim dResult As Double
'Iterate through vValues and evaluate against the criteria for numeric values only
For Each vVal In vValues
If IsNumeric(vVal) Then
If Evaluate(vVal & sCriteria) = True Then
'Value is numeric and passed the criteria, multiply it with our other values
'Note that until a valid value is found, dResult will be 0, so simply set it equal to the first value to avoid a 0 result
If dResult = 0 Then dResult = vVal Else dResult = dResult * vVal
End If
End If
Next vVal
'Output result
PRODUCTIF = dResult
End Function
And you would call the function like this: =PRODUCTIF(A1:A10,">0")

you could exploit AutoFiler() method
Function multpos(C As Range, criteria As String)
Dim MD As Long
Dim cell As Range
With C.Columns(1)
If IsEmpty(.Cells(1, 1)) Then .Cells(1, 1) = "|header|"
.AutoFilter Field:=1, Criteria1:=criteria
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then
MD = 1
For Each cell In C.SpecialCells(xlCellTypeVisible).SpecialCells(xlCellTypeConstants, xlNumbers)
MD = MD * cell.Value
Next cell
End If
If .Cells(1, 1) = "|header|" Then .Cells(1, 1).ClearContents
.Parent.AutoFilterMode = False
End With
multpos = MD
End Function
to be exploited in your main sub like:
MsgBox multpos(Range("A1:A10"), ">0")

Related

Reach a number with a given array as choosing only big numbers among it [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
In Excel or Excel Macro, I am trying to figure out an alghorithm/formula to reach a number with using highest ones as possible but there are some limitations. Let me explain with an example;
Let's say I have 5 numbers in 5 cells (line by line) to use (2,5,10,20,50) and I need to reach 98.
In this example, I should only use 50 + 20 + 20 + 5 + 2 + 2. Even if I can't reach exact number (like reaching 98), it should be the results with minimum exceeds (like 99). It can't be lower than the target number.
I will copy these rows (as fetching their numbers in their first column) and paste to another sheet from the list. Please think these numbers to select rows, so I could find the appropriate rows as finding the right algorithm / formula for it.
How about this:
Put your data for example (2,5,10,20,50) into A1:A5 at the top of sheet1.
Put the below code into a vba code module, and run Algorithm.
I wrote the result into column C, but modify as needed.
Sub Algorithm()
Dim Tgt As Double, currVal As Double
Dim iRow As Integer, oRow As Integer 'input and output row
Dim ary, sortedAry 'input arrays
Dim aResult(1 To 100, 1 To 1) 'assuming result is less than 100 rows long
Tgt = 98 'Target number
ary = Sheet1.Range("A1").CurrentRegion 'put data into an array
sortedAry = BubbleSort(ary, False) 'sort in descending order
iRow = 1
oRow = 1
currVal = 0
Do Until iRow > UBound(sortedAry)
If currVal + sortedAry(iRow, 1) > Tgt Then
iRow = iRow + 1 'goto next smallest number
Else
aResult(oRow, 1) = sortedAry(iRow, 1)
currVal = currVal + sortedAry(iRow, 1)
oRow = oRow + 1
End If
Loop
'one more so it goes over Tgt
aResult(oRow, 1) = sortedAry(UBound(sortedAry), 1)
'put results back into spreadsheet
Sheet1.Range("C1").Resize(100, 1) = aResult
End Sub
Function BubbleSort(myArray As Variant, Optional Ascending = True)
'suitable to sort a small list that is in a 1 column array
Dim i As Long, j As Long
Dim Temp As Variant
If Ascending = True Then
For i = LBound(myArray, 1) To UBound(myArray, 1) - 1
For j = i + 1 To UBound(myArray)
If myArray(i, 1) > myArray(j, 1) Then
Temp = myArray(j, 1)
myArray(j, 1) = myArray(i, 1)
myArray(i, 1) = Temp
End If
Next j
Next i
Else
For i = LBound(myArray, 1) To UBound(myArray, 1) - 1
For j = i + 1 To UBound(myArray)
If myArray(i, 1) < myArray(j, 1) Then
Temp = myArray(j, 1)
myArray(j, 1) = myArray(i, 1)
myArray(i, 1) = Temp
End If
Next j
Next i
End If
BubbleSort = myArray
End Function

Using an offset and range inside a loop

I know this will be a stupid simple answer, but it's killing me right now...
For Each cell In rng2
If cell.Offset(0, -13) And cell.Offset(0, -12).Value <> "" Then
cell.Value = 1
Else
cell.Value = 0
End If
Next cell
Where the .offset and .offset is, how can i just say
If cell.offset(0,12:13)
I know that's not it... This one is simple however, getting this piece right will save me so much headache throughout the vba code all over the workbook.
Thanks for helping this rookie in advance!
Maybe something like this:
For Each cell In rng2
If Application.CountA(cell.Offset(0, -13).Resize(1, 2)) = 2 Then
cell.Value = 1
Else
cell.Value = 0
End If
Next cell
Alternative using variant array
You can combine logical assumptions by multiplication (True * True corresponding to -1 * -1 = 1 in case of both reference cells greater"") and try the following:
'a) assign both offsets to variant 2-dim array
Dim v: v = rng2.Offset(0, -13).Resize(, 2)
'b) get values
Dim i As Long
For i = 1 To UBound(v) ' check each row
v(i, 1) = (v(i, 1) > 0) * (v(i, 2) > 0)
Next i
'c) write values
rng2.Resize(, 1) = v

How can I effectively use If statements with multiple conditions in VBA, comparing user input to a range?

I am attempting to create an Auto-grading test of sorts in Excel.
I have 5 values in Sheet1 that are input by a user in cells E5:E9. These should then be compared against a range of 5 more cells in Sheet2 (also cells E5:E9).
As the user might not always list these entries in the same order that I have in my Sheet2 range, I decided that I should loop through the range for each cell's input.
The next step would be to be able to ignore the value in the range once a match has been found but I need to get this part working correctly. Currently, the values absolutely match. However, I am not getting the correct output.
Sub Q1()
Dim i As Integer
For i = 5 To 9
If (Sheet1.Cells(5, 5) = Sheet2.Cells(i, 5)) And (Sheet1.Cells(6, 5) = Sheet2.Cells(i, 5)) And (Sheet1.Cells(7, 5) = Sheet2.Cells(i, 5)) And (Sheet1.Cells(8, 5) = Sheet2.Cells(i, 5)) And (Sheet1.Cells(9, 5) = Sheet2.Cells(i, 5)) Then
Sheet1.Cells(5, 6) = 1
Exit For
Else
Sheet1.Cells(5, 6) = 0
End If
Next
End Sub
I would expect the output of 1 to Sheet1 cell E6 but I am currently getting 0. Thanks!
Little Complex :)
Sub Q1()
Dim i As Integer
Dim j As Integer
Dim chck(5 To 9) As Boolean
For i = 5 To 9
For j = 5 To 9
If Sheet1.Cells(i, 5) = Sheet2.Cells(j, 5) Then
chck(i) = True
Exit For
Else: chck(i) = False
End If
Next
Next
j = 0
For i = LBound(chck) To UBound(chck)
If chck(i) = True Then j = j + 1
Next
If j = 5 Then
Sheet1.Cells(5, 6) = 1
Else: Sheet1.Cells(5, 6) = 0
End If
End Sub
Does this really need to be VBA? A formula can perform this calculation. Use this in 'Sheet1' cell F5:
=--(SUMPRODUCT(COUNTIF(Sheet2!E5:E9,E5:E9))>0)
If at least one of the values in 'Sheet1'!E5:E9 (the user entered values) exists in your 'Sheet2'!E5:E9 list, the formula will return a 1 else 0 which is the desired result based on your description.

Extracting Multiple Dates from a single cell

I have a single cell that is including all historical updates, each update displays a date/time stamp and then the user's name before their notes. I need to extract all the date/time/name stamps to total their occurrences. +EDIT+ I need to get the name and date portion from each stamp so that i am able to chart the information in a pivot table. Output of something like; "3/3/2016 Rachel Boyers; 3/2/2016 Rachel Boyers; 3/2/2016 James Dorty"
EX:
"3/3/2016 9:28:36 AM Rachel Boyers: EEHAW! Terri replied!!! Hello Rachel,
I cannot find a match using the 4232A or the 12319 part number. 3/2/2016 7:39:06 AM Rachel Boyers: Sent EM to Terri - Eng per EM reply. 3/2/2016 7:35:06 AM James Dorty: 2/29/16 sent another EM to Kim. Received Auto response as follows: Thank you for your mail. Kim 12/7/2015 12:26:25 PM Frank De La Torre: Again VM - pushing FU out until after the holidays.
Edited based on added information
Edit (5/16/2016): I made some changes to the code, as you'll find below. One change, based on the new information, allows you to use the JoinArrayWithSemiColons function as either a standard worksheet function, or as function to be used in a module. So, what does this mean? It means that (assuming your cell to parse is A1), in cell B1 you can write a function like =JoinArrayWithSemiColons(A1) just like you'd write a normal worksheet function. However, if you'd still like to perform the action over a range of cells using VBA, you can run a procedure like TestFunction() as found in the code posted below. Also note, the ExtractDateTimeUsers function doesn't necessarily ever need to be called directly by the user because it's now being used exclusively as a helper function for the JoinArray... function.
Let me know if this helps to clear things up a bit.
Old Post
You can accomplish this using some Regular Expressions. See the code below for an example. In my case, I have a function to return a multidimensional array of results. In my test procedure, I call this function, then assign the results to an EMPTY matrix of cells (in your test case, you will have to determine where to put it). You do NOT have to assign the result to a group of cells, but rather you can do whatever you want with the array.
Private Function ExtractDateTimeUsers(nInput As String) As Variant()
Dim oReg As Object
Dim aOutput() As Variant
Dim nMatchCount As Integer
Dim i As Integer
Dim vMatches As Object
Set oReg = CreateObject("VBScript.RegExp")
With oReg
.MultiLine = False
.Global = True
.Pattern = "([0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}) ([0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2} [AP]M) (.*?):"
End With
If oReg.Test(nInput) Then
Set vMatches = oReg.Execute(nInput)
nMatchCount = vMatches.Count
ReDim aOutput(0 To nMatchCount - 1, 0 To 2)
For i = 0 To nMatchCount - 1
aOutput(i, 0) = vMatches(i).Submatches(0)
aOutput(i, 1) = vMatches(i).Submatches(1)
aOutput(i, 2) = vMatches(i).Submatches(2)
Next i
Else
ReDim aOutput(0 To 0, 0 To 0)
aOutput(0, 0) = "No Matches"
End If
ExtractDateTimeUsers = aOutput
End Function
Function JoinArrayWithSemiColons(sInput As String) As String
Dim vArr As Variant
vArr = ExtractDateTimeUsers(sInput)
If vArr(0, 0) = "No Matches" Then
JoinArrayWithSemiColons = "No Matches"
Exit Function
End If
'Loop through array to build the output string
For i = LBound(vArr, 1) To UBound(vArr, 1)
sOutput = sOutput & "; " & vArr(i, 0) & " " & vArr(i, 2)
Next i
JoinArrayWithSemiColons = Mid(sOutput, 3)
End Function
Sub TestFunction()
'Assume the string we are parsing is in Column A
'(I defined a fixed range, but you can make it dynamic as you need)
Dim rngToJoin As Range
Dim rIterator As Range
Set rngToJoin = Range("A10:A11")
For Each rIterator In rngToJoin
rIterator.Offset(, 1).Value = JoinArrayWithSemiColons(rIterator.Value)
Next rIterator
End Sub
As simple (non-regex) function you can use something like this:
Public Function getCounts(str As String) As Variant
Dim output() As Variant, holder As Variant, i As Long
ReDim output(0, 0)
holder = Split(str, " ")
For i = 0 To UBound(holder) - 2
If IsDate(holder(i) & " " & holder(i + 1) & " " & holder(i + 2)) Then
If UBound(output) Then
ReDim Preserve output(1 To 3, 1 To UBound(output, 2) + 1)
Else
ReDim output(1 To 3, 1 To 1)
End If
output(1, UBound(output, 2)) = holder(i)
output(2, UBound(output, 2)) = holder(i + 1) & " " & holder(i + 2)
i = i + 3
While Right(holder(i), 1) <> ":" And i < UBound(holder)
output(3, UBound(output, 2)) = output(3, UBound(output, 2)) & " " & holder(i)
i = i + 1
Wend
output(3, UBound(output, 2)) = Trim(output(3, UBound(output, 2))) & " " & Left(holder(i), Len(holder(i)) - 1)
End If
Next
If Application.Caller.Rows.Count > UBound(output, 2) Then
i = UBound(output, 2)
ReDim Preserve output(1 To 3, 1 To Application.Caller.Rows.Count)
For i = i + 1 To UBound(output, 2)
output(1, i) = ""
output(2, i) = ""
output(3, i) = ""
Next
End If
getCounts = Application.Transpose(output)
End Function
Just put it in a module to use it as UDF. (Outputs a 3-column-table)
If you have any questions, just ask :)
Just another way to do it. Maybe a little slower, but short and easy to read...
Public Function DateCount(str As String) As Variant
Dim pos As Integer, endpos As Integer, namepos As Integer
Dim Text As String, Output() As String, counter As Integer
pos = InStr(pos + 1, str, "/")
Do While pos > 0
endpos = InStr(pos + 1, str, "M ")
Text = Mid(str, pos - 1, endpos - pos + 2)
If IsDate(Text) Then
counter = counter + 1
ReDim Preserve Output(1 To 2, 1 To counter)
namepos = InStr(endpos, str, ":")
Output(1, counter) = Text
Output(2, counter) = Mid(str, endpos + 2, namepos - endpos - 2)
pos = namepos
End If
pos = InStr(pos + 1, str, "/")
Loop
' Only Count
getCounts = counter
' complete List
getCounts = Output
End Function

VBA-Excel and large data sets causes program to crash

First time poster and new to programming in general. I have a project in which i have to build a financial model to mine for data in excel. I have succeeded in building said model on VBA. I have ran tests on 3,000 line dataset and it was successful. I will briefly explain what it does.
I track a given stock on a given day on multiple exchanges. I download the data (roughly 935,000 lines) The first step is copy all the data for a given exchange (roughly 290,000) onto a new sheet (this takes roughly 8min), then I create a new column to log the bid ask spread (12secs), the next step is what Im having trouble with, I basically rank each line of data twice, one column for Bid size and one column for Ask size. I created a function which uses excel Percentile function and ranks based on where the given bid and ask size lands. As of right now, I have been running the Macro for the last 35min and has yet to execute. I cant attempt the other macros since each macro depends on the previous one.
So my basic issue is that since my data set is large, my model keeps crashing.The code seems to be fine when working with the test data, and it doesn't throw any errors when I run the program, but with the larger data set it just crashes. Does anyone have any suggestions? Is this normal with such large amounts of data?
Thanks in advance.
Sham
Here is the sub and function thats giving me the trouble, the sub takes in the required inputs to run the function and then pops into the assigned cell. The code is suppose to repeat the process for three separate sheets. For now, Id like it to work on one sheet, hence used the comments to not include the loop
Sub Bucketting()
Dim firstRow As Long
Dim lastRow As Long
Dim counter As Long
Dim bidRange As Range
Dim offerRange As Range
Dim bidScroll As Range
Dim offerScroll As Range
Dim Ex As String
Dim i As Integer
'For i = 1 To 1 Step 1 'Sheet Selection Process
' If i = 1 Then
' Ex = "Z"
' ElseIf i = 2 Then
' Ex = "P"
' Else
' Ex = "T"
' End If
Sheets("Z").Select 'Sheet selected
With ActiveSheet
firstRow = .UsedRange.Cells(1).Row + 1
lastRow = .UsedRange.Rows.Count
Set bidRange = .Range("F2:F" & lastRow)
Set offerRange = .Range("G2:G" & lastRow)
For counter = lastRow To firstRow Step -1
Set bidScroll = .Range("F" & counter)
Set offerScroll = .Range("G" & counter)
With .Cells(counter, "J")
.Value = DECILE_RANK(bidRange, bidScroll)
End With
With .Cells(counter, "K")
.Value = DECILE_RANK(offerRange, offerScroll)
End With
Next counter
End With
Range("J1").Select
ActiveCell = "Bid Rank"
ActiveCell.Offset(0, 1) = "Offer Rank"
'Next i
End Sub
Function DECILE_RANK(DataRange, RefCell)
'Credit: BJRaid
'DECILE_RANK(The Range of data)
'Declares the function that can be called in the spreadsheet cell - enter '=DECILE_RANK(A5:A50,A5)
'Using the percentile worksheet function calculate where the 10th, 20th etc percentile of the reference range are
DEC1 = Application.WorksheetFunction.Percentile(DataRange, 0.1)
DEC2 = Application.WorksheetFunction.Percentile(DataRange, 0.2)
DEC3 = Application.WorksheetFunction.Percentile(DataRange, 0.3)
DEC4 = Application.WorksheetFunction.Percentile(DataRange, 0.4)
DEC5 = Application.WorksheetFunction.Percentile(DataRange, 0.5)
DEC6 = Application.WorksheetFunction.Percentile(DataRange, 0.6)
DEC7 = Application.WorksheetFunction.Percentile(DataRange, 0.7)
DEC8 = Application.WorksheetFunction.Percentile(DataRange, 0.8)
DEC9 = Application.WorksheetFunction.Percentile(DataRange, 0.9)
' Calculate the Decile rank that the reference cell value sits within
If (RefCell <= DEC1) Then DECILE_RANK = 1
If (RefCell > DEC1) And (RefCell <= DEC2) Then DECILE_RANK = 2
If (RefCell > DEC2) And (RefCell <= DEC3) Then DECILE_RANK = 3
If (RefCell > DEC3) And (RefCell <= DEC4) Then DECILE_RANK = 4
If (RefCell > DEC4) And (RefCell <= DEC5) Then DECILE_RANK = 5
If (RefCell > DEC5) And (RefCell <= DEC6) Then DECILE_RANK = 6
If (RefCell > DEC6) And (RefCell <= DEC7) Then DECILE_RANK = 7
If (RefCell > DEC7) And (RefCell <= DEC8) Then DECILE_RANK = 8
If (RefCell > DEC8) And (RefCell <= DEC9) Then DECILE_RANK = 9
If (RefCell > DEC9) Then DECILE_RANK = 10
End Function
935,000 lines is a lot for excel. Like, really a lot. Barring saying using a real database, If your application is literally putting a =Percentile(...) in each cell, I would recommend Trying to use another tool for that. Perhaps something within VBA itself. More generally, use something outside of a cell - then store the result value in the cell. There is a lot of overhead in maintaining those formulas that are interdependent on 935k rows of data.
The problem is that your looping through each row individually, the Excel way is to try and work with whole ranges at once whenever possible. I would load the ranges into arrays, then modify your DECILE_RANK code to work with the items in the array.
Note that variant arrays that read ranges in are 2-D.
Here is the fully functioning code including my custom VBA array slicer. Note that it was only tested on a small dataset:
Sub Bucketting()
Dim lastRow As Long
Dim bidArray As Variant
Dim offerArray As Variant
Sheets("Sheet1").Select 'Sheet selected
With ActiveSheet
lastRow = .UsedRange.Rows.Count + 1
bidArray = .Range("F2:F" & lastRow)
offerArray = .Range("G2:G" & lastRow)
Range("J2:J" & lastRow).Value = GetArraySlice2D(DECILE_RANK(bidArray), "column", 1, 1, 0)
Range("K2:K" & lastRow).Value = GetArraySlice2D(DECILE_RANK(offerArray), "column", 1, 1, 0)
End With
Range("J1").Select
ActiveCell = "Bid Rank"
ActiveCell.Offset(0, 1) = "Offer Rank"
End Sub
Function DECILE_RANK(DataRange As Variant) As Variant
' Credit: BJRaid
' DECILE_RANK(The Range of data)
' Declares the function that can be called in the spreadsheet cell - enter '=DECILE_RANK(A5:A50,A5)
Dim DEC(0 To 10) As Variant
Dim i As Integer, j As Integer
'Using the percentile worksheet function calculate where the 10th, 20th etc percentile of the reference range are
DEC(0) = 0
For i = 1 To 9
DEC(i) = Application.WorksheetFunction.Percentile(DataRange, 0.1 * i)
Next i
DEC(10) = Application.WorksheetFunction.Max(DataRange)
' Calculate the Decile rank that the reference cell value sits within
For i = 1 To UBound(DataRange, 1)
For j = 1 To 10
If ((DataRange(i, 1) > DEC(j - 1)) And (DataRange(i, 1) <= DEC(j))) Then
DataRange(i, 1) = j
Exit For
End If
Next j
Next i
DECILE_RANK = DataRange
End Function
Public Function GetArraySlice2D(Sarray As Variant, Stype As String, Sindex As Integer, Sstart As Integer, Sfinish As Integer) As Variant
' this function returns a slice of an array, Stype is either row or column
' Sstart is beginning of slice, Sfinish is end of slice (Sfinish = 0 means entire
' row or column is taken), Sindex is the row or column to be sliced (NOTE:
' 1 is always the first row or first column)
' an Sindex value of 0 means that the array is one dimensional 3/20/09 Lance Roberts
Dim vtemp() As Variant
Dim i As Integer
On Err GoTo ErrHandler
Select Case Sindex
Case 0
If Sfinish - Sstart = UBound(Sarray) - LBound(Sarray) Then
vtemp = Sarray
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(i + Sstart - 1)
Next i
End If
Case Else
Select Case Stype
Case "row"
If Sfinish = 0 Or (Sstart = LBound(Sarray, 2) And Sfinish = UBound(Sarray, 2)) Then
vtemp = Application.WorksheetFunction.Index(Sarray, Sindex, 0)
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(Sindex, i + Sstart - 1)
Next i
End If
Case "column"
If Sfinish = 0 Or (Sstart = LBound(Sarray, 1) And Sfinish = UBound(Sarray, 1)) Then
vtemp = Application.WorksheetFunction.Index(Sarray, 0, Sindex)
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(i + Sstart - 1, Sindex)
Next i
End If
End Select
End Select
GetArraySlice2D = vtemp
Exit Function
ErrHandler:
Dim M As Integer
M = MsgBox("Bad Array Input", vbOKOnly, "GetArraySlice2D")
End Function
I'm not sure if this will directly address your problem, but have you considered using Application.ScreenUpdating = False? Don't forget to set it back to true once your data has processed.

Resources