Reputation: 75
I'm trying to write a function in VBA that will count the cells that contain certain letters in a specific range. My code so far is
Function letters(list As range) As Double
Dim limit As Integer
limit = UBound(list.Value)
Dim total As Double
total = 0
Dim i As Integer
For i = 1 To limit
If range(list).Offset(i, 2) = "PD" Then
total = total + 1
End If
Next i
letters = total
End Function
So my input is =letters(A1:B6) and lets say B2, B3, and B5 have the text "PD". I expect my answer to be 3 but I get a #value! error. Where am I going wrong?
Thanks in advance.
Upvotes: 0
Views: 1677
Reputation: 29421
to answer your question
Where am I going wrong?
there are some points to be addressed:
you say you call your function with
=letters(A1:B6)
but A1:B6
doesn't mean anything, while you have two options:
either you call your function passing a literal string
as a valid Range
address
=letters("A1:B6")
or you call it passing a Range
object directly
=letters(Range("A1:B6"))
once you're done with the previous point you must act consistently, i.e.:
option 1: passing a literal string
as a valid Range
address
then "A1:B6"
(notice the double quotes surrounding A2:B6
) is the address of your range so you have to:
change:
limit = UBound(list.Value) '<--| error
a string
doesn't have a Value
property
to:
limit = UBound(Range(list).Value) '<--| OK
Range("A1:B6")
is a valid Range
reference so you can access its Value
property
change:
Range(list).Offset(i, 2) '<--| this references a range as large as the one it's being called upon!
Offset(nRows, nColumns)
method will reference a range that's "shifted" from the one it's being called upon by as many rows and columns as you put in its parameters. So Offset(i,2)
called upon Range("A1:B6")
references Range("C2:D7")
when i
=1, Range("C3:D8")
when i
=2 and so on
to:
Range(list).Cells(i, 2) '<--| it references a single cell
Cells(iRow, iColumn)
method will reference the cell in the iRow
th row and iColumn
th column of the Range
it's being called upon. So Range("A1:B6").Cells(1,2)
references cell "B7"
option 2: passing a Range
object
since list
is already a Range
object (namely Range("A1:B6")
), you have to:
change:
If Range(list).Offset(i, 2) = "PD" Then '<--| error
you cannot pass a Range
object as the only argument of another Range
object
to:
If list.Cells(i, 2) = "PD" Then '<--| Ok
where you use Cells
property of Range
object as already discussed in option 1
then you may want to consider the following miscellanea:
declare your Function
as of Long
type
since:
you're returning an integer value, so there's no need to use a Double
type
the number of row can exceed some 65 k limit of an Integer
type variable
declare Dim limit As Long
since with
limit = UBound(Range(list).Value) '<--| correct use of 'list' if it's a literal 'string'
you're assigning it the actual number of rows of a Range and it could exceed the some 65k limit of an Integer
type variable
all that said, I think the most effective VBA solution would be using WorksheetFunction.CountIF() function as follows:
Function letters(list As String) As Long
letters = WorksheetFunction.CountIf(Range(list).Columns(2), "PD")
End Sub
where you'd use .Columns(n)
property of Range
object to reference the nth column of the Range
it's being called upon
and which you could make more general by expanding its arguments list to the searched string
Function letters(list As String, myLetters As String) As Long
letters = WorksheetFunction.CountIf(Range(list).Columns(2), myLetters)
End Sub
that you'd call like:
MsgBox letters("A1:B6", "PD")
Upvotes: 1
Reputation: 2066
If you want, you can achieve this just by using the below formula instead of using VBA.
=SUMPRODUCT(--(IFERROR(FIND("PD",A1:A25,1)>0,FALSE)))
You need to enter this as an array Formula i.e. Enter the formula and press Ctrl Shift Enter
FIND("PD",A1:A25,1)
gives you the text position or an error if text is not found for each cell.
>0
converts the number to TRUE. If it an error IFERROR
converts it to FALSE. Hence you get an array of TRUEs
and FALSEs
like {TRUE,FALSE,...}
The --({...})
converts the array to 1s and 0s. 1 for TRUE
and 0 for FALSE
. SUMPRODUCT
adds this numeric array to give you the count.
Upvotes: 0
Reputation: 2066
You can loop through each cell in the range and use INSTR to find if the cell contains the text. Increment a counter if it does and then output it.
Function findText(myRange As Range, toFind As String)
Dim ra As Range
Dim rCount As Integer
rCount = 0
For i = 1 To myRange.Rows.Count
If InStr(1, myRange(i, 1), toFind, vbTextCompare) Then
rCount = rCount + 1
End If
Next i
findText = rCount
End Function
This will count event those cells which have the required text anywhere in the cell. If you want to match the whole cell contents, just use
If myRange(i, 1) = toFind Then
Also, the function works for a range in single column.
Upvotes: 1