So I need to count how many ž and č are there in all of these fields.
Example.
http://prntscr.com/jwz1em
I tryed with this code but it gives me 0
Function slova(iVal)
Dim output As Integer
output = Application.WorksheetFunction.CountIf(Range("A2:A11"), "ž")
End Function
I see multiple problems with your code:
There is no assignment of return value to function, in my example slova = charCnt, so it wouldn't return anything besides default 0 no matter what.
It lacks Application.Volatile, so the formula used in Excel cell would require navigating to cell and pressing ENTER to force an update when data in range changes.
Function has an argument iVal which isn't used anywhere.
Application.WorksheetFunction.CountIf returns count of cells, so it is limited to 1 character per cell. On top of it, correctly specified argument would be "*ž*"
Here is my solution to count all occurrences of hardcoded character in hardcoded range (must have exactly 1 column).
Function slova() As Long
Application.Volatile
Dim vData As Variant
Dim rowCounter As Long, charCnt As Long
Const myLetter As String = "ž"
vData = Range("A2:A11")
For rowCounter = LBound(vData) To UBound(vData)
If vData(rowCounter, 1) <> vbNullString Then
charCnt = charCnt + UBound(Split(vData(rowCounter, 1), myLetter))
End If
Next rowCounter
slova = charCnt
End Function
As you use function, you can also take advantage of it and use source range as an argument, the same goes for character.
Related
I am trying to make a simple countRows function that will count the number of cells I have in a dynamic range. Basically if I have values in cells, say B2:B500, the count would return 499. However next time around values are in cell B2:B501, the count would return 500. But you wouldn't have to do anything to the cell in which you typed in the formula.
I thought if I reference the cell as a Variant, then any value could be accepted. Then find the Address of that cell and return the Count of a Range. But I get a #Value error.
Public Function countRows(startRange As Variant)
Dim rng As Range
Set rng = startRange.Address
If IsEmpty(Range(rng, rng.End(xlDown))) = True Then
countRows = 1
Else
countRows = Range(rng, rng.End(xlDown)).Rows.Count
End If
End Function
This is the code I have used for many years successfully under many different worksheets. It handles many cells, singular cells or empty cells.
Public Function CountRows(ByRef r As Range) As Long
If IsEmpty(r) Then
CountRows = 0
ElseIf IsEmpty(r.Offset(1, 0)) Then
CountRows = 1
Else
CountRows = r.Worksheet.Range(r, r.End(xlDown)).Rows.count
End If
End Function
Public Function CountCols(ByRef r As Range) As Long
If IsEmpty(r) Then
CountCols = 0
ElseIf IsEmpty(r.Offset(0, 1)) Then
CountCols = 1
Else
CountCols = r.Worksheet.Range(r, r.End(xlToRight)).Columns.count
End If
End Function
It's not entirely clear what you are looking for, when you mentioned there are values in cells "B2:B500" and the count should return 499, as there could be a few possible scenarios:
You simply want to count the rows in the range "B2:B500". The code will be:
Range("B2:B500").Rows.Count
You want to count the non-blank cells in the range "B2:B500". In that case, as suggested in the comments:
WorksheetFunction.CountA(Range("B2:B500"))
As indicated in your code rng.End(xlDown), you probably want to the count continuous non-blank cells starting with the range "B2" in the overall range "B2:B500". You may create a function like this:
Public Function countRows(rng As Range) As Long
Dim rw As Range
For Each rw In rng
If IsEmpty(rw) Then Exit For
countRows = countRows + 1
Next
End Function
Clarification:
Based on subsequent comments, I thought it's worth explaining why the variable "countRows" wasn't initialized by adding a line countRows = 0.
Certain programming languages like assembly language, C, C++ require explicit initialization. This was intentionally so designed due to the philosophy in which conflicts between performance and safety were generally resolved in favor of performance.
However, such is not the case with other programming languages like VBA or Java.
Speaking about VBA, during macro run, all the variables are initialized to a value. A numeric variable is initialized to zero, a variable length string is initialized to a zero-length string (""), and a fixed length string is filled with the ASCII code 0. Variant variables are initialized to Empty. An Empty variable is represented by a zero in a numeric context and a zero-length string ("") in a string context.
Therefore a separate line of code countRows = 0 wasn't added in the above code block.
While coding, one need to keep this in perspective as the same might not be true for other languages.
So I want to make a basic function that takes an average of values that I highlight in Excel. I am well aware there is already a built-in function in Excel for this but I am trying to make one as practice.
My problem is I am not sure how to pass a range and then call on specific elements in the Range.
Below is the pseudo code I've been playing around with. I understand it may be horribly written. I am a beginner and I just want to get some practice.
Function averagetest(range As Range) '<------(Is this how I pass a Range into a function?)
Dim N as Integer
Dim i as Integer
Dim average as Double
average = 0
N = LengthofRange '<--------- (Is there a way to get the length of the
range like UBound or LBound for an array?)
Do Until i = LengthofRange
average = average + Range(i, i+1) '<--------(Is this how you call a
specific element in the range? I'm just adding every element in the
Range)
i = i + 1
Loop
average = average/N
End Function
You can't assume a Range is going to be contiguous, nor can you assume a Range is going to be horizontal, nor vertical.
A Range is a collection of objects, so you iterate it with a For Each loop for optimal performance.
Assuming the function is meant to be used as a UDF worksheet function, and therefore is defined in a standard module (.bas):
Public Function AverageTest(ByVal target As Range) As Variant
Dim total As Double
Dim count As Double
Dim cell As Range
For Each cell In target
If IsNumeric(cell.Value) Then
total = total + cell.Value
count = count + 1
'Else
' AverageTest = CVErr(xlErrValue)
' Exit Function
End If
Next
If count = 0 Then
AverageTest = CVErr(xlErrDiv0)
Else
AverageTest = total / count
End If
End Function
Note:
Parameter is passed ByVal, and isn't named after an existing type (Range); we don't need a reference to the range pointer, a copy of it is good enough.
Function is explicitly Public, and has an explicit return type (Variant).
Function returns a Variant, so as to return a Double result in the "happy path", or an appropriate Error value (#Div/0!) when applicable.
Function is only counting numeric cells, which means it works even if the target range contains error values. The commented-out code would bail out and return a #VALUE! error if a non-numeric value is encountered.
How you "pass the range" is the caller's problem. There are many ways you can do this - from an Excel formula:
=AverageTest(A1:A10)
=AverageTest(A1:B12,F4:L4)
You can also use it in VBA code:
foo = Module1.AverageTest(ActiveSheet.Range("A1:D10"))
Do not use range as a variable.
Then you can use rows.Count or Columns.Count to get the extent
Function averagetest(rng As Range)
Dim N as Integer
Dim i as Integer
Dim average as Double
average = 0
N = rng.rows.count
For i = 1 to N 'use For loop
average = average + rng.cells(i,1)'Cells will work here
Next i
averagetest= average/N
End Function
Or you can do this -- there's not really any need to iterate over the count of cells, when you can just iterate over Each cell in the rng.Cells collection. I would also change the variable name from average (which is misleading) to something a bit more descriptive, like total:
Option Explicit
Function averagetest(rng As Range)
Dim cl As Range
Dim total As Double
For Each cl In rng.Cells
total = total + cl.Value
Next
averagetest = total / rng.Cells.Count
End Function
As a bonus, this latter method would work on a 2-dimensional range as well.
Note that this will treat empty cells as 0-values (the AVERAGE worksheet function ignores empty cells, so your results may vary) and it will raise an error if there are non-numeric values in the range.
I am writing a VBA formula to check that all characters in a cell "TestChars" are allowed, where allowed means that each character appears in a list defined by another cell "AllowedChars". To make things even harder, I would like this formula to work on ranges of cells rather than on a single cell.
The current code seems to work:
Option Explicit
Public Function AllCharsValid(InputCells As Range, AllowedChars As String) As Boolean
' Check that all characters in InputCells are among
' the characters in AllowedChars
Dim Char As String
Dim Index As Integer
Dim RangeTestChars As Range
Dim TestChars As String
For Each RangeTestChars In InputCells
TestChars = RangeTestChars.Value
For Index = 1 To Len(TestChars)
Char = Mid(TestChars, Index, 1)
If InStr(AllowedChars, Char) = 0 Then
AllCharsValid = False
Exit Function
End If
Next Index
Next RangeTestChars
AllCharsValid = True
End Function
I have the following questions:
The formula takes a range and returns a single boolean. I would prefer a vectorized function, where, given an input range, you get a corresponding range of booleans. It seems like built-in formulas like 'EXACT' can do this (those formulas where you have to press ctrl-shift-enter to execute them and where you get curly-brackets). Is there a way to do that with user-defined functions?
I am not new to programming, however I am completely new to VBA (I started literally today). Is there any obvious problem, weirdness with the above code?
Are there special characters, extremely long texts or particular input values that would cause the formula to fail?
Is there an easier way to achieve the same effect? Is the code slow?
when you start typing built-in formulas in excel you get suggestions and auto-completion. This doesn't seem to work with my formula, am I asking for too much or is it possible to achieve this?
I realize that this question contains several weakly related sub-questions, so I would be very happy also with sub-answers.
The following code will return a range of boolean values offset one column from the initial input range. Simply create a new tab in Excel and run testAllCharsValid and show the Immediate window in the IDE to see how it works.
Sub testAllCharsValid()
Dim i As Integer
Dim cll As Range, rng As Range
Dim allowedChars As String
' insert test values in sheet: for testing purposes only
With ActiveSheet ' change to Thisworkbook.Sheets("NameOfYourSheet")
Set rng = .Range("A1:A10")
For i = 1 To 10
.Cells(i, 1) = Chr(i + 92)
Next i
End With
' fill allowedChars with letters a to z: for testing purposes only
For i = 97 To 122
allowedChars = allowedChars & Chr(i)
Next i
' get boolean range
Set rng = AllCharsValid(rng, allowedChars)
' check if the returned range contains the expected boolean values
i = 0
For Each cll In rng
i = i + 1
Debug.Print i & " boolean value: " & cll.Value
Next cll
End Sub
' Check that all characters in InputCells are among
' the characters in AllowedChars
Public Function AllCharsValid(InputCells As Range, allowedChars As String) As Range
Dim BoolTest As Boolean
Dim Char As String
Dim Index As Integer
Dim RangeTestChars As Range, RangeBooleans As Range, RangeTemp As Range
Dim TestChars As String
For Each RangeTestChars In InputCells
BoolTest = True
TestChars = RangeTestChars.Value
For Index = 1 To Len(TestChars)
Char = Mid(TestChars, Index, 1)
If InStr(allowedChars, Char) = 0 Then BoolTest = False
Next Index
Set RangeTemp = RangeTestChars.Offset(0, 1) ' change offset to what suits your purpose
RangeTemp.Value = BoolTest
If RangeBooleans Is Nothing Then
Set RangeBooleans = RangeTestChars
Else
Set RangeBooleans = Union(RangeBooleans, RangeTemp)
End If
Next RangeTestChars
Set AllCharsValid = RangeBooleans
End Function
cf 2) If the length of the test string is zero, the function will return True for the cell in question, which may not be desirable.
cf 3) There is a limit to how many characters an Excel cell can contain, read more here. I suppose, if you concatenated some very long strings and sent them to the function, you could reach the integer limit of +32767, which would cause a run-time error due to the integer Index variable. However, since the character limit of Excel cells is exactly +32767, the function should work as is without any problems.
cf 4) None that I know of.
cf 5) This is not the easiest thing to achieve, but there is help to be found here.
I am trying to add the data in the two cells of the excel sheet but even if the excel cell is of the type number it does not add up the cells. It seems that there is space infornt of the number that it does not add....image is below.
Is there a vba code to remove this space from each of the cell if its presesnt.
I have exported the excel from a pdf.
Excel will attempt to convert any value to a number if you apply an operator to it, and this conversion will handle spaces. So you can use =A1*1 or A1+0 to convert a value in A1 to a number, or something like this within a function =SUM(IFERROR(A1*1,0)).
That kind of implicit conversion automatically performs a trim(). You can also do this conversion explicitly by using the funciton N(), or NumberValue() for newer versions of Excel. However, as others have pointed out, many characters won't be automatically handled and you may need to use Substitute() to remove them. For instance, Substitute(A1,160,"") for a non-breaking space, a prime suspect because of its prevalence in html. The Clean() function can give you a shortcut by doing this for a bunch of characters that are known to be problematic, but it's not comprehensive and you still need to add your own handling for a non-breaking space. You can find the ASCII code for any specific characters that are grieving you by using the Code() function... for instance Code(Mid(A1,1,1))
Character Handling UDF
The UDF below gives flexibility to the character handling approach by allowing multiple characters to be removed from every cell in a range, and produces a result that can be used as an argument. For example, Sum(RemoveChar(A1:A5,160)) would remove all non-breaking spaces from the range being summed. Multiple characters can removed by being specified in either a range or array, for example Sum(RemoveChar(A1:A5,B1:B3)) or Sum(RemoveChar(A1:A5,{160,150})).
Function RemoveChar(R As Range, ParamArray ChVal() As Variant)
Dim x As Variant
Dim ResVals() As Variant
ReDim ResVals(1 To R.Count)
'Loop through range
For j = 1 To R.Count
x = R(j).Value2
If x <> Empty Then
'Try treating character argument as array
'If that fails, then try treating as Range
On Error Resume Next
For i = 1 To UBound(ChVal(0))
x = Replace(x, Chr(ChVal(0)(i)), "")
Next
If Err = 92 Then
Err.Clear
For Each Rng In ChVal(0)
x = Replace(x, Chr(Rng.Value2), "")
Next
End If
Err.Raise (Err)
On Error GoTo 0
'If numeric then convert to number
'so that numbers will be treated as such
'when array is passed as an argument
If IsNumeric(x) Then
ResVals(j) = Val(x)
Else
ResVals(j) = x
End If
End If
Next
'Return array of type variant
RemoveChar = ResVals
End Function
Numeric Verifying UDF
The drawback with replacing characters is that it's not comprehensive. If you want something that's more of a catch-all, then perhaps something like this.
Function GetNumValues(R As Range)
Dim c, temp As String
Dim NumVals() As Double
ReDim NumVals(1 To R.Count)
'Loop through range
For j = 1 To R.Count
'Loop through characters
'Allow for initial short-circuit if already numeric
For i = 1 To Len(R(j).Value2)
c = Mid(R(j).Value2, i, 1)
'If character is valid for number then include in temp string
If IsNumeric(c) Or c = Application.DecimalSeparator Or c = Application.ThousandsSeparator Then
temp = temp + c
End If
Next
'Assign temp string to array of type double
'Use Val() function to convert string to number
NumVals(j) = Val(temp)
'Reset temp string
temp = Empty
Next
'Return array of type double
GetNumValues = NumVals
End Function
So I wrote a simple function in VBA and I want to use it in my excel workbook. I wrote the following code:
Option Explicit
Public Function KOLICINA(fiksnacena As Long, ceni() As Long, nedela() As Long) As Long
Dim brojac As Integer
For brojac = 1 To UBound(nedela)
If Not ((IsEmpty(nedela(brojac) Or nedela(brojac) = 0) And ceni(brojac) <> fiksnacena)) Then KOLICINA = nedela(brojac)
Next brojac
End Function
When I try to use it in a worksheet cell (using =KOLICINA(18;G22:G26;H22:H26))
, I get the #VALUE error.
I don't understand why. The function should go through nedela Array and if it finds a Non empty or different value than 0 AND if the matching cell in the ceni Array is different from the number fiksnacena, it should return the value of the cell in nedela.
You cannot simply pass a cell range reference into a UDF and have it interpreted as a single dimensioned array of longs.
Public Function KOLICINA(fiksnacena As Long, ceni As Range, nedela As Range) As Long
Dim brojac As Long, vCeni As Variant, vNedela As Variant
vCeni = ceni.Value2
vNedela = nedela.Value2
For brojac = LBound(vNedela, 1) To UBound(vNedela, 1)
If Not ((IsEmpty(vNedela(brojac, 1) Or vNedela(brojac, 1) = 0) And vCeni(brojac, 1) <> fiksnacena)) Then
KOLICINA = vNedela(brojac, 1)
Exit For
End If
Next brojac
End Function
When you dump values from a range reference into an array, you always end up with a two dimensioned array; in your example it is 1 to 5, 1 to 1.
To further illustrate this point, your original UDF code would work if you pulled the values from the ranges after transposing them and finish off the UDF with CSE so that the values are processed as an array.
=KOLICINA(18, VALUE(TRANSPOSE(G22:G26)), VALUE(TRANSPOSE(H22:H26)))
Finalize with [ctrl]+[shift]+[enter].