Macro generated by maximum of absolute value of cells - excel

Numeric data is streamed into cells B8, B10, B12, B14, B16 and B18 (see below).
Cell B23 is the maximum of the absolute value of the above cells at any time, so the formula in B23 is :
=MAX(ABS($B$8),ABS($B$10),ABS($B$12),ABS($B$14),ABS($B$16),ABS($B$18))
Cell B5 is a user-defined constant, in our case 13.00, and is the threshold value that will trigger one of the macros.
So, in the case below, B23 = 8.00, and because 8.00 < 13.00 no macro is called.
If, however, B5 was 7.50, then since B23 (8.00) >= 7.50, and B14 is a positive value, Macro_7 is to be called. Had B14 been -8.00, then Macro_8 is to be called.
This process is to be started when the user presses the START button, which has macro START assigned to it. Once a macro is called, the process ends till the user restarts it.
I am having trouble coding this in VBA and would appreciate any assistance.

Please try this function.
Function AbsoluteMaximum(RowNum As Long, _
Sign As Long) As Double
Dim AbsMax As Double ' variables range
Dim Tmp As Double ' temporary value
Dim R As Long ' row number
Dim i As Integer ' loop counter: iterations
R = RowNum
RowNum = 0 ' return 0 in case of failure
For i = 1 To 6 ' number of cells
Tmp = Cells(R, "B").Value
If Abs(Tmp) > AbsMax Then
AbsMax = Abs(Tmp)
Sign = Sgn(Tmp)
RowNum = R
End If
R = R + 2
Next i
AbsoluteMaximum = AbsMax
End Function
It returns 3 values: the absolute maximum, the row number where it was found and its Sign. The Sgn() function returns 1 for a positive number, -1 for a negative number and 0 for zero.
This is how you can call the function from VBA.
Sub Test_AbsMax()
Dim RowNum As Long
Dim Sign As Long
Dim AbsMax As Double
RowNum = 8 ' start row: change to suit
AbsMax = AbsoluteMaximum(RowNum, Sign)
MsgBox "Absolute Max = " & AbsMax & vbCr & _
"Sign = " & Sign & vbCr & _
"in row number " & RowNum
End Sub
You can use the Sign variable with code like
Clm = Iif(Sign < 0, 3, 1), specifying columns A or C to link to a button.
Observe that RowNum is the first row number for your variables when the function is called but changed by the function to become the row number where the maximum was found. Therefore its value is different before and after the function call.
If this number is below the threshold you would call no further macro. Else you would call a macro determined by RowNum and Sign.

Try this
Sub RunMacro()
Dim rng As Range
Dim dThreshold As Double
Dim i As Long
Dim dValue As Double
Dim dRunningMin As Double: dRunningMin = 1E+20
Dim lIndex As Long
' Change the sheet name
With ThisWorkbook.Sheets("Sheet2")
Set rng = .Range("B8:B18")
dThreshold = .Range("B5")
lIndex = 0
For i = 1 To rng.Rows.Count Step 2
dValue = rng.Cells(i, 1).Value
If Abs(dValue) >= dThreshold Then
If Abs(dValue) - dThreshold < dRunningMin Then
dRunningMin = Abs(dValue) - dThreshold
lIndex = i + IIf(dValue < 0, 1, 0)
End If
End If
Next i
If lIndex > 0 Then
Application.Run "Macro_" & lIndex
End If
End With
End Sub
The code above will work out the number whose absolute value is greater than the threshold and is nearest to it.
e.g.
Threshold Macro
13 None
7.5 Macro_7
4 Macro_3 (but not Macro_10)
3.1 Macro_6
3 Macro_11
2 Macro_1
If, however, you want to run all macros for numbers whose absolute values are greater than the threshold then you need something like this:
Sub RunMacros()
Dim rng As Range
Dim dThreshold As Double
Dim i As Long
Dim dValue As Double
' Change the sheet name
With ThisWorkbook.Sheets("Sheet2")
Set rng = .Range("B8:B18")
dThreshold = .Range("B5")
For i = 1 To rng.Rows.Count Step 2
dValue = rng.Cells(i, 1).Value
If Abs(dValue) >= dThreshold Then
Application.Run "Macro_" & i + IIf(dValue < 0, 1, 0)
End If
Next i
End With
End Sub
e.g.
Threshold Macro
13 None
7.5 Macro_7
4 Macro_3, Macro_7 and Macro_10
3.1 Macro_3, Macro_6, Macro_7, Macro_10
3 Macro_3, Macro_6, Macro_7, Macro_10, Macro_11
2 Macro_1, Macro_3, Macro_6, Macro_7, Macro_10, Macro_11

Related

Copy values from cells in range and paste them in random cell in range

I have two ranges as showed in this picture.
I'm trying to write a VBA macro that successively selects a single cell in the first range (“B23, F27”) , copies the selected cell's value, then selects a random cell in the second range (“G23, K27”), and pastes the first cell's value into the randomly selected cell in the second range.
This should repeat until every cell from the first range has been copied, or every cell in the second range is filled with a new value. In this example both outcomes are equivalent as both ranges have the same number of cells (25).
The result should be like the second image.
I tried to assign the first range to an array and then pick a random value from this array and paste it to the second range.
I also tried to extract unique values from the first range, build a dictionary with it then pick a random cell from the second range and a random value from the dictionary and paste it.
Later I tried again using the VBA syntax “with range” and f"or each cell in range" but I can’t just come up with something that actually works. Sometimes the second range is filled by various values, but not as intended.
First example: this one just does not work
Sub fillrange()
Dim empty As Boolean
'This part checks if every cell in the first range as a value in it
For Each Cell In Range("B23", "F27")
If Cell.Value = "" Then
empty = True
End If
Next
'If every cell is filled then
If empty Then
Exit Sub
Else:
With ThisWorkbook.Worksheets("Sheet1)").Range("B23", "F27")
.Cells(Application.WorksheetFunction.RandBetween(1, 25)).Select
.Copy 'the cell select works, but it will copy all range
'This does not work
'For Each Cell In Range("G23", "K27")
'Cells(Application.WorksheetFunction.RandBetween(1, 25)).Select
'.PasteSpecial Paste:=xlPasteValues
'Next
End With
End If
End Sub
Second example: it fills the range but with wrong values
Sub fillrange2()
Dim empty As Boolean
For Each cell In Range("B23", "F27")
If cell.Value = "" Then
empty = True
'This part checks if every cell in the first range as a value in it
Exit For
End If
Next cell
If empty Then
Exit Sub
Else:
Dim ws As Worksheet
Dim lRow As Long, i As Long
Dim col As New Collection, itm As Variant
Set ws = ThisWorkbook.Sheets("Sheet1")
With ws
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 1 To lRow
On Error Resume Next
col.Add .Range("B23", "F27").Value, CStr(.Range("A" & i).Value)
On Error GoTo 0
Next i
End With
Dim MyAr() As Variant
ReDim MyAr(0 To (col.Count - 1))
For i = 1 To col.Count
MyAr(i - 1) = col.Item(i)
Next
For Each cell In Range("G23", "K27")
cell.Value = Application.WorksheetFunction.RandBetween(LBound(MyAr), UBound(MyAr))
Next
End If
End Sub
Third example: as the second example, it fills the range but with wrong values
Sub fillrange3()
Dim MyAr() As Variant
MyAr = Range("B23", "F27")
For Each cell In Range("G23", "K27")
cell.Value = Application.WorksheetFunction.RandBetween(LBound(MyAr), UBound(MyAr))
Next
End Sub
Maybe something like this ?
Sub test()
Set Rng = Range("G23:K27")
n = 1
totCell = 25
Set oFill = Range("G23")
Set oSource = Range("B23")
For i = 1 To 5
oFill.Value = "X" & n
oFill.AutoFill Destination:=Range(oFill, oFill.Offset(4, 0)), Type:=xlFillSeries
Set oFill = oFill.Offset(0, 1)
n = n + 5
Next i
For i = 1 To 5
Do
RndVal = Int((totCell - 1 + 1) * Rnd + 1)
xVal = "X" & RndVal
Set C = Rng.Find(xVal, lookat:=xlWhole)
If Not C Is Nothing Then
C.Value = oSource.Value
Set oSource = oSource.Offset(1, 0)
check = check + 1
If check = 5 Then Exit Do
End If
Loop
Set oSource = oSource.Offset(-5, 1)
check = 0
Next i
End Sub
I cheat by making a preparation for the range G23 to K27 fill with X1 to X25 in the first for i = 1 to 5.
The second for i = 1 to 5 is to offset from column B to G.
The Do - Loop is to generate random number between 1 to 25.
If the generated number is found then the found cell has the value from the "source",
if not found, it loop until the generated number is found 5 times (hence also the found cell is fill with 5 different source). Then before the next i, the "source" cell is offset to the next column.
This if I'm not wrong to get what you mean.
Here's another approach, just for a bit of variety.
Sub x()
Dim r1 As Range, r2 As Range, i As Long
Dim r As Long, c As Long
Set r1 = Range("B23").Resize(5, 5) 'define our two ranges
Set r2 = Range("G23").Resize(5, 5)
r2.ClearContents 'clear output range
With WorksheetFunction
Do Until .Count(r2) = r2.Count 'loop until output range filled
r = .RandBetween(1, 25) 'random output cell number
If .CountIf(r2, r1.Cells(i)) = 0 Then 'if not in output range already
If r2.Cells(r) = vbNullString Then 'if random cell empty
r2.Cells(r).Value = r1.Cells(i).Value 'transfer value
i = i + 1
End If
End If
Loop
End With
End Sub

Excel VBA Vlookup return error 2042 even if using IsError

The following code is not working. I get a 2042 error for my VLOOKUP function, however whatever I do I cannot solve it. I have been using if ISERROR and it still does not catch it properly compromising my whole macro. If I run a local window you can see that the value to search for being stored in array "arr" if not found in the "target" range return a 2042 even for subsequent entries.
Sub test()
ThisWorkbook.Activate
Worksheets.add
Worksheets("Test4").Range("A1:T110").copy Destination:=ActiveSheet.Range("A1")
With ActiveSheet
Dim Search_Array As Variant
Search_Array = Range("C2", Range("C1").End(xlDown)) 'use this array to loop through the value to search for
Dim Target_MatchValue As Integer
Dim Target_Range As Range
Dim arr As Variant
Dim counter As Integer
Dim n As Integer
counter = 0
n = 0
Target_MatchValue = 0
For counter = LBound(Search_Array) To UBound(Search_Array)
Target_MatchValue = 0
Target_MatchValue = Application.Match(Search_Array(counter, 1), .Range("H2:H200"), 0) - 1
Set Target_Range = .Range(.Cells(2 + n, 8), .Cells(1000, 9))
arr = Application.VLookup(Search_Array(counter, 1), Target_Range, 2, False)
If IsError(arr) Then
.Range(Cells(1 + counter, 6), Cells(1 + counter, 6)).value = "N/A"
Else
.Range(Cells(1 + counter, 6), Cells(1 + counter, 6)).value = arr 'Return the value of the array in this cell
End If
Target_Range.Select
If Target_MatchValue = 0 Then
n = n + 1
ElseIf Target_MatchValue > 0 Then
n = n + Target_MatchValue
End If
Next counter
End With
End Sub
SOLUTION
Sub test()
Dim Search_Array As Variant
Dim Target_MatchValue As Variant
Dim Target_Range As Range
Dim arr As Variant
Dim counter As Integer
Dim n As Integer
Worksheets("Test4").Range("A1:T110").copy Destination:=ActiveSheet.Range("A1")
With ActiveSheet
'data must be ordered in order to apply the non-repetitive condition
Search_Array = Sheet1.Range("A2", Sheet1.Range("A1").End(xlDown)) 'use this array to loop through the value to search for
n = 0
With ActiveSheet
For counter = LBound(Search_Array) To UBound(Search_Array)
Target_MatchValue = 0
Target_MatchValue = Application.Match(Search_Array(counter, 1), .Range(Cells(2 + n, 4), Cells(1000, 4)), 0) 'The problem was here. "A1:T110" did not allowed to the shifting range to change. Now this code will return the value used for the shifting range
Set Target_Range = .Range(Cells(2 + n, 4), Cells(1000, 5)) 'this is supposed to work as a shifting range allowing to match entries without making repetitions. I used the MATCH function in order to set the start of the range. i.e. if there is a match in the target table the range will shift from the location of the match downwards. If the match is at on the same level then it does not shift the range in order to match the same-level entry afterwards it is supposed to shift by one unit in order to prevent repetitions.
'target_range.select Activate this code in order to see the macro in action
arr = Application.VLookup(Search_Array(counter, 1), Target_Range, 2, False) 'store the vlookup value in an array in order to increase the efficiency the code and to speed up the whole proces
If IsError(arr) Then
.Cells(2 + n, 2).value = "" 'if the macro does not find anything, no value will be recorded anywhere
Else
.Cells(1 + n + Target_MatchValue, 2).value = Search_Array(counter, 2) 'Return the value of the search_array in this cell so to match column A values with column D values if they are found
End If
If IsError(arr) Then
n = n
ElseIf Target_MatchValue = 0 Then 'if the macro does not find anything, the shifting range does not shift so that subsequent values can be searched in the same range without missing precious matches
n = n + 1
ElseIf Target_MatchValue > 0 Then 'if there is a matching value between Column A and Column B, the shifting range shifts by the n + the distance between the the current vlookupvalue and the found value. Note that Data must be stored in a filtered order otherwise vlookup will not work correctly
n = n + Target_MatchValue
End If
Next counter
End With
End Sub
Declare your Target_MatchValue As Variant so no errors will be raised, instead you will have to handle what do you want to do when IsError(Target_MatchValue) (when no matches are found)

Excel duplicate words in row

How do I count only as "2" if the word "apple" show in the same row like row3 and row4 ? The code i need is in Microsoft excel 2010 not vba
Expected Output:
Got very close to figuring out a formula but I'm afraid I bailed and created a UDF instead after getting fed up.
Paste the following into a module in the vba editor (will have to save the file as a .xlsm now as well). This will work for all 2D ranges (i.e. where the count of rows and the count of columns are both greater than 1) a 1D range you can use COUNTIF as stated in the comments above.
Public Function CountStringOccurence(count_text As String, within_range As Range) As Long
Dim arr As Variant
Dim i As Long
' Create array of 1's and 0's (Numerical trues and falses)
arr = Application.Evaluate("--(" & within_range.Parent.Name & "!" & within_range.Address & "=""" & count_text & """)")
' Loop through each row array
For i = LBound(arr, 1) To UBound(arr, 1)
' Get max value in each row and sum (i.e. if there is a True present add it to the total count)
CountStringOccurence = CountStringOccurence + Application.Max(Application.Index(arr, i, 0))
Next i
End Function
and call it using CountStringOccurence(B7,A3:G4)
In the function it first populates an array from the range with 1 if the value in the range matches the string wanted and 0 if it doesn't. It then loops through each row in the array summing the maximum value in the row (i.e. 1 if the value exists and 0 if it doesn't). It then feeds the answer back to the Excel cell
If someone can come up with a formula for it though I'd love to see it.
If you can add an extra column to sheet you can also achieve this doing:
Last column in sheet enter =MAX(--(A3:G3=$B$7)) for each row and then sum this column to get your answer
It may not be the most simple way to do it but here you go:
Public Sub getRowCountOfStringOccurance()
Dim thisRange As Range
Set thisRange = Selection
MsgBox (countStringOccurancesInRows("apple", thisRange))
End Sub
Public Function countStringOccurancesInRows(stringToFind As String, searchRange As Range) As Integer
Dim wb As Workbook
Set wb = ActiveWorkbook
Dim ws As Worksheet
Set ws = wb.ActiveSheet
Dim firstRow As Integer
Dim lastRow As Integer
Dim firstColumn As Integer
Dim lastColumn As Integer
Dim rowOccurances As Integer
rowOccurances = 0
Dim occurances As Integer
occurances = 0
firstRow = searchRange.Rows(1).Row
lastRow = searchRange.Rows.Count + firstRow - 1
firstColumn = searchRange.Columns(1).Column
lastColumn = searchRange.Columns.Count + firstColumn - 1
For thisRow = firstRow To lastRow
For thisColumn = firstColumn To lastColumn
If (ws.Cells(thisRow, thisColumn) = stringToFind) Then
rowOccurances = rowOccurances + 1
End If
Next
If (rowOccurances > 0) Then
occurances = occurances + 1
End If
Next
countStringOccurancesInRows = occurances
End Function
Be aware that I've entered the string for the moment and the range to be searched through has to be selected in the sheet. It will then give a messagebox with the result. While testing I had no issues.

Code to Generate a Sequential Range

I have a userform whose image is below.
What i need is when i open the userform, there should be sequential number against voucher # textbox.
for example.
Column B has values BPV/1, BPV/2, BPV/3.
What i need is when i run the userform, the voucher # textbox should show the next serial number i.e. BPV/4 and so on...
Below is my code.
Private Sub UserForm_Initialize()
Dim NextNum As Long, prefix As String
Dim i As Long
prefix = "BPV/"
NextNum = Application.WorksheetFunction.Max(Worksheets("Sheet1").Columns(2))
i = NextNum + 1
Me.TextBox2.Enabled = False
Me.TextBox2.Value = prefix & i
End Sub
Kindly review the code and advise how to achieve it.
Thanks
Salman Khan
In order to find the Max value in Column B , that consists of Strings, I am reading the strings into an array on type Long (in case you have very large numbers), using the Mid function. Afterwards, I can find the Max value in the array of numbers.
Conveting using the Mid function is done with the following line:
myArr(lRow) = CLng(Mid(.Cells(lRow, 2), 5))
the value 5 is calculated by Len(prefix) +1
Code
Option Explicit
Private Sub UserForm_Initialize()
Dim NextNum As Long, prefix As String
Dim LastRow As Long, lRow As Long
Dim myArr() As Long
prefix = "BPV/"
With Sheets("Sheet1")
'find last row with data in Column B
LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
ReDim myArr(2 To LastRow)
' read all cells contents and convert them to array of numbers
' start from 2nd row , 1st row has headers
For lRow = 2 To LastRow
If Mid(.Cells(lRow, 2), 5) <> "" Then
myArr(lRow) = CLng(Mid(.Cells(lRow, 2), 5))
End If
Next lRow
' find maximum value in array
NextNum = WorksheetFunction.Max(myArr)
End With
Me.TextBox2.Enabled = False
Me.TextBox2.Value = prefix & NextNum + 1
End Sub
You could enter this FormulaArray in Sheet1 let's say in A1:
=MAX(VALUE(SUBSTITUTE(B:B,"BPV/","")))
Then have this line pointing to that cell:
NextNum = Worksheets("Sheet1").Range("A1").value2
FormulaArrays are entered pressing* [Ctrl] + [Shift] + [Enter] simultaneously, you shall see { and } around the formula if entered correctly
This solutions uses the Application.Evaluate Method (Excel) to obtain the Last Voucher number at once avoiding the use of For...Next. It also uses constants (Const) to hold the Prefix and the MAX formula.
Private Sub UserForm_Initialize_EEM_Publish()
Const kPrefix As String = "BPV/"
Const kFml As String = "=MAX(IFERROR(1" & _
"*VALUE(SUBSTITUTE(#rTrg,""#Prefix"",""""))" & _
"*(SEARCH(""#Prefix"",#rTrg)),0))"
Dim rTrg As Range, sFml As String
Dim lNextNum As Long, l As Long
Rem Get Last Voucher Number
With ThisWorkbook.Worksheets("Sheet1").Columns("B")
Set rTrg = .Cells(1).Resize(.Cells(.Rows.Count).End(xlUp).Row)
End With
sFml = kFml
sFml = Replace(sFml, "#Prefix", kPrefix)
sFml = Replace(sFml, "#rTrg", rTrg.Address(, , , 1))
lNextNum = Application.Evaluate(sFml)
Rem Set Next Voucher Number
l = 1 + lNextNum
Me.TextBox2.Enabled = False
Me.TextBox2.Value = sPrefix & i
End Sub

VBA Looping/Logic Issue

I am writing a macro in excel for work and I am having trouble. In this scenario there are two sheets, "BU" and "TOPS Information". When the macro is used it is supposed to search every line of "BU" for the value found in "TOPS Information", then go to the next line of "TOPS Information and repeat the process. If it finds a correct match it is supposed to copy a cell and paste it into "TOPS Information".
Here is the code:
Sub QIM()
Dim j As Integer
Dim k As Integer
Dim i As Integer
Dim l As Integer
Dim m As Integer
Dim searchArray(1 To 3) As String
j = 0
k = 1
'WARNING: Temporary Sheet Names
lastRowTOPS = Worksheets("TOPS Information").Cells(Rows.Count, "A").End(xlUp).Row
lastRowBU = Worksheets("BU").Cells(Rows.Count, "A").End(xlUp).Row
'Cycle through BU rows
For j = lastRowTOPS To 1 Step -1
'Cycle through searchArray for each BU row
For k = lastRowBU To 1 Step -1
'//////////////////////////////////////
x = Sheets("BU").Range("B" & k).Value
y = Range("C" & j).Value
If StrComp(x, y) = 1 Then
Sheets("BU").Range("C" & k).Copy
Range("H" & j).PasteSpecial
End If
'//////////////////////////////////////
Next k
Next j
End Sub
This Macro obviously only works if "TOPS Information" is selected at the time. Any and all help would be most appreciated. THANKS!
You sorta answered it yourself. Range refers to the current sheet, but when you're bouncing around then you have to qualify it.
Prefix your ranges with the appropriate sheet like so,
Sub QIM()
Dim j As Integer
Dim k As Integer
Dim i As Integer
Dim l As Integer
Dim m As Integer
Dim searchArray(1 To 3) As String
j = 0
k = 1
'WARNING: Temporary Sheet Names
lastRowTOPS = Worksheets("TOPS Information").Cells(Rows.Count, "A").End(xlUp).Row
lastRowBU = Worksheets("BU").Cells(Rows.Count, "A").End(xlUp).Row
'Cycle through BU rows
For j = lastRowTOPS To 1 Step -1
'Cycle through searchArray for each BU row
For k = lastRowBU To 1 Step -1
'//////////////////////////////////////
x = Sheets("BU").Range("B" & k).Value
y = Sheets("TOPS Information").Range("C" & j).Value
If StrComp(x, y) = 1 Then
Sheets("BU").Range("C" & k).Copy
Sheets("TOPS Information").Range("H" & j).PasteSpecial
End If
'//////////////////////////////////////
Next k
Next j
End Sub
Assuming only want to copy the top most found data in BU to TOPS, you can use below.
Sub QIM()
Dim oWS_TOPS As Worksheet, oWS_BU As Worksheet ' Worksheet objects
Dim oRng_TOPS As Range, oRng_BU As Range ' Range objects
Dim R_TOPS As Long, R_BU As Long
Set oWS_TOPS = ThisWorkbook.Worksheets("TOPS Information") ' <-- Replace this "TOPS Information" to match future changes
Set oWS_BU = ThisWorkbook.Worksheets("BU") ' <-- Replace this "BU" to match future changes
R_TOPS = oWS_TOPS.Cells(Rows.Count, "A").End(xlUp).Row
R_BU = oWS_BU.Cells(Rows.Count, "A").End(xlUp).Row
' Search column B of BU for each cell in column C of TOPS
For Each oRng_TOPS In oWS_TOPS.Columns("C").Cells ' <-- Replace this "C" to match future changes
' Exit if row is more than last A column data
If oRng_TOPS.Row > R_TOPS Then Exit For
For Each oRng_BU In oWS_BU.Columns("B").Cells ' <-- Replace this "B" to match future changes
' Exit if row is more than last A column data
If oRng_BU.Row > R_BU Then Exit For
' Check if Ranges match (## See Update ##)
If InStr(1, oRng_TOPS.Value, oRng_BU.Value, vbTextCompare) > 0 Then
' Copy column C of found row in BU to column H of TOPS, then exit
oWS_BU.Cells(oRng_BU.Row, "C").Copy oWS_TOPS.Cells(oRng_TOPS.Row, "H") ' <-- Replace these "C" and "H" to match future changes
Exit For
End If
Next
Next
Set oWS_TOPS = Nothing
Set oWS_BU = Nothing
End Sub
There are many ways to achieve your goal, and this is one of it.
UPDATE Note on comparing cell values (String):
StrComp(S1,S2[,mode]) only return 3 values {-1, 0, 1} to indicate if S1 is less/equal/greater than S2. If you want an exact match (case sensitive and exact spacing), use If StrComp(S1,S2) = 0 Then.
InStr([i,]S1,S2[,mode]) only returns positive values - it returns the character location of first appearance of S2 in S1. If S2 is not found then it returns zero.
You can also use Trim(sText) to remove leading/ending spaces of sText.
Hope below screenshot says more.

Resources