When I compare the value of a cell that contains ? to a variable, it always returns true. Is there any way I can prevent this? Here is my current code:
'Option Explicit
Dim hws As Worksheet
Set hws = ActiveSheet
Dim rng As Range, rng2 As Range
Dim letters(2, 2)
alpha = Range("CipherTable").Value
For x = 1 To 7
For y = 1 To 7
If alpha(x, y) = rng.Cells(i, j + 1).Value Then
letters(2, 1) = x
letters(2, 2) = y
End If
Next y
Next x
alpha, by the way, looks like this:
A B C D E F G
H I J K L M N
O P Q R S T U
V W X Y Z 1 2
3 4 5 6 7 8 9
0 ; : ' " . ,
( ) _ - + ? !
This always returns A, which is in alpha(1,1). Come to think of it, since they each go to seven, I don't know why it don't come back with !. How can I get around this and make it return true only when it actually matches?
As far as I understand you want to create a substitution algorithm. If there is no specific reason to use a two dimensional cipher table I would rather use a one dimensional approach like the following:
Function Cipher(Argument As String) As String
Dim Model As String
Dim Subst As String
Dim Idx As Integer
Dim MyPos As Integer
Cipher = ""
' note double quotation mark within string
Model = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890;:'"".,()_-+?!"
Subst = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890;:'"".,()_-+?!"
For Idx = 1 To Len(Argument)
' get position from Model
MyPos = InStr(1, Model, UCase(Mid(Argument, Idx, 1)))
' return character from substitution pattern
If MyPos <> 0 Then Cipher = Cipher & Mid(Subst, MyPos, 1)
Next Idx
End Function
calling this function with
Sub Test()
Debug.Print Cipher("The quick brown (?) fox 123 +-")
End Sub
results in THEQUICKBROWN(?)FOX123+- (because we don't allow blanks in Model or Subst)
Now change Subst to
Subst = "!?+-_)(,.""':;0987654321ZYXWVUTSRQPONMLKJIHGFEDCBA"
result is 4,_73.+'?6910GBF)9ZWVUCD
if you feed the above into the cipher function, you end up again with THEQUICKBROWN(?)FOX123+- as you would expect from a symetrical substitution.
I tried the following, and got the expected result (it was able to find the question mark):
(1) Created CipherTable range in worksheet, as above;
(2) Created a function QM similar to code above;
(3) Entered a formula in the style of =QM(cell-ref).
It worked fine. Function QM:
Public Function QM(theChar)
Dim CipherTable
Dim x As Integer
Dim y As Integer
CipherTable = Range("CipherTable").Value
For x = 1 To 7
For y = 1 To 7
If CipherTable(x, y) = theChar Then
QM = "X" & x & "Y" & y
Exit Function
End If
Next y
Next x
QM = ""
End Function
====
I also tried something more direct, and got the response expected:
Public Sub QM2()
Dim questMark As Range
Dim someChar As String
Set questMark = Range("CipherTable").Cells(7, 6)
someChar = "A"
Debug.Print questMark = someChar
End Sub
Related
I aske more or less the same question as recently in German.
I have a workbook with several worksheets that contain several cells with numeric content,
there are numbers with at least 3 decimal places,
Therefore I first cose Double as the data type, which amazes me that I got an error message when I start the debugger for the SUB "Startnahe"
in single step mode
immediately get for the line in which I try to read the cell conten v and the debugger got stuck in this line,
now I delared v and d, s, dl as Varian, the result
is alas that debugger give me the same error message but in the line later,
where I calculate d = s - v.
Public Function Nahe(ZelleSuchWert As Variant, TabName)
Dim rw As Integer
Dim cl As String
Dim c As Range
Dim v As Variant
Dim s As Variant, d As Variant, dl As Variant
Dim i As Integer, a As Integer
Dim Liste As Worksheet
Set Liste = ThisWorkbook.Sheets(TabName)
If (Liste Is Nothing) Then
Nahe = "No hay tabla de este nombre"
Exit Function
Else
cl = Left(ZelleSuchWert, 1)
rw = CInt(Right(ZelleSuchWert, 2))
Set c = Liste.Cells(rw, cl)
If (c Is Nothing) Then
Nahe = "No esta instanciado celdula-obj"
Exit Function
Else
s = c.Value
With Liste
i = 1
dl = 360
a = 0
Do While (Not IsEmpty(Liste.Cells(i, 1)))
v = Liste.Cells(i, 1).Value
d = s - v 'error datatype incompatible
If d < dl Then
dl = d
a = i
Else
Exit Do 'maximal aproximada encontrado
End If
i = i + 1
Loop
Nahe = .Cells(a, 2)
End With
End If
End If
End Function
Public Sub StartNahe()
Dim r As String
r = Nahe("E11", "Rosa_Brujula")
End Sub
Or do I have a buck in there that has nothing to do with the cell content?
"Rosa_Brujula is a worksheet, with 2 columnes in the 1st column
there is a value,
with 2 decimale places
in the 2nd column there is a string which should be behind the cell after the cell to be read out.
If the decimal places should be a problem, then it will be difficult;
because the cell to be read contains an angle that is determined trigonometrically
and which can have decimal places.
I have Excel 10 in a German installation.
I have in Column K:
K8 is 6384 i.e. =SUM(J1:J8)
K9 is 2598 i.e. =SUM(J2:J9)
K10 is 12176 i.e =SUM(J3:J10)
:
:
K5488
up to K5488 (No numbers in sequence, all different numbers)
The largest number appearing in K is 1 400 000.
I need in Column M: The prime factors of each number in K
e.g. K8 is 6384 then M8 should be 2,2,2,2,3,7,19
k9 is 2598 then M9 should be 2,3,433 etc.
I found the following code by John Coleman on your site (Mar 28) which tested well, but seeing I have no programming knowledge, I don't know how to modify it to use in my columns K & M setup.
Here's the sample code:
Function Factor(ByVal n As Long, Optional FirstTrial As Long = 2) As String
Dim i As Long
Dim t As Long
Dim limit As Long
Dim rest As String
Dim s As String
If n = 1 Then
Factor = n
Exit Function
End If
limit = Int(Sqr(n))
t = FirstTrial
Do While t <= limit
If n Mod t = 0 Then
rest = Factor(n / t, t)
If rest <> "1" Then
s = t & "," & rest
End If
Factor = s
Exit Function
Else
If t = 2 Then t = 3 Else t = t + 2
End If
Loop
'if we get here:
Factor = n
End Function
Function PrimeOrFactor(n As Long) As String
Dim s As String
s = Factor(n)
If n = 1 Then
PrimeOrFactor = "Neither"
ElseIf (s) = Trim(n) Then
PrimeOrFactor = "Prime"
Else
PrimeOrFactor = s
End If
End Function
Tested like:
Sub test()
Dim i As Long
For i = 1 To 20
Cells(i, 1) = i
Cells(i, 2) = PrimeOrFactor(i)
Next i
End Sub
The function you provided is a udf (user defined function) to be used in your worksheet.
If you put the functions you provided in a normal code module, you can enter the following in your worksheet in M8:
=Factor(K8)
and copy that function down to your desired range.
I am trying to use a formula that it will allow me to pick 183 names randomly from a list of 355 names. My excel sheet will look something like this:
Names Random.Names
Paty
Oscar
John
Anna
Jane
Carlos
Maria
Jennifer
Susan
Kayla
On my actual sheet I have more names but this is just an example. I used the following formula but I have a few cells that show #REF after it randomizes.
=IF(ROWS($1:1)>$E$2,"",INDEX($A$8:$A$355,RANDBETWEEN(1,354)))
Please let me know if you have a better formula or if you know what I am doing wrong.
That is because INDEX is relative, so row 8 is 1 and row 355 is 355-8+1 = 348. Change the RANDBETWEEN to 1,348
Anything greater than the number of cells referenced will produce the error.
=IF(ROWS($1:1)>$E$2,"",INDEX($A$8:$A$355,RANDBETWEEN(1,348)))
Or you can reference the whole column and use 8,355:
=IF(ROWS($1:1)>$E$2,"",INDEX($A:$A,RANDBETWEEN(8,355)))
You do not have 355 names between A8 and A355 only 355-8+1.
So fix the RANDBETWEEN()
Following the logic of my previous anwser
You only have to open your VBA editor an paste the following code:
'By Julio Jesus Luna Moreno
'jlqmoreno#gmail.com
Option Base 1
Public Function UNIQRAND(a As Variant, b As Variant) As Variant
Application.Volatile
Dim k%, p As Double, flag As Boolean, x() As Variant
k = 1
flag = False
ReDim x(1)
x(1) = Application.RandBetween(a, b)
Do Until k = b - a + 1
Do While flag = False
Randomize
p = Application.RandBetween(a, b)
'Debug.Assert p = 2
resultado = Application.Match(p, x, False)
If IsError(resultado) Then
k = k + 1
ReDim Preserve x(k)
x(k) = p
flag = True
Else
flag = False
End If
Loop
flag = False
Loop
UNIQRAND = x
End Function
This function will do the trick
Public Function RANDNAMES(Rango As Range, HowMany As Integer) As Variant
Dim n, p(), x(), i As Variant
n = Rango.Rows.Count
If n < HowMany Then
MsgBox "Number of pairs must be less than number of total elements"
Exit Function
End If
ReDim x(HowMany)
ReDim p(n)
p = UNIQRAND(1, n)
For i = 1 To HowMany Step 1
x(i) = Application.Index(Rango, p(i))
Next i
Debug.Print HowMany
RANDNAMES = Application.Transpose(x)
End Function
I have been banging my head (and a few other heads as well on other Excel programming sites) to get a Combobox in a Userform to sort the rows (coming from two columns in the source spreadsheet) in alpha order.
Ideally, I want a 2 dimensional sort, but at this point, will settle for ONE that works.
Currently, the Combobox, when dropped down, reads in part (minus the bullet points, which do NOT appear and are not needed):
Zoom MRKPayoutPlan
Chuck PSERSFuture
Chuck PSERSCurrent
What I want is:
Chuck PSERSCurrent
Chuck PSERSFuture
Zoom MRKPayoutPlan
The first order is derived from the order in which the rows appear in the source worksheet.
At this point, I am getting a Runtime Error '13', Type Mismatch error. Both fields are text fields (one is last name, the other is a classification code- I want to sort first by name).
Below are the two relevant sections of the VBA code. If someone can help me sort this out, I'll buy at least a virtual round of beers. Excel VBA is not my most comfortable area- I can accomplish this in other apps, but the client spec is that this all must run in Excel alone. Thanks in advance.
Private Sub UserForm_Initialize()
fPath = ThisWorkbook.Path & "\"
currentRow = 4
sheetName = Sheet5.Name
lastRow = Sheets(sheetName).Range("C" & Rows.Count).End(xlUp).Row
Dim rngUID As Range
Dim vList
Set rngUID = Range("vUID")
With rngUID
vList = Application.Index(.Cells, .Parent.Evaluate("ROW(" & .Address & ")"), Array(7, 1))
End With
vList = BubbleSort2D(vList, 2, 1)
With ComboBox1
.ColumnCount = 2
.ColumnWidths = "100;100"
.List = vList
End With
PopulateControls
End Sub
Public Function BubbleSort2D(Strings, ParamArray SortColumns())
Dim tempItem
Dim a As Long
Dim e As Long
Dim f As Long
Dim g As Long
Dim i As String
Dim j As String
Dim m() As String
Dim n
Dim x As Long
Dim y As Long
Dim lngColumn As Long
e = 1
n = Strings
Do While e <> -1
For a = LBound(Strings) To UBound(Strings) - 1
For y = LBound(SortColumns) To UBound(SortColumns)
lngColumn = SortColumns(y)
i = n(a, lngColumn)
j = n(a + 1, lngColumn)
f = StrComp(i, j)
If f < 0 Then
Exit For
ElseIf f > 0 Then
For x = LBound(Strings, 2) To UBound(Strings, 2)
tempItem = n(a, x)
n(a, x) = n(a + 1, x)
n(a + 1, x) = tempItem
Next x
g = 1
Exit For
End If
Next y
Next a
If g = 1 Then
e = 1
Else
e = -1
End If
g = 0
Loop
BubbleSort2D = n
End Function
Here is a bubble sort in VBA source.
Public Sub BubbleSort(ByRef sequence As Variant, _
ByVal lower As Long, ByVal upper As Long)
Dim upperIt As Long
For upperIt = upper To lower + 1 Step -1
Dim hasSwapped As Boolean
hasSwapped = False
Dim bubble As Long
For bubble = lower To upperIt - 1
If sequence(bubble) > sequence(bubble + 1) Then
Dim t as Variant
t = sequence(bubble)
sequence(bubble) = sequence(bubble + 1)
sequence(bubble + 1) = t
hasSwapped = True
End If
Next bubble
If Not hasSwapped Then Exit Sub
Next upperIt
End Sub
Note that using variable names that specify what they are and do instead of single letters makes it easier to read.
As for the 2D sort. Don't. Sort each array individually then sort the array of arrays using the same method. You will need to provide an abstraction to compare the columns. Do not try to sort them both at the same time. I can't think of a scenario where that is a good idea. If for some reason elements can change their sub array in the 2D array, then flatten it into 1 array, sort that and split it back into a 2D array.
Honestly from what I am understanding of you specific problem. You are going from 1D sequence to a 1D sequence so I think 2D arrays are and unnecessary complication.
Instead use a modified bubble sort routine with the comparison statement,
If sequence(bubble) > sequence(bubble +1) Then '...
replaced with a custom comparison function
ComboBoxItemCompare(sequence(bubble), sequence(bubble + 1))
that will return True if the first argument should be swapped with the second.
I am getting an error at the subroutine called NLRegress. I think the array types are not the same that are being multiplied in the first call in Sub NLRegress. The Z matrix is the following array [1,0.2,0.04: 1,0.5,0.25: 1,0.8,0.64: 1,1.2,1.44: 1,1.7,2.89: 1,2,4]
This is my code :
Option Explicit
Option Base 1
Sub Main()
Dim x() As Double, y() As Double, n As Integer, p As Integer, _
a() As Double, syx As Double, r2 As Double, m As Integer, _
yf() As Double, Z() As Double
Dim i As Integer, k As Integer
For k = 1 To 100
If Worksheets("Sheet1").Range("A2").Cells(k, 1).Value <> "" Then
n = n + 1 'counts the number of data points
Else
Exit For
End If
Next k
For k = 1 To 100
If Worksheets("Sheet1").Range("B2").Cells(k, 1).Value <> "" Then
p = p + 1 'counts the number of data points
Else
Exit For
End If
Next k
If p = n Then
p = n
ReDim yf(n)
Else: MsgBox ("Unequal number of x and y values")
End If
ReDim x(n)
ReDim y(n)
For i = 1 To n 'Read data for matrix x
x(i) = _
Worksheets("Sheet1").Range("A2").Cells(i, 1).Value
Next
For i = 1 To n 'Read data for matrix y
y(i) = _
Worksheets("Sheet1").Range("B2").Cells(i, 1).Value
Next
m = Worksheets("Sheet1").Range("E2").Value
ReDim a(m + 1)
Call BuildZP(x, Z, n, m)
Call NLRegress(Z, y, a, n, m)
Call MultiplyMatrixByVector(Z, a, yf)
End Sub
Sub Fitted_Data(yf, a, x, n)
Dim q As Integer
For q = 1 To n
yf(q) = a(1) + a(2) * x(q) + a(3) * x(q) ^ 2
Worksheets("Sheet1").Range("C2").Cells(q, 1).Value = yf(q)
Next
End Sub
Sub NLRegress(Z, y, a, n, m)
Dim er As Double, tol As Double, ZT() As Double, ZTZ() As Double, ZTZI() As Double, ZTY() As Double
er = 0
tol = 0.0001
ReDim ZT(m + 1, n)
Call TransposeMatrix(Z, ZT)
Call MultiplyMatrices(ZT, Z, ZTZ)
Call MatrixInverse(ZTZ, ZTZI, m + 1, tol, er)
Call MultiplyMatrixByVector(ZT, y, ZTY)
Call MultiplyMatrixByVector(ZTZI, ZTY, a)
End Sub
Sub BuildZP(x, Z, n, m)
Dim i As Integer, j As Integer
ReDim Z(n, m + 1)
For i = 1 To n
For j = 1 To m + 1
Z(i, j) = x(i) ^ (j - 1)
Next j
Next i
End Sub
This answer will probably not solve your issue (see my comment) - but let me nonetheless give you a few best practices that might make programming in VBA easier and maybe prevent such errors in the first place - in your next project.
Try to incorporate the following into your programming
Proper indenting: Every time you use a programming structure the encloses another block of code - such as For, If, While, indent the enclosed code block one level further. E.g. your first few lines of code should look like
For k = 1 To 100
If Worksheets("Sheet1").Range("A2").Cells(k, 1).Value <> "" Then
n = n + 1 'counts the number of data points
Else
Exit For
End If
Next k
You are already using Option Explicit, which is great. However, you should also properly Dim each variable in the procedure/function calls - e.g. Sub Fitted_Data(yf as Double, ...)
You're using a total of 12 variables in your main procedure. This is a very strong indicator, that your routine is doing too much! Better break it up in to small sub routines and maybe use a few module wide variables - see the example below.
The variable names are absolutely meaningless - which makes it hard to debug for you - and almost impossible for outsiders to understand what your code is doing.
AFAIK your first 25 rows "only" assign two ranges to an array and check if these are the same size. Using the syntax x = StartRange.Resize(NumberOfRows).Cells you can achieve this with much less code - and it executes much faster.
Same thing goes finding the first blank row - instead of looping, use StartRange.End(xlDown) - this will return you the last non-blank row!
Also, if you want to assign an array to a range, it works the other way round, too: StartRange.Resize(NumberOfRows) = x.
Hardcoding Worksheets("Sheet1").Range("A2") will lead to problems when the user changes the worksheet structure, e.g. rename the sheet or insert rows/columns. Better assign the cells A2 and B2 names, e.g. StartVector1 and then access them with Range("StartVector1"). Much more robust - and your code is less cluttered
"Don't repeat yourself" (DRY). If you see yourself doing the same code twice, make it a separate procedure - e.g your code to count the number of data points
No need to use Call Sub(x, y) - Sub x, y is equivalent to it in VBA
Excel function can also be used in VBA. This is especially handy for matrix function. E.g. to transpose an array, you could use this code: transposedX = worksheetFunctions.Transpose(x)
Here's the code structure with the first few
Option Explicit
Private mVec1() As Double 'Better give a better name representing the target content of variable
Private mVec2() As Double 'I use m as a prefix to indicate module wide scoped variables
Public Sub SubDoingSomething() 'Use a name that tells the reader what the sub does
LoadVectors
BuildZP Z, n, m 'use proper variable names here
NLRegress Z, y, a, n, m 'and maybe use some more module wide variables that you don't need to pass
MultiplyMatrixByVector Z, a, yf
End Sub
Private Sub LoadVectors()
Dim count1 As Long, count2 As Long
count1 = GetRowLength(Range("StartVector1"))
count2 = GetRowLength(Range("StartVector2"))
If count1 <> count2 Then
MsgBox ("Unequal number of x and y values")
End
End If
mVec1 = Range("StartVector1").Resize(count1).Cells
mVec2 = Range("StartVector2").Resize(count2).Cells
End Sub
Private Function GetRowLenght(rng As Range)
If rng.Offset(1) = "" Then
GetRowLength = 1
Else
GetRowLength = rng.End(xlDown).Row - rng.Row + 1
End If
End Function