I'm trying to reassemble some data that was loss for my office. One thing that could make life exponentially better is the ability to count the number of variables in a formula. For example:
=500+500+500
Ideally, I would like to return "3" in this situation, as there are three "variables" in the above formula. Right now I'm using a formula to pull the exact value from the particular cell, but I'd like to add something that would allow me to also output the number of variables in the formula/calculation.
If the only operator is + then this will get the count:
=LEN(FORMULATEXT(A1))-LEN(SUBSTITUTE(FORMULATEXT(A1),"+",""))+1
Since you may have different operators beside a + sign, you could do something like this in VBA:
Function count_parts(rngFormula As Range) As Integer
'Create an array with all the operators you want to account for
Dim operatorArray As Variant, operator As Variant
operatorArray = Array("+", "-", "/", "*")
'Capture the formula in the cell passed in
Dim strFormIn As String: strFormIn = rngFormula.Formula
Dim strFormOut As String: strFormOut = strFormIn
'Loop through the operators in the array and swap them out
'So the strFormOut is completely rid of them all
For Each operator In operatorArray
strFormOut = Replace(strFormOut, operator, "")
Next
'Count the difference in characters between our starting
'formula and the formula without operators, subtracting 1.
count_parts = Len(strFormIn) - Len(strFormOut) + 1
End Function
If + is the only operator you have to account for, then a much more simple function could be used:
Function count_parts(rngFormula As Range) As Integer
count_parts = UBound(Split(rngFormula.Formula, "+")) + 1
End Function
You can stick either of these in a new module in your VBE and after saving the workbook you can use this formula in a cell like =count_parts(A1)
Related
I have table with Product name and Batch No. The product name is selected from a combo box frmmaster.cmbproduct.
Each product has a unique combination for batch number and the last digit of the batch number changes.
Sample table
The next batch number of a particular product is the last batch number +1. I have used aggregate function in excel to find the highest batch number. But the same aggregate function is throwing error in VBA.
Aggregate function for excel is =AGGREGATE(14,4,($B$2:$B$2000 =N12)*$C$2:$C$2000,1) where in N12 I am putting product name. Column B contains product name and column C contains batch number
Dim res As Long
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("Database")
With sh
'On Error Resume Next
res = WorksheetFunction.Aggregate(14, 4, ("R2C2:R2000C2" = FrmMaster.CmbProduct.Value) * "R2C3:R2000C3", 1)
MsgBox (res)
End With
The above code is throwing type mismatch error
I have changed the variable to hold value to variant and string but no result.
The reason for the type mismatch error is that the term ("R2C2:R2000C2" = FrmMaster.CmbProduct.Value) * "R2C3:R2000C3" tries multiplying a boolean value (result of ("R2C2:R2000C2" = FrmMaster.CmbProduct.Value)) with the string literal "R2C3:R2000C3". That of course is a type mismatch.
But there are other issues.
The term "R2C2:R2000C2" = FrmMaster.CmbProduct.Value compares the string "R2C2:R2000C2" to the FrmMaster.CmbProduct.Value. This always gets false except FrmMaster.CmbProduct.Value would be "R2C2:R2000C2". And it is not the same as $B$2:$B$2000=N12 in your array formula. There the $B$2:$B$2000 is a range of cell values which each gets compared to value of N12. But this is nothing what VBA is able to do. Even (sh.Range("$B$2:$B$2000") = FrmMaster.CmbProduct.Value) cannot work because this tries comparing an array (sh.Range("$B$2:$B$2000")) to a single value.
So the only way to evaluate an array formula having that kind of array operations in it is using Evaluate. WorksheetFunction cannot help here since it needs the single function parameters evaluated trough VBA first.
Example solving your problem:
Dim res As Long
Dim sh As Worksheet
Dim range1Address As String
Dim range2Address As String
Dim arrayFormula As String
Set sh = ThisWorkbook.Sheets("Database")
With sh
range1Address = .Range("B2:B2000").Address(RowAbsolute:=True, ColumnAbsolute:=True, ReferenceStyle:=xlA1, External:=True)
range2Address = .Range("C2:C2000").Address(RowAbsolute:=True, ColumnAbsolute:=True, ReferenceStyle:=xlA1, External:=True)
arrayFormula = "AGGREGATE(14,4,(" & range1Address & "=""" & FrmMaster.CmbProduct.Value & """)*" & range2Address & ",1)"
res = Evaluate(arrayFormula)
MsgBox res
End With
Getting range1Address and range2Address as external addresses (including workbook name and sheet name) from the ranges makes this independent of the active sheet.
I wonder if the formula below will be of help to you. It will return whatever number it finds associated with the lookup value +1.
=IFERROR(LOOKUP(2,1/($A1:$A$1=A2),$B1:$B$1),0)+1
First, the functionality. The function looks for the last occurrence of A2 above A2 which is in a range starting at absolute $A$1 and ending in relative $A1. The latter will expand as the formula copied down. Same system in column B. If nothing is found and error occurs, and if an error occurs the function returns 0. To the result of this 1 is added.
Now, if you would replace the 0 in the formula (which is the result in case of no precedent) with a number like 2022050 the formula would then return the correct result in the first row of your example and for all subsequent occurrences of "Amoxycillin Capsules 500".
It would return 1 on the line of Paracetamol tablets because there is no precedent. The 1 would stick out and perhaps you have a system by which you can fill in the missing numbers there.
The problem would be the same, regardless of whether you apply VBA or a worksheet function. You need a seed number to base increments on. All considered, the worksheet function looks easier to implement.
I am trying to write a simple formula to count how many times a particular name appears in a column. I am using COUNTIF as it is a pretty straight forward process but I cannot work out how to make it happen for a name in particular. This is the case:
The column named Age will display cells with one or more names, separated by commas in case there are more than one value. Putting "Young" as an example is easy to tell the COUNTIF formula to give me the number representing how many times this word appears, either being the only value in cell or as a part of a cell with a longer string value by giving the formula the "Young" statement.
The problem comes when I want the formula to count how many times "Mature" appears in my column. I cannot work out the way to make it count only when it says "Mature" without also taking all the "Early_Mature" or "Semi_Mature"
I know this is easy for whoever knows the basics of Excel so I don't think there is need to give more details.
Thanks
Most of the times I succeed solving such problems by adding the same delimiter (of our string) at the beginning and end of the main string.
So since your data is at COL:Y, you may create a new helper COL:Z and enter this formula:
="," & Y1 & ","
I did not use any spaces before or after comma since your data seems not having any space. Depending on your case, you may have to use spaces.
Now your string is wrapped with commas, which you may alter COUNTIF formula to such:
=COUNTIF(Z:Z,"*,"&B1&",*")
* characters are jokers which stand for "anything" in this context.
With an UDF. Code goes in a standard module added by opening the VBE with Alt + F11 then right-click in project explorer and add module.
Code
Option Explicit
Public Function GetCount(ByRef selectRange As Range, ByVal searchTerm As String) As Long
Application.Volatile
With selectRange
Dim arr(), joinedString As String, i As Long, outputCount As Long
arr = .Value
joinedString = Join(Application.WorksheetFunction.Transpose(Application.WorksheetFunction.Index(arr, 0, 1)), ",")
Dim arr2() As String
arr2 = Split(joinedString, ",")
For i = LBound(arr2) To UBound(arr2)
If Trim$(arr2(i)) = "Mature" Then
outputCount = outputCount + 1
End If
Next i
End With
GetCount = outputCount
End Function
Usage in sheet
To get the number of occurrences of Mature excluding those that have prefix you can use this array formula:
=SUM(((LEN(A2:A7)-LEN(SUBSTITUTE(A2:A7,"Mature",""))) / LEN("Mature"))-((LEN(A2:A7)-LEN(SUBSTITUTE(A2:A7,"_Mature",""))) / LEN("_Mature")))
Please take note that this formula is applied with Ctrl + Shift + Enter.
Given that your range is in Y:Y column, just change the range to one you need.
An alternative would be to change "Mature" to "Fully_Mature". Then you could just use Countif().
You'd have to do this in steps:
1) Change "Early_Mature" to "E_M"
2) Change "Semi_Mature" to "S_M"
3) Change "Mature" to "Fully_Mature"
4) reverse of step 1).
5) reverse of step 2).
I have a simple array formula in excel that doesn't work in the way I wish. In columns A and B there is data (A1 is paired with B1 and so on) while in column F there is the calculation based on the parameter in column E.
In cell F1 the formula is:
{=SUM(MAX(A$1:A$9, E1)*B$1:B$9)}
What this formula does is:
=MAX(A$1:A$9, E1)*B$1 + MAX(A$1:A$9, E1)*B$2 + ...
Instead, I need a formula that does this:
=MAX(A$1, E1)*B$1 + MAX(A$2, E1)*B$2 + ...
In words, the formula I wrote (the first one) always finds the max between the values from A1 to A9 and E1, multiplies it by the i-th B value and sums the results. What I need is a formula that finds the max between the i-th A value and E1, and not between all the A values.
What I'm looking for is easily done by adding in column C the formula =MAX(A1;E$1)*B1 and then in F1 just =SUM(A1:A9), but I can't use this solution because in column F the same formula is repeated, with the E parameter changing every time.
I can use a IF instruction: in F1 I can write
{=SUM(IF(A$1:A$9>E1, A$1:A$9, E1)*B$1:B$9)}
While this formula does what I need in this case, I think it's a bad solution because I find it difficult to read and to expand. For example, if there is another parameter in column D and the factor is MIN(MAX(A$1:A$9;E1);D1), using IF will result in a very long and very unreadable and complicated formula.
Are there better solutions to my problem? Thank you all!
NOTE: syntax may vary a little because I am using the italian version of excel.
The problem is that MAX takes an array as an argument. Functions that normally take an array never return an array - they were designed to turn an array into one number. No matter how many arrays you throw at MAX, it's always just going to return one number.
I couldn't come up with a good solution, so here's a bad one
=SUMPRODUCT(((A1:A9*(A1:A9>E1))+(E1*(A1:A9<=E1)))*B1:B9)
I don't think that really increases the maintainability of the IF-based formula that you're trying to avoid. I think you're stuck with IF or a helper column.
Another possibility is a VBA function.
Public Function SumMaxMin(rRng1 As Range, rRng2 As Range, ParamArray vaMinMax() As Variant) As Double
Dim rCell As Range
Dim dReturn As Double
Dim aMult() As Double
Dim lCnt As Long
Dim i As Long
ReDim aMult(1 To rRng1.Cells.Count)
For Each rCell In rRng1.Cells
lCnt = lCnt + 1
aMult(lCnt) = rCell.Value
For i = LBound(vaMinMax) To UBound(vaMinMax) Step 2
If Not Evaluate(aMult(lCnt) & vaMinMax(i + 1) & vaMinMax(i)) Then
aMult(lCnt) = vaMinMax(i)
End If
Next i
Next rCell
For i = LBound(aMult) To UBound(aMult)
dReturn = dReturn + aMult(i) * rRng2.Cells(i).Value
Next i
SumMaxMin = dReturn
End Function
For your example
=SumMaxMin(A1:A9,B1:B9,E1,">")
Adding another condition
=SumMaxMin(A1:A9,B1:B9,E1,">",D1,"<")
It will error if your ranges aren't the same number of cells or you pass arguments that don't work with Evaluate.
Another possibility for avoiding repetitions of cell references is:
=SUM(B1:B9*ABS(A1:A9-E1*{1,-1}))/2
assuming values are non-negative. More generally to return an array of pairwise max values:
=MMULT((A1:A9-E1*{1,-1})^{2,1}^{0.5,1},{1;1}/2)
which returns:
MAX(A1,E1)
MAX(A2,E1)
...
MAX(A9,E1)
I don't remember ever cracking this problem, but for maintainability I'd probably do something like this:
{=SUM((A1:A9<E1)*E1*B$1:B$9) + SUM((A1:A9>=E1)*A1:A9*B$1:B$9)}
If I understand the problem correctly, using IF instead of MAX should do:
=SUM(IF($A$1:$A$9>E1;$A$1:$A$9;E1)*$B$1:$B$9)
When calculating series in Excel, most tutorials begin by setting sequence values to certain range of cells, say
A1=1, A2=2, A3=3,..., A10=10
and to get the value of 1+2+...+10, execute
A11=SUM(A1:A10)
But I don't want the "generate the sequence in worksheet cells first" part because initially I don't know the 'n' (10 in the above) and I want to define a custom function that takes n as a function argument.
So, is there a way to do something like this?
B1 = SUM([1:10]) // adding array of 'constants', not cell reference
EDIT: If I could 'summon' some (array of) big number(s) without any cell/ROW/COL operation as in calling rand(), that would be great.
Try using Array Formula as below
=SUM(ROW(A1:A10)) and then press CTRL+SHIFT+ENTER
Row(A1:A10) will become {1,2,3,4,5,6,7,8,9,10}.
Usage:
If you want to sum cells A20 to A50
sumjeff("A", 20,50)
Code
Function sumJeff(letter As String, nFrom As Integer, nTo As Integer) As Double
Dim strAddress As String
strAddress = letter & nFrom & ":" & letter & nTo
sumJeff = Application.WorksheetFunction.Sum(Range(strAddress))
End Function
I am trying to perform the following in one steps (one formula):
Strip a letter from a column of elements and add them up.
Example:
Data:
1x
2y
3x
I want to strip letters and add up numbers all in one formula.
I understand that I could have a helper column in which I strip letters x,y,z and then have a formula to add up the numbers, but I don't want to do this.
Thanks for any suggestions.
Assuming one entry per cell:
Is there only one letter at the end? If so, you can use:
=SUMPRODUCT(--LEFT(A1:A100,LEN(A1:A100)-1))
If there might be multiple letters at the end, a simple UDF would be simpler:
Option Explicit
Function AddStrings(rg As Range)
Dim L As Long
Dim I As Long
For I = 1 To rg.Count
L = L + Val(rg(I))
Next I
AddStrings = L
End Function
EDIT: If some of the cells might be blank, you can use either the UDF, or, if you prefer the formula, this array-entered formula:
=SUM(IFERROR(--LEFT(A1:A100,LEN(A1:A100)-1),0))
To array-enter a formula, after entering
the formula into the cell or formula bar, hold down
ctrl-shift while hitting enter. If you did this
correctly, Excel will place braces {...} around the formula.
Assuming that the format is consistent, you can do something like
=VALUE(LEFT(A1,1))+VALUE(MID(A1,4,1))+VALUE(MID(A1,7,1))
If the format is not consistent, things get more difficult. Let me know and I will expand the answer.
EDIT:
This function works with a variable length text, assuming that the fields are separated by the spaces and have one letter after the number:
Function AddValues(Text As String)
Dim Tokens() As String, I As Integer
Tokens = Split(Text)
For I = 0 To UBound(Tokens)
AddValues = AddValues + Val(Left(Tokens(I), Len(Tokens(I)) - 1))
Next I
End Function