This is probably very basic for most here, I have no background and just need some help. I have to write an isDup function in VBA to compare two tweets based on their similar word counts to determine if the tweets are duplicate or not, based on a decimal threshold chosen (0-1). If they are duplicates, the function will return true. Every time i call the function it returns a #NAME in the cell instead of a true/false, and was wondering if anyone can give me some tips or let me know what i am doing wrong. Thank you!
Option Explicit
Function isDup(tweet1 As String, tweet2 As String, threshold As Double) As Boolean
Set threshold = 0 - 1
Dim tweet1Split() As String
tweet1Split = Split(tweet1, " ")
Dim tweet2Split() As String
tweet2Split = Split(tweet2, " ")
Dim i As Integer
Dim j As Integer
Dim sameCount As Integer
For i = LBound(tweet1Split) To UBound(tweet1Split) Step 1
For j = LBound(tweet2Split) To UBound(tweet2Split) Step 1
If StrComp(tweet1Split(i), tweet2Split(j), vbTextCompare) = 0 Then
sameCount = sameCount + 1
Exit For
End If
Next j
Next i
Dim score As Double
Dim arraySize As Double
arraySize = UBound(tweet1Split) - LBound(tweet1Split) + 1
score = sameCount / arraySize
If score > threshold Then
isDup = True
Else
isDup = False
End If
End Function
Try this. Place the following in a standard code module...
Public Function isDup(tweet1$, tweet2$, threshold#) As Boolean
Dim c&, i&, j&, t1, t2
t1 = Split(tweet1, " ")
t2 = Split(tweet1, " ")
For i = 0 To UBound(t1)
For j = 0 To UBound(t2)
If Not StrComp(t1(i), t2(j), vbTextCompare) Then c = c + 1
Next
Next
isDup = (c / (UBound(t2) + 1)) > threshold
End Function
And then use it from a worksheet:
=isDup(A1,A2,0.5)
...assuming the text of two tweets to compare is in A1 and A2.
If you're getting the #NAME error when trying to use a User-Defined Function in a spreadsheet, it's because Excel doesn't recognise the function. Make sure the code for the function is going in a Module in the workbook where you are trying to use the function, as per the image below.
The only change you need to make to the code for the function to run is to delete the line:
Set threshold = 0 - 1
You can then test to ensure it's giving the desired result.
The function isn't available if you just place it in a Sheet or ThisWorkbook object. You need to insert a Module and place the code there.
It also isn't available to other workbooks, unless you add references in those workbooks or save it to an add-in which is then correctly installed. You can look or ask for help on those processes separately if needed.
Related
Currently messing around with macros in excel. One that generates a range that fills 3 columns of cells with 1-9.
Another that plots these numbers kind of like battle ship with x,y and v for the value.
I've gotten the number generation part working and I'm stuck on the plotting bit.
Currently the issue it that I'm getting an error "13" which means that my variables don't match up.
But i'm using a CInt to convert the variant to a int.
Debugging it seems like the for loop is getting all the values properly but just not converting.
Here is the code I have so far and a screenshot of the whole thing.
Sub random()
Dim MyRange As Range
Dim c As Integer, r As Integer
Set MyRange = Workbooks("test random gen").Sheets("Sheet1").Range("G16:I30")
For c = 1 To MyRange.Columns.Count
For r = 1 To MyRange.Rows.Count
Randomize
MyRange.Cells(r, c) = Int((9 - 1 + 1) * Rnd + 1)
Next r
Next c
End Sub
Sub Button6_Click()
Dim Board As Range
Dim Table As Range
Dim c As Integer, r As Integer
Dim Xboard As Integer, Yboard As Integer, Vboard As Integer
Dim Xboardv As Variant, Yboardv As Variant, Vboardv As Variant
Set Table = Workbooks("test random gen").Sheets("Sheet1").Range("G16:G30")
Set Board = Workbooks("test random gen").Sheets("Sheet1").Range("M16:U24")
For r = 1 To Table.Rows.Count
Xboardv = Table.Cells.Value
Yboardv = Table.Cells.Offset(columnOffset:=1).Value
Vboardv = Table.Cells.Offset(columnOffset:=2).Value
Xboard = CInt(Xboardv)
Yboard = CInt(Yboardv)
Vboard = CInt(Vboardv)
Board.Cells(Xboard, Yboard).Value = (Vboard)
Next r
End Sub
Hm, oke I'll recap what I mentioned in my comments above:
"It is getting stuck on the Xboard = CInt(Xboardv) line with the error "13""
You are creating an array of values with Xboardv = Table.Cells.Value. The array is sized 1 To 15, 1 To 1 and you need to use these index numbers as row and column parameters when you refer to any element in the array. So basically: Xboard = CInt(Xboardv(<X>,<Y>)).
"That did it but now it only does it for the first value of the array due to it being called out as (1,1)"
That is because you are constantly refering to the same element. However, you have created a loop with r variable allready. So you can use that to call different elements: Xboard = CInt(Xboardv(r,1))
I've neglected the fact that your structure is somewhat strange and you are creating the same array in a loop. So move that outside your loop and possibly use a For R = Lbound(Xboard) to Ubound(Xboard) loop instead. And you can just address values without having to convert them too.
Btw, no need for Integer variables at all. Use Long instead.
What's the reason for doing a conversion to begin with?
In cell 'D4', I've put the value 325, and I've run following piece of code:
Dim b As Integer
b = Range("D4").Value
Value b is 325, no problem. No conversion needed.
In form controls we can use { controls("Textbox"&1) } as for loops.
My question is I have already defined the String as D1,D2,D3. I want to use like D as common and suffix as variable
sub abcd ()
dim i, k as integer
dim D1 as string
dim D2 as string
k="abcd1"
for i = 1 to 2
if k<> "" then 'like controls("textbox" & i ) for loop
"D"&i = true
Else "D" & i+1
end sub
It shows a messagebox with the error:
expected : line number or label or statement or end of statement
This code has several (actually a lot) of issues:
Dim i, k As Integer declares k As Integer but i As Variant in VBA you need to specify a type for every variable.
You declare D1 as String but True is a Boolean.
If you declare Dim D1 As String you cannot access the variabele with "D" & i = True. This is no valid syntax. Therefore you would need to use an array:
Dim MyArray(1 To 2) As Boolean
So you can access it like
MyArray(i) = True 'where i can be 1 or 2
If you declare k As Integer that means k repersents a number (without decimals). So you cannot put text in there k = "abcd1", this will produce a type missmatch. Also comparing k against a string (text) "" like If k <> "" Then will missmatch since k is a number.
Also have a look at data type summary to study which data type to use for which kind of data.
Your For i = 1 To 2 loop is missing a Next i in the end of the loop.
Your If statement is missing a End If in the end.
So in general I can only recommend you to study some tutorials or books about the basic syntax of VBA, you are lacking a lot of basic things.
So the following example might help you:
Option Explicit
Public Sub Test()
Dim i As Long
Dim MyArray(1 To 3) As Boolean
For i = 1 To 3
MyArray(i) = True
Next i
End Sub
I am having trouble getting Variants to subtract. I am pulling data from a spreadsheet and if one cell states a phrase then I need the code to subtract one cell from another. If the cell does not state a phrase then I need it to copy one cell to another. I can get the code to run but nothing happens.
Private Sub CommandButton1_Click()
Dim x As Variant, y As Variant, z As Variant, a As Integer, B As String
'getting values for data
x = Range("D2:D48").Value
y = Range("I2:I48").Value
z = Range("E2:E48").Value
B = "Total ISU Days: "
'The the cells are empty then subtract. This is not what I wanted to do but I can't think of extracting strings from variants.
If IsEmpty(Range("D2:D48").Value) = True Then
a = y - z
End If
Range("N2:N48").Value = a
Range("M2:M48").Value = B
End Sub
x = Range("D2:D48").Value
y = Range("I2:I48").Value
z = Range("E2:E48").Value
A Variant contains metadata about its subtype. In this case, x, y, and z are all arrays of variants.
a = y - z
The right-hand side of this expression simply cannot be evaluated, because {array1} - {array2} means nothing: operators (arithmetic or logical) work off values, not array of values.
What is a supposed to be? It's declared As Integer, so its value is capped at 32,767 (should probably be a Long). If you mean to add up all the values in y and subtract that total from the sum of all values in z, then you need to be more explicit about how you do that - you could use Application[.WorksheetFunction].Sum to add things up:
sumOfY = Application.Sum(Range("I2:I48"))
sumOfZ = Application.Sum(Range("E2:E48"))
a = sumOfY - sumOfZ
And then...
Range("N2:N48").Value = a
That will put the value of a in every single cell in the N2:N48 range - is that really what you mean to do?
Or maybe you meant to do this instead?
Range("N2:N48").Formula = "=IF(D2="""",I2-E2,0)"
That would make each cell in N2:N48 calculate the difference between I and E for each row where D is empty... and there's not really any need for any VBA code to do this.
Let's simplify a bit the task and say that the idea is to substract the values in Range("C1:C6") from the corresponding values in the left - Range("B1:B6"). Then write the corresponding results in column E:
Of course, this would be done only in case that all values in column A are empty. This is one way to do it:
Sub TestMe()
Dim checkNotEmpty As Boolean: checkNotEmpty = False
Dim substractFrom As Range: Set substractFrom = Worksheets(1).Range("B1:B6")
Dim substractTo As Range: Set substractTo = Worksheets(1).Range("C1:C6")
Dim MyCell As Range
Dim result() As Variant
ReDim result(substractFrom.Cells.Count - 1)
Dim areCellsEmpty As Boolean
For Each MyCell In substractFrom
If Len(MyCell) > 0 Then checkNotEmpty = True
Next
Dim i As Long
For i = LBound(result) + 1 To UBound(result) + 1
result(i - 1) = substractFrom.Cells(i) - substractTo.Cells(i)
Next
Worksheets(1).Range("E1").Resize(UBound(result) + 1) = Application.Transpose(result)
End Sub
The code could be improved further, saving all ranges to an Array, but it works quite ok so far.
The part with the +1 and -1 in the For-loop is needed as a workaround:
For i = LBound(result) + 1 To UBound(result) + 1
result(i - 1) = substractFrom.Cells(i) - substractTo.Cells(i)
Next
because the arrays start from index 0, but the Cells in a range start with row 1.
Worksheets(1).Range("E1").Resize(UBound(result) + 1) = Application.Transpose(result) is needed, to write the values of the result array to the column E, without defining the length of the range in E.
I have a for loop, and inside it i have if statement.
In my Excel I have a list that contains each value one time. Once I found it i don't want the code to even check the conditional, i want it to skip this part of the if statement completely each time the loop is executed, is it possible?
Here is my code and list:
the first iteration of the loop will find that "c" is the value so it will do what inside it (xc = i)
I don't want the code to even check "ElseIf Cells(1, i) = "c" again, like the following image, is this possible?
code as text:
Sub test()
Dim i, xa, xb, xc As Integer
For i = 1 To 5
If Cells(i, 1) = "a" Then
xa = i
ElseIf Cells(i, 1) = "b" Then
xb = i
ElseIf Cells(i, 1) = "c" Then
xc = i
End If
Next i
End Sub
My initial interpretation of your need was "if the code hits 'c' again, just don't act".
To do so, you could modify the logic as follows:
ElseIf (xc = 0) And (Cells(i, 1) = "c") Then
This way, as soon as xc is set, the first boolean expression would be False, and the overall condition would not ever be met again. As mentioned by #TimWilliams, VBA would still evaluate the second boolean expression, unlike other languages that feature short-circuiting options. #Gene's answer describes a way around this. Typically, for better performance, you would evaluate the simple conditions first, before resorting to costly ones.
Additional notes
In VBA, you must give a type to each variable. In your Dim line, only xc is an Integer, while the other variables are Variants.
An unqualified Cells() call operates on the currently active worksheet, which might not be the expected one. Suggestion: qualify Cells() with the CodeName of your worksheet. The CodeName is what you see or specify under a worksheet's (Name) property as seen from the Visual Basic editor. For example, if (Name) is Sheet1, use Sheet1.Cells(). This will only work if the code resides in the same workbook as Sheet1. If the code is behind the worksheet itself, you can even use Me.Cells().
When dealing with cell values as your code does, VBA is (silently) being nice and understands that, among the numerous properties of the Range class, Value is what you are interested in. It is better practice, however, to explicitly state the target property, such as in Sheet1.Cells(i, j).Value.
EDIT
Knowing the values will be distinct and that there are about 60 of them, I suggest you simply use a Dictionary, as shown below, to get each value's row in one go, without a cascade of Ifs:
Option Explicit
Sub test()
Dim i As Integer
Dim dict As Object 'Scripting.Dictionary
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 To 5
dict(Cells(i, 1).Value) = i
Next
Debug.Print dict("a") '4
Debug.Print dict("b") '2
Debug.Print dict("c") '1
'Etc.
End Sub
if i understood your question you can try this code:
Sub test()
Dim i, xa, xb, xc As Integer
Dim a, b, c As Boolean
a = False
b = False
c = False
For i = 1 To 5
If Cells(i, 1) = "a" And a <> True Then
xa = i
a = True
ElseIf Cells(i, 1) = "b" And b <> True Then
xb = i
b = True
ElseIf Cells(i, 1) = "c" And c <> True Then
xc = 1
c = True
End If
Next i
End Sub
Boolean variable is setted true for example only when the cells(i,1)="a" and after the next "a" value are skipped...
hope this helps
I just wanted to "mod" Ferdinando's code so it's a bit more "readable", I think. The main (the substantive) difference between this version and Ferdinando's or Excelosaurus' is that the cell is not even tested once the value is detected. Remember that the question was: I don't want the code to even check "ElseIf Cells(1, i) = "c" again... So, this version does exactly that.
Sub test()
Dim i As Integer, xa As Integer, xb As Integer, xc As Integer
Dim aFound As Boolean, bFound As Boolean, cFound As Boolean
Dim r As Range
For i = 1 To 5
Set r = Cells(i, 1)
If Not aFound Then
If r = "a" Then xa = i: aFound = True
ElseIf Not bFound Then
If r = "b" Then xb = i: bFound = True
ElseIf Not cFound Then
If r = "c" Then xc = i: cFound = True
End If
Next i
End Sub
I don't like the idea of 60 ElseIfs. Please examine the code below. In order to test it, create a worksheet called "TestSheet" and enter your A1:A5 to cells H2:H6.
Sub TestSpike()
' 06 Jan 2019
Dim Rng As Range
Dim Items As Variant
Dim Spike As String
Dim Tmp As String
Dim i As Integer
Dim R As Long
Items = Split("c|b|0|a|1", "|")
With Worksheets("TestSheet").Columns("H")
For R = 2 To 6
Tmp = CStr(.Cells(R).Value)
If InStr(1, Spike, Tmp, vbTextCompare) = 0 Then
Spike = Spike & "|" & Tmp
On Error Resume Next
i = Application.WorksheetFunction.Match(Tmp, Items, 0)
If Err Then
MsgBox Tmp & " wasn't found in Array"
Else
MsgBox "i = " & i & " = Item " & Tmp
End If
End If
Next R
End With
End Sub
The code has a "Spike". Each item is first checked against the Spike. If it is found there no further tests are carried out. Else, it is added to the Spike.
New items, after being added to the Spike, are checked against the Array "Items" which would hold your 60 elements, separated by Chr(124) thus, Split("c|b|0|a|1", "|"). I use the worksheet function MATCH to look for the item in the array. The result is an index number (or an error, if not found). You can use this index number in a Select Case statement to process each item distinct from others, basically the same way as you now process it when the If statement returns True.
One idea you may find useful with this kind of setup is to use the index from the Match function to return a value from another array. The other array might, for example, contain function names and you use Application.Run to call a different function for each item. This would run significantly faster than examining 60-odd Select Case statements.
My user defined function always returns 'A value used in the formula is of the wrong data type' (I am passing cell ranges to the function). I've searched lots of threads and am quite sure my code should work:
Function SortAndEvaluate(ByRef Probs() As Variant, ByRef ResidualProbs() As Variant, ByRef Costs() As Variant)
Dim Temp As Double
Dim i As Integer
Dim NoExcanges As Integer
'Exchange values in probs in descending order
Do
NoExchanges = True
' Loop through each element
For i = 0 To UBound(Probs) - 1
'If element is greater than the last exchange, else do nothing
If Probs(i) > Probs(i + 1) Then
NoExchanges = False
' Exchange probability values
Temp = Probs(i)
Probs(i) = Probs(i + 1)
Probs(i + 1) = Temp
' Exchange residual probability values
Temp = ResidualProbs(i)
ResidualProbs(i) = ResidualProbs(i + 1)
ResidualProbs(i + 1) = Temp
' Exchange cost values
Temp = Costs(i)
Costs(i) = Costs(i + 1)
Costs(i + 1) = Temp
End If
Next i
Loop While Not (NoExchanges)
Temp = 0
For i = 1 To UBound(Probs)
If i = 1 Then
Temp = Temp + Probs(i) * Costs(i)
Else
Temp = Temp + Probs(i) * ResidualProbs(i - 1) * Costs(i)
End If
Next i
SortAndEvaluate = Temp
End Function
Can anyone give me any feedback please?
Try something like
Option Explicit
Function SortAndEvaluate(ByRef Probs As Range, ByRef ResidualProbs As Range, ByRef Costs As Range)
Dim NumberOfProbs As Long
Dim NumberOfProbsRows As Long
Dim NumberOfProbsColumns As Long
Dim RangeAddressProbs As String
' Dim for ResidualProbs and Costs
' ...
NumberOfProbs = Probs.Cells.Count
NumberOfProbsRows = Probs.Rows.Count
NumberOfProbsColumns = Probs.Columns.Count
RangeAddressProbs = Probs.Address
' also for ResidualProbs and Costs
' ...
' Might want some validation, e.g.
If NumberOfProbsColumns <> 1 Then Exit Function
If NumberOfResidualProbsColumns <> 1 Then Exit Function
' Get Values from range as an array
Dim ValuesProbs As Variant
Dim ValuesResidualProbs As Variant
Dim ValuesCosts As Variant
ValuesProbs = Probs
ValuesResidualProbs = ResidualProbs
ValuesCosts = Costs
' Do your logic stuff using the Values... arrays
' ...
' This example return value shows how to reference the Values... arrays (note you need to index the column too)
SortAndEvaluate = ValuesProbs(NumberOfProbsRows, 1)
End Function
You are not using Option Explicit and you also have a typographical error in your code for where you Dim NoExcanges As Integer.
When you later write NoExchanges = True this implicitly Dims a new type of Boolean with the reference NoExchanges — NoExcanges is never used.
You also might want to explicitly declre the return type of your function:
Function SortAndEvaluate(ByRef Probs() As Variant, ByRef ResidualProbs() As Variant, ByRef Costs() As Variant) As Double
The reason is that Excel VBA will not allow a user-defined function to alter the value of another cell. The only thing a UDF is allowed to do (with a few minor exceptions) is to return values to the cells it is called from.
The line Probs(i) = Probs(i + 1) is simply not allowed inside a Function.
See similar question
You can do this with a Sub but you would have to pass the cells to sort in another way (possibly using Selection).