VBA - How Can I Get Randomize String Array To Work? - ms-access

Overview: I am pasting many words on separate lines into text box: txtWordRandomizer. Then, move each line into the string array. I need to shuffle/randomize the array, but can't seem to get it to work.
I got the bottom Sub, ShuffleArray(), here: http://www.cpearson.com/excel/ShuffleArray.aspx
...and it seems to be what everyone references when talking about shuffling/randomizing an array.
I get Error: Type Mismatch: array or user-defined type expected on calling ShuffleArrayInPlace(), but thought this was for randomizing a string array. Do I somehow need to translate the string array into a variant array?
Or, any other suggestion on how I can get this to work?
Private Sub btnRandomize_Click()
Dim strRandoms() As String
strRandoms() = Split(Me.txtWordRandomizer.Value, vbCrLf)
strRandoms() = ShuffleArray(strRandoms())
End Sub
Function ShuffleArray(InArray() As Variant) As Variant()
' This function returns the values of InArray in random order. The original
' InArray is not modified.
Dim N As Long
Dim Temp As Variant
Dim J As Long
Dim Arr() As Variant
Randomize
L = UBound(InArray) - LBound(InArray) + 1
ReDim Arr(LBound(InArray) To UBound(InArray))
For N = LBound(InArray) To UBound(InArray)
Arr(N) = InArray(N)
Next N
For N = LBound(InArray) To UBound(InArray)
J = CLng(((UBound(InArray) - N) * Rnd) + N)
Temp = InArray(N)
InArray(N) = InArray(J)
InArray(J) = Temp
Next N
ShuffleArray = Arr
End Function

Based on some quick testing, the ShuffleArray function in the posted link doesn't actually return a randomized array. Since you're keeping your array in the strRandoms variable, you can use the in-place function anyways (the in-place function will also be more efficient, since it doesn't have to create and populate an entirely new array when called).
When calling the function and passing your array as an argument, don't include parentheses after your array. So do this:
ShuffleArrayInPlace a
' Or this:
Call ShuffleArrayInPlace(a)
However, in order to do this successfully, you have to slightly change the method signature of ShuffleArrayInPlace from this (as it is now):
Sub ShuffleArrayInPlace(InArray() As Variant)
to this:
Sub ShuffleArrayInPlace(InArray As Variant)
Note that the parentheses after the InArray are gone. Why do this?
Originally, with the parentheses, the function is expecting an array of Variant values. However, the split function returns an array of string values. By changing the method signature to remove the parentheses, you're basically saying you can pass anything to the function (a string array, a variant array, or even something that's not an array at all). Because of this, you could update ShuffleArrayInPlace to raise an error if the argument is not an array (using the IsArray function).
Speaking of refactoring: While the algorithm the ShuffleArrayInPlace uses to shuffle an array is clear, it's not necessarily the best one. I would review the Fisher-Yates shuffle, and try implementing it yourself in VBA as an exercise.
So, in summary...
When calling a function that takes an array as an argument, don't put parentheses after the array: `Call ShuffleArrayInPlace(strRandoms)
Use ShuffleArrayInPlace, not ShuffleArray.
Change the ShuffleArrayInPlace function so that InArray is a Variant, not Variant().

This works for me:
Private Sub btnRandomize_Click()
Dim strRandoms() As String
strRandoms = Split("A|B|C|D|E", "|")
strRandoms = ShuffleArray(strRandoms)
Debug.Print Join(strRandoms, ", ")
End Sub
Function ShuffleArray(InArray() As String) As String()
Dim N As Long, Temp As Variant
Dim J As Long, Arr() As String
Randomize
'make a copy of the array
ReDim Arr(LBound(InArray) To UBound(InArray))
For N = LBound(InArray) To UBound(InArray)
Arr(N) = InArray(N)
Next N
'shuffle the copy
For N = LBound(Arr) To UBound(Arr)
J = CLng(((UBound(Arr) - N) * Rnd) + N)
Temp = Arr(N)
Arr(N) = Arr(J)
Arr(J) = Temp
Next N
ShuffleArray = Arr 'return the shuffled copy
End Function

Related

Extracting results from an API call with a VBA function

I'm just starting to work with JSON in one of my macros. I'm able to send a call to an API service and it returns results. The service has been set up to return something like 25 fields (?). I don't need all of the fields, just some of them. The macro uses VBA.
I'm able to write the results to a specific worksheet with the following code:
For Each result In jsonObject
With Sheets("Sheet1")
.Cells(r, 5).Value = result("firstName")
.Cells(r, 6).Value = result("lastName")
.Cells(r, 9).Value = result("userCodeInfo")("userCode")
.Cells(r, 10).Value = result("userCodeInfo")("previousUserCode")
.Cells(r, 28).Value = result("saleType")
.Cells(r, 29).Value = result("cost")
End With
Next
Later, in the code, a decision point needs to use to one of the fields in the results. I'm trying to use a function to return that field value:
Public Function APIUnitData_Read(MyField As String)
Dim JSONConverter As New clsJSONParser
Set jsonObject = JSONConverter.ParseJson(UnitResp)
For Each result In jsonObject
APIUnitData_Read = result(MyField)
Next
End Function
The function works well when the field is a "non-grouped" field, e.g., "firstName, "lastName", etc.
vDecPt = API.APIUnitData_Read("firstName")
It doesn't work (or at least I haven't gotten it to work with the "grouped" fields; e.g., ("userCodeInfo")("userCode") and ("userCodeInfo")("previousUserCode").
vDecPt = API.APIUnitData_Read("userCodeInfo")("PreviousUseCode")
I'm guessing I don't have the right combination of parentheses, double quotes, single quotes, etc. I've tried a number of different combinations without success. I'm sure my lack of experience is also in play here.
Any suggestions or advice how to resolve this issue would be greatly appreciated. Thanks for taking the time to review this question and for any help you can provide......
If you're asking about accessing nested values with a single call then maybe you can try something like this:
Sub tester()
Dim json As Object
Set json = JsonConverter.ParseJson(JsonContent)
Debug.Print JSONValue(json, "name") ' fred
Debug.Print JSONValue(json, "addresses/1/city") ' NYC
Debug.Print JSONValue(json, "addresses/2/street") ' Rue blah
Debug.Print JSONValue(json, "values/4") ' 40
End Sub
'return a "leaf" value from a `json` object
Public Function JSONValue(json As Object, MyField As String)
Dim res, arr, i, v
Set res = json
arr = Split(MyField, "/") 'array of keys/indexes
For i = LBound(arr) To UBound(arr)
v = arr(i)
If TypeName(res) = "Collection" Then v = CLng(v) 'numeric index for collection
If i <> UBound(arr) Then
'not at the end yet so have either a dictionary or a collection
Set res = res(v)
Else
'accessing a single non-object value
JSONValue = res(v)
End If
Next i
End Function
'dummy JSON content
Function JsonContent()
JsonContent = Replace("{'name':'fred','addresses':" & _
"[{'city':'NYC','street':'Easy St'}," & _
"{'city':'Paris','street':'Rue blah'}]," & _
"'values':[10,20,30,40]}", "'", """")
End Function
Very basic but should work if you just want a single non-object return value which might be nested several fields deep.

Query to parse a field and display it

I have a table with values
Errors:
X_11;SR_4;D_11;SR_2
SR_4;T_22
E_18; E_28; SR_3;
E_28; SR_3;
SR_2;SR_4
I need to put in a query to parse the values so that anything with SR comes up so I do like "*SR*" but in the output I need to display only this:
Errors:
SR_4;SR_2
SR_4
SR_3
SR_3
SR_2;SR_4
I would like this in query with many fields other than this one ... instead of VBA. I am using MS Access 2010, I am guessing some type of parsing with each field being separated with ";" that will only capture SR ones?
I think regular expressions might be a way to go.
In VBA, you need to enable the reference to "Microsoft VBScript Regular Expressions 5.5". This question and its accepted answer has a detailed descrpition on what are Regular Expressions and how to enable them in your project (it's for Excel, but for Access is the same route).
Once you have the reference enabled, this little function will give you a "clean" string:
Public Function filterString(str As String)
Dim re As RegExp, obj As Object, x As Variant, first As Boolean
Set re = New RegExp
With re
.Global = True
.IgnoreCase = True
.MultiLine = False
.Pattern = "SR_[0-9]" ' This will match the string "SR_"
' followed by a digit
End With
filterString = ""
first = True
If re.Test(str) Then
Set obj = re.Execute(str)
For Each x In obj
If first Then
first = False
Else
filterString = filterString & ";"
End If
filterString = filterString & x
Next x
End If
End Function
If you test it you'll see that the result is:
filterString("X_11;SR_4;D_11;SR_2")
SR_4;SR_2
which is the result you want.
Now, a simple select query will give you what you need:
select filterString([Errors]) as err
from [yourTable]
where [yourTable].[Errors] like '*sr*'
Hope this helps
I think you can get what you need by splitting your input string into an array and then using the Filter function to create a second array which includes only the SR_ matches from the first array. Finally Join the second array to produce your output string which contains the matches.
Public Function filterString(ByVal pInput As String) As String
Dim array1() As String
Dim array2() As String
array1 = Split(Replace(pInput, " ", vbNullString), ";")
array2 = Filter(array1, "SR_")
filterString = Join(array2, ";")
End Function
Compared to a regular expression approach, this function is more concise. I find the logic simpler. And it does not require setting a reference.
Notice also it will accommodate SR codes which include more than a single digit (in case that eventually becomes a requirement). For example:
? filterString("X_11;SR_4;D_11;SR_234")
SR_4;SR_234
You could use that function in a query in the same way #Barranka suggested:
SELECT filterString(y.Errors) AS sr_codes
FROM [yourTable] AS y
WHERE y.Errors Like '*sr*';

Return an array from a function within a sub

I'm somehow new to excel VBA and I'm stuck with something that should be super easy and I hope you can help me.
I have a Sub (called test() in this case) and I want there to get an array from my function ReturnArray().
But all I get is "Run-time error '13': Type mismatch" when I try to run the sub.
Thanks in Advance.
Function ReturnArray() As Variant
Dim Accounts As Variant
Accounts = Array(1, 2, 3, 4, 5, 6, 7, 8, 9,)
ReturnArray = Accounts
End Function
Sub test()
Dim acc() As Variant
acc = ReturnArray
Debug.Print acc
End Sub
Dim acc() As Variant declares acc as an array of variants but you are returning a single variant from your ReturnArray function. That single variant happens to contain an array. But just one array.
Dim acc As Variant
Now with that said, you are now going to get a runtime error on Debug.Print acc because you cannot print out a whole array on one line. You need to loop over all the elements and print them one at a time. You can turn all the array elements into one string by joining them all together using the Join function: Debug.Print Join(acc, ",")

returning a two dimensional array from a function in vbscript

im having an issue with a program im working on.
what im trying to do is have a function accept input from a user and store that data in an array
for small testing purposes it is a 3 x 3 array
i have gotten the array within the function to work as tested by echoing out all values stored.
however when i attempt to return the array to the sub from which it is called i get mismatch errors, im not sure what i am doing wrong.
Sub SubroutineA()
Dim Array(2,2)
Array = GetInfo()
End Sub
Function GetInfo()
Dim FunctionArray(2,2)
{input all data into array}
GetInfo = FunctionArray()
End Function
Any Help i could get would be great as this is new to me.
Cheran Shunmugavel points to the right direction, but his explanation contains an ambiguety. To make it clear:
Sub SubroutineA()
Dim Arr ' <<<--- do not use parenthesis here and do not use
' the reserved keyword "Array"
Arr = GetInfo()
End Sub
Function GetInfo()
Dim FunctionArray(2,2)
' {input all data into array}
GetInfo = FunctionArray ' <<<--- do not use parenthesis here
End Function

Return multiple values from a function, sub or type?

So I was wondering, how can I return multiple values from a function, sub or type in VBA?
I've got this main sub which is supposed to collect data from several functions, but a function can only return one value it seems. So how can I return multiple ones to a sub?
You might want want to rethink the structure of you application, if you really, really want one method to return multiple values.
Either break things apart, so distinct methods return distinct values, or figure out a logical grouping and build an object to hold that data that can in turn be returned.
' this is the VB6/VBA equivalent of a struct
' data, no methods
Private Type settings
root As String
path As String
name_first As String
name_last As String
overwrite_prompt As Boolean
End Type
Public Sub Main()
Dim mySettings As settings
mySettings = getSettings()
End Sub
' if you want this to be public, you're better off with a class instead of a User-Defined-Type (UDT)
Private Function getSettings() As settings
Dim sets As settings
With sets ' retrieve values here
.root = "foo"
.path = "bar"
.name_first = "Don"
.name_last = "Knuth"
.overwrite_prompt = False
End With
' return a single struct, vb6/vba-style
getSettings = sets
End Function
You could try returning a VBA Collection.
As long as you dealing with pair values, like "Version=1.31", you could store the identifier as a key ("Version") and the actual value (1.31) as the item itself.
Dim c As New Collection
Dim item as Variant
Dim key as String
key = "Version"
item = 1.31
c.Add item, key
'Then return c
Accessing the values after that it's a breeze:
c.Item("Version") 'Returns 1.31
or
c("Version") '.Item is the default member
Does it make sense?
Ideas :
Use pass by reference (ByRef)
Build a User Defined Type to hold the stuff you want to return, and return that.
Similar to 2 - build a class to represent the information returned, and return objects of that class...
You can also use a variant array as the return result to return a sequence of arbitrary values:
Function f(i As Integer, s As String) As Variant()
f = Array(i + 1, "ate my " + s, Array(1#, 2#, 3#))
End Function
Sub test()
result = f(2, "hat")
i1 = result(0)
s1 = result(1)
a1 = result(2)
End Sub
Ugly and bug prone because your caller needs to know what's being returned to use the result, but occasionally useful nonetheless.
A function returns one value, but it can "output" any number of values. A sample code:
Function Test (ByVal Input1 As Integer, ByVal Input2 As Integer, _
ByRef Output1 As Integer, ByRef Output2 As Integer) As Integer
Output1 = Input1 + Input2
Output2 = Input1 - Input2
Test = Output1 + Output2
End Function
Sub Test2()
Dim Ret As Integer, Input1 As Integer, Input2 As Integer, _
Output1 As integer, Output2 As Integer
Input1 = 1
Input2 = 2
Ret = Test(Input1, Input2, Output1, Output2)
Sheet1.Range("A1") = Ret ' 2
Sheet1.Range("A2") = Output1 ' 3
Sheet1.Range("A3") = Output2 '-1
End Sub
you can return 2 or more values to a function in VBA or any other visual basic stuff but you need to use the pointer method called Byref. See my example below. I will make a function to add and subtract 2 values say 5,6
sub Macro1
' now you call the function this way
dim o1 as integer, o2 as integer
AddSubtract 5, 6, o1, o2
msgbox o2
msgbox o1
end sub
function AddSubtract(a as integer, b as integer, ByRef sum as integer, ByRef dif as integer)
sum = a + b
dif = b - 1
end function
Not elegant, but if you don't use your method overlappingly you can also use global variables, defined by the Public statement at the beginning of your code, before the Subs.
You have to be cautious though, once you change a public value, it will be held throughout your code in all Subs and Functions.
I always approach returning more than one result from a function by always returning an ArrayList. By using an ArrayList I can return only one item, consisting of many multiple values, mixing between Strings and Integers.
Once I have the ArrayList returned in my main sub, I simply use ArrayList.Item(i).ToString where i is the index of the value I want to return from the ArrayList
An example:
Public Function Set_Database_Path()
Dim Result As ArrayList = New ArrayList
Dim fd As OpenFileDialog = New OpenFileDialog()
fd.Title = "Open File Dialog"
fd.InitialDirectory = "C:\"
fd.RestoreDirectory = True
fd.Filter = "All files (*.*)|*.*|All files (*.*)|*.*"
fd.FilterIndex = 2
fd.Multiselect = False
If fd.ShowDialog() = DialogResult.OK Then
Dim Database_Location = Path.GetFullPath(fd.FileName)
Dim Database_Connection_Var = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=""" & Database_Location & """"
Result.Add(Database_Connection_Var)
Result.Add(Database_Location)
Return (Result)
Else
Return (Nothing)
End If
End Function
And then call the Function like this:
Private Sub Main_Load()
Dim PathArray As ArrayList
PathArray = Set_Database_Path()
My.Settings.Database_Connection_String = PathArray.Item(0).ToString
My.Settings.FilePath = PathArray.Item(1).ToString
My.Settings.Save()
End Sub
you could connect all the data you need from the file to a single string, and in the excel sheet seperate it with text to column.
here is an example i did for same issue, enjoy:
Sub CP()
Dim ToolFile As String
Cells(3, 2).Select
For i = 0 To 5
r = ActiveCell.Row
ToolFile = Cells(r, 7).Value
On Error Resume Next
ActiveCell.Value = CP_getdatta(ToolFile)
'seperate data by "-"
Selection.TextToColumns Destination:=Range("C3"), DataType:=xlDelimited, _
TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=False, Tab:=True, _
Semicolon:=False, Comma:=False, Space:=False, Other:=True, OtherChar _
:="-", FieldInfo:=Array(Array(1, 1), Array(2, 1)), TrailingMinusNumbers:=True
Cells(r + 1, 2).Select
Next
End Sub
Function CP_getdatta(ToolFile As String) As String
Workbooks.Open Filename:=ToolFile, UpdateLinks:=False, ReadOnly:=True
Range("A56000").Select
Selection.End(xlUp).Select
x = CStr(ActiveCell.Value)
ActiveCell.Offset(0, 20).Select
Selection.End(xlToLeft).Select
While IsNumeric(ActiveCell.Value) = False
ActiveCell.Offset(0, -1).Select
Wend
' combine data to 1 string
CP_getdatta = CStr(x & "-" & ActiveCell.Value)
ActiveWindow.Close False
End Function