I have 4 variables and wish to list which (maximum of 3) variables are being used.
I have used VBA functions before but I am stumped as to the reason this isn't working..
The four variables are percentages so for example:
if (20%,empty,20%,60%) I want the three cells to be (A,C,D)
if (50%,50%,empty,empty) => (A,B,empty)
Hello,
if (empty,empty,100%,empty) => (C,empty,empty)
The code I have at the moment isn't working (for the first cell):
Function whichtoa(w As Integer, x As Integer, y As Integer, z As Integer) As String
If w <> 0 Then
whichtoa = "A"
ElseIf x <> 0 Then
whichtoa = "B"
ElseIf y <> 0 Then
whichtoa = "C"
ElseIf z <> 0 Then
whichtoa = "D"
End If
End Function
Could it be to do with the empty cells being general and the others being a percentage? I can't really change this as the data is coming from another program.
Could I use a null check or something similar?
Thanks in advance!
Lucas
Consider the following data. the last column has the formula for whichtoA
A B C D E
60% 40% 30% 30% ABC
30% 60% 30% 90% ABC
10% 20% 50% ABC
30% 50% BC
30% C
50% 60% CD
If you are using percentages you need to use something other than integer in your function since you're dealing with decimals.
Function whichtoa(w As Double, x As Double, y As Double, z As Double) As String
Dim emptyCount As Integer
Dim results As String
' Assume zero
valueCount = 0
' Assume empty string
results = ""
If w <> 0 Then
results = results & "A"
valueCount = valueCount + 1
End If
If x <> 0 Then
results = results & "B"
valueCount = valueCount + 1
End If
If y <> 0 Then
results = results & "C"
valueCount = valueCount + 1
End If
' This is the only time you need to check the condition of valueCount. If you want 3 maximum
If (z <> 0) And (valueCount < 3) Then
results = results & "D"
End If
whichtoa = results
End Function
Each condition is checked individually. The If block you have will only process the first match and then stop evaluating the block. Then, counting the number of positive values, or hits if you will, with valueCount we can stop processing if we get 3 hits. This only needs to be checked with z parameter in the event we have 3 hits already at that point. Build the results as a string and return it.
Your conditional statement is chained: each ElseIf is only evaluated if the preceding If evaluates to True, so the function will only return a single string value (either A, B, C, or D but not a combination of multiple possible values, which would require stroing them all in a collection/dictionary/array/etc., and removing the ones that are empty values.
Compounded by implied type conversion (presumably you're passing range objects to this function, on a worksheet, which evaluate to their .Value which is "0" if the range is empty.
Another problem you may not have hit yet (if you're still working through the above) is that if the cell values contain percentages, by casting them as Integer in the function declaration, any values which round down to 0 will be evaluated as zero.
I suggest declaring the variables as Range objects, and then specifically check their .Value property. Store ALL cells and a key value ("A", "B", etc.) in a dictionary. Iterate the dictioanry and check the value for emptiness:
I also use this to return an error value if the dictionary contains 4 items, since you want a maximum of 3.
Function whichtoa(a As Range, b As Range, c As Range, d As Range)
Dim dict As Object
Dim itm As Variant
Set dict = CreateObject("Scripting.Dictionary")
'Add each cell value to a dictionary, using unique letter as Key
dict("A") = a.Value
dict("B") = b.Value
dict("C") = c.Value
dict("D") = d.Value
'iterate the dictionary keys, removing empty values
For Each itm In dict.Keys()
If IsEmpty(dict(itm)) Then dict.Remove (itm)
Next
If Not dict.Count = 4 Then
whichtoa = Join(dict.Keys(), ",")
Else:
whichtoa = CVerr(2023)
End If
End Function
I'm not sure exactly what the return value is that you want (your example are inconsistent), but the following may point you in the right direction:
Public Function whichtoa(r as Range)
Dim arr, i
arr = Array("A", "B", "C", "D")
For i = 0 to 3
If IsEmpty(r.Cells(1,i+1) Then
arr(i) = "empty"
End If
Next
whichtoa = arr(0) & "," & arr(1) & "," & arr(2) & "," & arr(3)
End Function
Related
what I am currently trying to do is to find and highlight cells that contain simultaneously a certain phrase and (among some other text) a number that is equal or greater than 20 (including numbers with decimals like 25.8332). I tried using FormatConditions, but I wasn't able to make it consider two simultaneous conditions (a phrase and a number). So I decided to use a combination of If and InStr, but I wonder how to fill in the number that is equal or greater than 20?
Select the cells you wish to process and run:
Sub ColorMeYellow()
Dim r As Range, s As String, n As Double
Dim happy As String, CH As String, temp As String
Dim L As Long, i As Long
happy = "happy"
For Each r In Selection
s = r.Value
If InStr(1, s, happy) > 0 Then
L = Len(s)
temp = ""
For i = 1 To L
CH = Mid(s, i, 1)
If CH Like "[0-9]" Or CH = "." Then
temp = temp & CH
End If
Next i
If IsNumeric(temp) Then
If CDbl(temp) > 20 Then
r.Interior.ColorIndex = 6
End If
End If
End If
Next r
End Sub
It will look for cells containing both *"happy" and a number greater than 20.
Situation
I have a UDF that works with a range that it is passed that is of variable height and 2 columns wide. The first row will contain text in column 1 and an empty column2. The remainder of column 1 will contain unsorted text with an associated value in the same row in column 2. I need to sort the data such that if some text in column 1 also appears in some other text in column.
Problem
My VBA skills are all self taught and mimimal at best. I remember a few decades ago in university we did bubble sorts and played with pointers, but I no longer remember how we achieved any of that. I do well reading code but creating is another story.
Objective
I need to generate a sort procedure that will produce unique text towards the bottom of the list. I'll try wording this another way. If text in column1 can be found within other text in column, that the original text need to be placed below the other text it can be found in along with its associated data in column 2. The text is case sensitive. Its not an ascending or descending sort.
I am not sure if its a restriction of the UDF or not, but the list does not need to be written back to excel, it just needs to be available for use in my UDF.
What I have
Public Function myFunk(rng As Range) As Variant
Dim x As Integer
Dim Datarange As Variant
Dim Equation As String
Dim VariablesLength As Integer
Dim Variable As String
Datarange = rng.Value
'insert something around here to get the list "rng or Datarange" sorted
'maybe up or down a line of code depending on how its being done.
Equation = Datarange(1, 1)
For x = 2 To UBound(Datarange, 1)
VariablesLength = Len(Datarange(x, 1)) - 1
Variable = Left$(Datarange(x, 1), VariablesLength)
Equation = Replace$(Equation, Variable, Datarange(x, 2))
Next x
myFunk = rng.Worksheet.Evaluate(Equation)
End Function
Example Data
Any help with this would be much appreciated. In that last example I should point out that the "=" is not part of the sort. I have a routine that strips that off the end of the string.
So in order to achieve what I was looking for I added a SWAP procedure and changed my code to look like this.
Public Function MyFunk(rng As Range) As Variant
Dim x As Integer
Dim y As Integer
Dim z As Integer
Dim datarange As Variant
Dim Equation As String
Dim VariablesLength As Integer
Dim Variable As String
'convert the selected range into an array
datarange = rng.Value
'verify selected range is of right shape/size
If UBound(datarange, 1) < 3 Or UBound(datarange, 2) <> 2 Then
MyFunk = CVErr(xlErrNA)
Exit Function
End If
'strip the equal sign off the end if its there
For x = 2 To UBound(datarange, 1)
If Right$(datarange(x, 1), 1) = "=" Then
datarange(x, 1) = Left$(datarange(x, 1), Len(datarange(x, 1)) - 1)
End If
Next x
'sort the array so that a variable does not get substituted into another variable
'do a top down swap and repeat? Could have sorted by length apparently.
For x = 2 To UBound(datarange, 1) - 1
For y = x + 1 To UBound(datarange, 1)
If InStr(1, datarange(y, 1), datarange(x, 1)) <> 0 Then
For z = LBound(datarange, 2) To UBound(datarange, 2)
Call swap(datarange(y, z), datarange(x, z))
Next z
y = UBound(datarange, 1)
x = x - 1
End If
Next y
Next x
'Set the Equation
Equation = datarange(1, 1)
'Replace the variables in the equation with values
For x = 2 To UBound(datarange, 1)
Equation = Replace$(Equation, datarange(x, 1), datarange(x, 2))
Next x
'rest of function here
End Function
Public Sub swap(A As Variant, B As Variant)
Dim Temp As Variant
Temp = A
A = B
B = Temp
End Sub
I sorted by checking to see if text would substitute into other text in the list. Byron Wall made a good point that I could have sorted based on text length. Since I had completed this before I saw the suggestion it did not get implemented though I think it may have been a simpler approach.
I am having trouble figuring out how to subtract two ranges from each other, some cells in range H:H have "#N/A" while in range D:D there are no errors. I know in Excel it's a simple "=H2-D2" and drag that down but I'm in the process of recording a Macro and wanted to automate the subtraction as well. So far this is what I have:
Dim quantity1, quantity2, rIntersect, Qdiff, x As Range
Set quantity1 = Range("D:D")
Set quantity2 = Range("H:H")
Set rIntersect = Intersect(quantity1, quantity2)
For Each x In quantity1
If Intersect(rIntersect, x) Is Nothing Then
If Qdiff Is Nothing Then
Set Qdiff = x
Else
Set Qdiff = Application.Union(Qdiff, x)
End If
End If
Next x
Range("J2").Select
Dim lastRowJ As Long
lastRowJ = Range("A" & Rows.Count).End(xlUp).Row
Range("J2").AutoFill Destination:=Range("J2:J" & lastRowJ)
Place this procedure in a standard code module:
Public Sub Subtract()
[j2:j99] = [h2:h99-d2:d99]
End Sub
If you like how that works, I'm happy to embellish it so that it is not hard-coded for 98 rows only. Let me know.
UPDATE
Here is a version that will deal with any number of rows. It keys off of column D. So if there are 567 numbers in column D, then you will get 567 corresponding (subtracted) results in column J.
This assumes that the data start in row 2, and that there are no blank cells until the numbers in column D end.
If you are going to call this from the Macro Dialog then you should keep it Public. If on the other hand you are going to call it from another procedure in the same module, then you can make it Private.
Here is the enhanced solution:
Public Sub Subtract()
Dim k&
Const F = "iferror(h2:h[]-d2:d[],0)"
k = [count(d:d)]
[j2].Resize(k) = Evaluate(Replace(F, "[]", k + 1))
End Sub
Note that the routine now handles the errors and places a ZERO value in column J when the corresponding value in column H is an error. If you would prefer to have something other than a ZERO (like a blank for instance) when there are errors in column H, just let me know and I'll update to whatever you want.
UPDATE 2
Here is how to handle displaying blanks instead of zeroes:
Public Sub Subtract()
Dim k&
Const F = "iferror(if(h2:h[]-d2:d[]=0,"""",h2:h[]-d2:d[]),0)"
k = [count(d:d)]
[k2].Resize(k) = Evaluate(Replace(F, "[]", k + 1))
End Sub
I have an extremely large dataset with customer numbers and we cannot just use a =IF(E3=160248, "YES", "NO") to tag a particular customer number of 160248 with YES or NO. Instead, I would like to use VBA code to lookup Customer_Number in column E and return a YES or NO in the corresponding row in Column AG, called Incorporated_160248. I have not done an If then clause in VBA, so I have no idea where to start. Please note, each month the data set can change. One month it could be 4,000 entries and the next 3,500, so that has to be dynamic. Any thoughts?
Sub TagTryco()
Dim CN As Integer, result As String
CN = Range("E:E").Value
If CN = 160248 Then
result = "YES"
Else
result = "NO"
End If
Range("AG:AG").Value = result
End Sub
I get a Compile error: Wrong number of arguments or invalid property assignment.
This CODE Works now:
Sub TagTryco()
Dim listLength
listLength = Worksheets("ILS_Import").Cells(Rows.Count, "E").End(xlUp).Row - 1
Dim i As Integer
For i = 2 To listLength + 2
If Worksheets("ILS_Import").Range("E" & i) = 160248 Then
Worksheets("ILS_Import").Range("AG" & i) = "Yes"
Else
Worksheets("ILS_Import").Range("AG" & i) = "No"
End If
Next
End Sub
To know how many entries you have:
dim listLength
listlength = Sheet1.Cells(Rows.Count, "E").End(xlUp).Row - 1 'I assumed column E, starting at row 2
You need to loop from row 2 to the row 2 + listLength, check the cell in column E, and check if it is equal to your number:
dim i as integer
for i = 2 to listLength + 2
If Range("E" & i) = 160248 Then
Range("AG" & i) = "Yes"
Else
Range("AG" & i) = "No"
End If
Next
If you wish to scan for different numbers you can adapt the code to use a value from a cell in which you enter that number, OR use an inputbox to enter the number you want to look for, or something else. This code was not tested.
If you want to use the column name you assigned instead of AG (which is safer) you can use something along the lines of:
= Range("Incorporated_160248")(i+1)
Instead, which gives the column with an offset of i. Should bring you to the right cell.
I need a code that could give me a list of unique combinations from a set of elements in an array, something like this:
Say myArray contains [A B C]
So, the output must be:
A
B
C
A B
A C
B C
A B C
or
A B C
B C
A C
A B
A
B
C
either output is OK for me (Starts with 1 combination, followed by 2 combinations and ends with all combination OR vice versa).
The position of the letters are not critical and the order of letters within the same combination type is also not critical.
I'd found a suggestion by 'Dick Kusleika' in a thread: Creating a list of all possible unique combinations from an array (using VBA) but when I tried, it did not present me with the arrangement that I wanted.
I'd also found a suggestion by 'pgc01' in a thread: http://www.mrexcel.com/forum/excel-questions/435865-excel-visual-basic-applications-combinations-permutations.html and it gave me the arrangement that I wanted however, the combinations was not being populated in an array but it was being populated in excel cells instead, using looping for each combination.
So, I wanted the arrangement of combinations to be like what 'pgc01' suggested and being populated in an array as what 'Dick Kusleika' presented.
Anyone can help? Appreciate it.
Start from here:
Sub TestRoutine()
Dim inputt() As String, i As Long
Dim outputt As Variant
inputt = Split("A B C", " ")
outputt = Split(ListSubsets(inputt), vbCrLf)
For i = LBound(outputt) + 2 To UBound(outputt)
MsgBox i & vbTab & outputt(i)
Next i
End Sub
Function ListSubsets(Items As Variant) As String
Dim CodeVector() As Long
Dim i As Long
Dim lower As Long, upper As Long
Dim SubList As String
Dim NewSub As String
Dim done As Boolean
Dim OddStep As Boolean
OddStep = True
lower = LBound(Items)
upper = UBound(Items)
ReDim CodeVector(lower To upper) 'it starts all 0
Do Until done
'Add a new subset according to current contents
'of CodeVector
NewSub = ""
For i = lower To upper
If CodeVector(i) = 1 Then
If NewSub = "" Then
NewSub = Items(i)
Else
NewSub = NewSub & " " & Items(i)
End If
End If
Next i
If NewSub = "" Then NewSub = "{}" 'empty set
SubList = SubList & vbCrLf & NewSub
'now update code vector
If OddStep Then
'just flip first bit
CodeVector(lower) = 1 - CodeVector(lower)
Else
'first locate first 1
i = lower
Do While CodeVector(i) <> 1
i = i + 1
Loop
'done if i = upper:
If i = upper Then
done = True
Else
'if not done then flip the *next* bit:
i = i + 1
CodeVector(i) = 1 - CodeVector(i)
End If
End If
OddStep = Not OddStep 'toggles between even and odd steps
Loop
ListSubsets = SubList
End Function
Note we discard the first two elements of the output array.