VBA - Looping through string with delimeters - vba

Hoping someone could help me with this. I have the following string:
RSSRRSSRRR;RSRRSSRSRSRSSRSS;RSRSSS;SRRRSSRR;SSSRS;SRSSRRSRRSSS;SRSSRS;SRSSS;RSSRSRSS;RSRSSRSRRSSS;RSSSS;SRSSS;S/RR/SR/SS/RS/RR.SSSRRS;RSSSS;SRSSS;SSSS;RSSRSRSS;SSRSS;SSRRSRSRSS;SRRSRSSS;RSSSRRSS;SSSS;SSSRS;SRSSS;R/SR/SS/RR/RR/SR/RR/S
This is data from a tennis match. The 'S' represents a point won by the server, 'R' represents a point won by the receiver, ';' represents the end of a game and '.' represents the end of the set. The server in the first game will be Player 1 and the server in the second game will be Player 2. This will swap throughout the match after each game. Given a tie break occurs in the 13th game of a set, the '/' will represent when the serve is switched over to the other player.
I want to get the data into the following format:
Set, Game, Point, PlayerPointID, P1net, P2net,result
1, 1, 1, player2 , -1, +1
1, 1, 2, player1 , 0, 0
1, 1, 3, player1 , +1, -1
...
1, 1, 10, player2, -2,+2, Player2
I've wrote the following code so far:
Sub LoopThroughString()
Dim Counter As Integer
Dim MyString As String
MyString = Cells(2, 9)
For Counter = 1 To Len(MyString)
If Mid(MyString, Counter, 1) = "S" Then
Cells(Counter + 1, 16) = Cells(Counter, 16) + 1
ElseIf Mid(MyString, Counter, 1) = "R" Then
Cells(Counter + 1, 16) = Cells(Counter, 16) - 1
End If
Cells(Counter + 1, 17) = -Cells(Counter + 1, 16)
Next
End Sub
That basically just loops the string and calculates net values. I'm not sure however how to deal with the delimiters, how it would swap over player 1 and player 2 after each game. Also how I could do a point, game, set count. The point count would need to reset after each game and the game count would reset after each set. Any ideas?

You can use Split([String], [Delimiter])to get an Array of strings in which there is a delimiter. for example, your string RSSRRSSRRR;RSRRSSRSRSRSSRSS;RSRSSS;SRRRSSRR;SSSRS;SRSSRRSRRSSS;SRSSRS;SRSSS;RSSRSRSS;RSRSSRSRRSSS;RSSSS;SRSSS;S/RR/SR/SS/RS/RR.SSSRRS;RSSSS;SRSSS;SSSS;RSSRSRSS;SSRSS;SSRRSRSRSS;SRRSRSSS;RSSSRRSS;SSSS;SSSRS;SRSSS;R/SR/SS/RR/RR/SR/RR/S when used in the function :
Dim MyArray() As String
MyArray = Split(MyString, ";")
would be separated as such :
RSSRRSSRRR
RSRRSSRSRSRSSRSS
RSRSSS
SRRRSSRR
SSSRS
SRSSRRSRRSSS
SRSSRS
SRSSS
RSSRSRSS
RSRSSRSRRSSS
RSSSS
SRSSS
S/RR/SR/SS/RS/RR.SSSRRS
RSSSS
SRSSS
SSSS
RSSRSRSS
SSRSS
SSRRSRSRSS
SRRSRSSS
RSSSRRSS
SSSS
SSSRS
SRSSS
R/S
R/SS/RR/RR/SR/RR/S
Each line being a separate entry in the Array. MyArray[0] would then be the first line RSSRRSSRRR, which would be the first game, and MyArray[UBound(MyArray)] would be the last one R/SS/RR/RR/SR/RR/S. UBound(MyArray)is the last index of the array provided, which would be equivalent to 25in this example.
To loop through each entry, you can use a for loop :
Dim CurrentGame As Integer
For CurrentGame = 0 to UBound(MyArray)
For Counter = 0 to Len(MyArray[CurrentGame])
' do stuff
' like MyArray[CurrentGame][Counter] to get the character "R", "S" or whatever is there.
Next
Next
NOTE : I assume you are using the default base 0 indexes from your example, but the best practice would be to use For CurrentGame = LBound(MyArray) '... instead, where LBound(MyArray) gives you the first index of the provided Array.
To remember which player is serving, you can create another variable :
Dim player1Serving as Boolean
player1Serving = True
When you want to switch, simply write :
player1Serving = Not player1Serving
And when you want to act according to who is serving :
If player1Serving Then
' Do stuff for Player 1 serving
Else
' Do stuff for Player 2 serving
End If

Related

VBA - Create Dynamic Variable from different strings

I'm a beginner on VBA. I have been following SO for years but have never really posted. I'm really struggling to understand a concept and have found no answers elsewhere.I want to use a for loop that 's going to loop these three arrays going like the following:
EUR_Buy = (1,2,3,4,5,6)
USD_BUY = (2,4,6,8,10,12)
GBP_BUY = (1,3,5,7,9,11)
curr = (EUR,USD,GBP)
For i = 0 To 2
For j = 0 To 5
If curr(i) & "_BUY" & (j) = 8
MsgBox Yes
End If
Next j
Next i
The only thing I get is the name of the variable (ex: Eur_Buy(0) but not the value of the value which would be "1". Any idea how I could get this? Would be very helpful).
Thanks a lot and please do not hesitate if you have any questions.
You cannot create a string from pieces and then expect the runtime to use this as variable name.
If you have a list of names and associated values, you can use a Collection (or a Dictionary).
The following piece of code gives you the idea how to use them.
' Create collection and fill it with 3 elements, each holding an array of 6 values
Dim myVars As New Collection
' Elements are added to a collection with add <value>, <key>
myVars.Add Array(1, 2, 3, 4, 5, 6), "EUR_Buy"
myVars.Add Array(2, 4, 6, 8, 10, 12), "USD_BUY"
myVars.Add Array(1, 3, 5, 7, 9, 11), "GBP_BUY"
Dim curr as Variant
Dim j As Long
For Each curr In Array("EUR", "USD", "GBP")
Dim key As String
key = curr & "_BUY"
' You can access an element of a collection with it's key (name) or index.
For j = 0 To 5
If myVars(key)(j) = 5 Then Debug.Print curr, j, "Found 8 in " & key
Next
Next
Referencing an Array of arrays via Enum statement
If you have to deal with a greater number of currencies, it can increase readibility to
use an enumeration defined in the head of a code module and to
reference an Array of arrays (aka jagged array) by these placeholder variables in the main code and which
holds the individual currency arrays for its part; you may think it as sort of container.
Option Explicit ' head of code module
Enum C ' Enum statement allows automatic increments (if no special assignments)
[_Start] = -1
EUR
USD
GBP
LastElement = GBP ' (re-)set to last currency (here GBP), if changed
End Enum
Note that you can easily insert or add other currencies without caring in further code for the actual number as Enum automatically increments the start element (if not assigned explicitly).
The following example code
assigns the individual arrays (starting a little bit tricky with the "Name" of the array as string value, e.g. "EUR") to buy() serving as container array and
executes a Match over all enumerated currencies eventually.
Sub ExampleCall()
'1) define zero-based buy arrays referenced by Enum values (~> module top)
Dim buy(C.LastElement) ' declare 0-based Array of arrays
buy(C.EUR) = Array("EUR", 1, 2, 3, 4, 5, 6) ' assign the individual arrays
buy(C.USD) = Array("USD", 2, 4, 6, 8, 10, 12)
buy(C.GBP) = Array("GBP", 1, 3, 5, 7, 9, 11)
'2) define a search value
Dim srch As Variant
srch = 5
'3) find value 5
Dim curr As Long
For curr = 0 To C.LastElement
Dim no As Variant
no = Application.Match(srch, buy(curr), 0) ' << Find ordinal element position
If IsNumeric(no) Then ' check for valid findings only
no = no - 1 ' adjust counter as Match returns 1-based numbers
'4) display result of individual sub-array buy(curr)
Debug.Print _
buy(curr)(0), _
"index " & no, _
"Found " & buy(curr)(no) & " in " & buy(curr)(0) & "_BUY"
End If
Next
End Sub
Note that Application.Match always returns a 1-based position number (adjusted to the 0-based index by a -1 subtraction) within the individual arrays or an Error if there is no finding at all; checking the no result by IsNumeric allows to get only valid findings.
Results in the VB Editor's immediate window would be displayed e.g. as follows:
EUR index 5 Found 5 in EUR_BUY
GBP index 3 Found 5 in GBP_BUY

Dynamicaly change the nr. of dimensions of a VBA array

I was wondering if there was any way to change the number of dimensions of an array:
In VBA,
Depending on an integer max_dim_bound which indicates the the
desired nr. of dimensions.
Allowing for a starting index of the dimension: E.G. `array(4 to 5, 3 to 6) where the number of 3 to 6 are variable integers.
*In the code itself without extra tools
*Without exporting the code.
To be clear, the following change does not change the nr of dimensions of an array, (merely the starting end ending indices of the elements in each respective dimension):
my_arr(3 to 5, 6 to 10)
'changed to:
my_arr(4 to 8, 2 to 7)
The following example would be a successfull change of the nr. of dimensions in an array:
my_arr(3 to 5, 6 to 10)
'changed to:
my_arr(4 to 8, 2 to 7,42 to 29)
This would also be a change in the nr. of dimensions in an array:
my_arr(4 to 8, 2 to 7,42 to 29)
'changed to:
my_arr(3 to 5, 6 to 10)
So far my attempts have consisted of:
Sub test_if_dynamically_can_set_dimensions()
Dim changing_dimension() As Double
Dim dimension_string_attempt_0 As String
Dim dimension_string_attempt_1 As String
Dim max_dim_bound As String
Dim lower_element_boundary As Integer
Dim upper_element_boundary As Integer
upper_element_boundary = 2
max_dim_bound = 4
For dimen = 1 To max_dim_bound
If dimen < max_dim_bound Then
dimension_string_attempt_0 = dimension_string_attempt_0 & "1 To " & upper_element_boundary & ","
MsgBox (dimension_string_attempt_0)
Else
dimension_string_attempt_0 = dimension_string_attempt_0 & "1 To " & upper_element_boundary
End If
Next dimen
MsgBox (dimension_string_attempt_0)
'ReDim changing_dimension(dimension_string_attempt_0) 'does not work because the "To" as expected in the array dimension is not a string but reserved word that assists in the operation of setting an array's dimension(s)
'ReDim changing_dimension(1 & "To" & 3, 1 To 3, 1 To 3) 'does not work because the word "To" that is expected here in the array dimension is not a string but a reserved word that assists the operation of setting an array's dimension(s).
'ReDim changing_dimension(1 To 3, 1 To 3, 1 To 3, 1 To 3)
'attempt 1:
For dimen = 1 To max_dim_bound
If dimen < max_dim_bound Then
dimension_string_attempt_1 = dimension_string_attempt_1 & upper_element_boundary & ","
MsgBox (dimension_string_attempt_1)
Else
dimension_string_attempt_1 = dimension_string_attempt_1 & upper_element_boundary
End If
Next dimen
MsgBox (dimension_string_attempt_1)
ReDim changing_dimension(dimension_string_attempt_1) 'this does not change the nr of dimensions to 2, but just one dimension of "3" and "3" = "33" = 33 elements + the 0th element
'changing_dimension(2, 1, 2, 1) = 4.5
'MsgBox (changing_dimension(2, 1, 2, 1))
End Sub
*Otherwise a solution is to:
Export the whole code of a module, and at the line of the dimension substitute the static redimension of the array, with the quasi-dynamic string dimension_string.
Delete the current module
Import the new module with the quasi-dynamic string dimension_string as a refreshed static redimension in the code.
However, it seems convoluted and I am curious if someone knows a simpler solution.
Note that this is not a duplicate of: Dynamically Dimensioning A VBA Array? Even though the question seems to mean what I am asking here, the intention of the question seems to be to change the nr. of elements in a dimension, not the nr. of dimensions. (The difference is discussed in this article by Microsoft.)
In an attempt to apply the answer of Uri Goren, I analyzed every line and looked up what they did, and commented my understanding behind it, so that my understanding can be improved or corrected. Because I had difficulty not only running the code, but also understanding how this answers the question. This attempt consisted of the following steps:
Right click the code folder ->Insert ->Class Module Then clicked:
Tools>Options> "marked:Require variable declaration" as shown
here at 00:59.
Next I renamed the class module to
Next I wrote the following code in class module FlexibleArray:
Option Explicit
Dim A As New FlexibleArray
Private keys() As Integer
Private vals() As String
Private i As Integer
Public Sub Init(ByVal n As Integer)
ReDim keys(n) 'changes the starting element index of array keys to 0 and index of last element to n
ReDim vals(n) 'changes the starting element index of array keys to 0 and index of last element to n
For i = 1 To n
keys(i) = i 'fills the array keys as with integers from 1 to n
Next i
End Sub
Public Function GetByKey(ByVal key As Integer) As String
GetByKey = vals(Application.Match(key, keys, False))
' Application.Match("what you want to find as variant", "where you can find it as variant", defines the combination of match type required and accompanying output)
'Source: https://msdn.microsoft.com/en-us/vba/excel-vba/articles/worksheetfunction-match-method-excel
' If match_type is 1, MATCH finds the largest value that is less than or equal to lookup_value. Lookup_array must be placed in ascending order: ...-2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE.
' If match_type is 0, MATCH finds the first value that is exactly equal to lookup_value. Lookup_array can be in any order.
' If match_type is -1, MATCH finds the smallest value that is greater than or equal to lookup_value. Lookup_array must be placed in descending order: TRUE, FALSE, Z-A, ...2, 1, 0, -1, -2, ..., and so on.
'so with False as 3rd optional argument "-1" it finds the smallest value greater than or equal to the lookup variant, meaning:
'the lowest value of keys that equals or is greater than key is entered into vals,
'with keys as an array of 1 to n, it will return key, if n >= key. (if keys is initialized right before getbykey is called and is not changed inbetween.
'vals becomes the number inside a string. So vals becomes the number key if key >= n.
End Function
Public Sub SetByKey(ByVal key As Integer, ByVal val As String)
vals(Application.Match(key, keys, False)) = val
'here string array vals(element index: key) becomes string val if key >=n (meaning if the element exists)
End Sub
Public Sub RenameKey(ByVal oldName As Integer, ByVal newName As Integer)
keys(Application.Match(oldName, keys, False)) = newName
'here keys element oldname becomes new name if it exists in keys.
End Sub
And then I created a new module11 and copied the code below in it, including modifications to try and get the code working.
Option Explicit
Sub use_class_module()
Dim A As New FlexibleArray 'this dimensions object A but it is not set yet
A.Init (3) 'calls the public sub "Init" in class module FlexibleArray, and passes integer n = 3.
'A.SetByKey(1, "a") 'this means that Objecgt A. in class FlexibleArray function SetByKey sets the private string array vals(1) in class Flexible Array becomes "a"
'A.SetByKey(2, "b") 'this means that Objecgt A. in class FlexibleArray function SetByKey sets the private string array vals(2) in class Flexible Array becomes "b"
'A.SetByKey(3, "c") 'this means that Object A. in class FlexibleArray function SetByKey sets the private string array vals(3) in class Flexible Array becomes "c"
'A.RenameKey(3,5) 'This means that object A in class FlexibleArray keys element 3 becomes 5 so keys(3) = 5
' Would print the char "c"
'to try to use the functions:
'A.SetByKey(1, "a") = 4
'MsgBox (keys("a"))
'test = A.SetByKey(1, "a") 'this means that Objecgt A. in class FlexibleArray function SetByKey sets the private string array vals(1) in class Flexible Array becomes "a"
'MsgBox (test)
'test_rename = A.RenameKey(3, 5) 'This means that object A in class FlexibleArray keys element 3 becomes 5 so keys(3) = 5
'MsgBox (test_rename)
'Print A.GetByKey(5) 'Method not valid without suitable object
'current problem:
'the A.SetByKey expects a function or variable, even though it appears to be a function itself.
End Sub
What I currently expect that this code replaces the my_array(3 to 4,5 to 9..) to an array that exists in/as the class module FlexibleArray, that is called when it needs to be used in the module. But Any clearifications would be greatly appreciated! :)
If the goal of redimensioning arrays is limited to a non-absurd number of levels, a simple function might work for you, say for 1 to 4 dimensions?
You could pass the a string representing the lower and upper bounds of each dimension and that pass back the redimensioned array
Public Function FlexibleArray(strDimensions As String) As Variant
' strDimensions = numeric dimensions of new array
' eg. "1,5,3,6,2,10" creates ARRAY(1 To 5, 3 To 6, 2 To 10)
Dim arr() As Variant
Dim varDim As Variant
Dim intDim As Integer
varDim = Split(strDimensions, ",")
intDim = (UBound(varDim) + 1) / 2
Select Case intDim
Case 1
ReDim arr(varDim(0) To varDim(1))
Case 2
ReDim arr(varDim(0) To varDim(1), varDim(2) To varDim(3))
Case 3
ReDim arr(varDim(0) To varDim(1), varDim(2) To varDim(3), varDim(4) To varDim(5))
Case 4
ReDim arr(varDim(0) To varDim(1), varDim(2) To varDim(3), varDim(4) To varDim(5), varDim(6) To varDim(7))
End Select
' Return re-dimensioned array
FlexibleArray = arr
End Function
Test it by calling it with your array bounds
Public Sub redimarray()
Dim NewArray() As Variant
NewArray = FlexibleArray("1,2,3,8,2,9")
End Sub
Should come back with an array looking like this in Debug mode
EDIT - Added Example of truly dynamic array of variant arrays
Here's an example of a way to get a truly flexible redimensioned array, but I'm not sure it's what you're looking for as the firt index is used to access the other array elements.
Public Function FlexArray(strDimensions As String) As Variant
Dim arrTemp As Variant
Dim varTemp As Variant
Dim varDim As Variant
Dim intNumDim As Integer
Dim iDim As Integer
Dim iArr As Integer
varDim = Split(strDimensions, ",")
intNumDim = (UBound(varDim) + 1) / 2
' Setup redimensioned source array
ReDim arrTemp(intNumDim)
iArr = 0
For iDim = LBound(varDim) To UBound(varDim) Step 2
ReDim varTemp(varDim(iDim) To varDim(iDim + 1))
arrTemp(iArr) = varTemp
iArr = iArr + 1
Next iDim
FlexArray = arrTemp
End Function
And if you look at it in Debug, you'll note the redimensioned sub arrays that are now accessible from the first index of the returned array
Sounds like you are abusing arrays for something they weren't meant to do with a ton of memory copying.
What you want is to write your own Class (Right click the code folder ->Insert ->Class Module), let's call it FlexibleArray.
Your class code would be something like this:
Private keys() as Integer
Private vals() as String
Private i as Integer
Public Sub Init(ByVal n as Integer)
Redim keys(n)
Redim vals(n)
For i = 1 to n
keys(i) = i
Next i
End Sub
Public Function GetByKey(ByVal key As Integer) As String
GetByKey = vals(Application.Match(key, keys, False))
End Function
Public Sub SetByKey(ByVal key As Integer, ByVal val As String)
vals(Application.Match(key, keys, False)) = val
End Sub
Public Sub RenameKey(ByVal oldName As Integer, ByVal newName As Integer)
keys(Application.Match(oldName, keys, False))=newName
End Sub
Now you can rename whatever key you want:
Dim A as New FlexibleArray
A.Init(3)
A.SetByKey(1, "a")
A.SetByKey(2, "b")
A.SetByKey(3, "c")
A.RenameKey(3,5)
Print A.GetByKey(5)
' Would print the char "c"
Extending it to integer ranges (like your example) is pretty straight forward

Random number creating

I got some help from one, and the code works perfectly fine.
What im looking for, is an explanation of the code, since my basic VBA-knowledge does not provide me with it.
Can someone explain what happens from "Function" and down?
Sub Opgave8()
For i = 2 To 18288
If Left(Worksheets("arab").Cells(i, 12), 6) = "262015" Then
Worksheets("arab").Cells(i, 3) = "18" & UniqueRandDigits(5)
End If
Next i
End Sub
Function UniqueRandDigits(x As Long) As String
Dim i As Long
Dim n As Integer
Dim s As String
Do
n = Int(Rnd() * 10)
If InStr(s, n) = 0 Then
s = s & n
i = i + 1
End If
Loop Until i = x + 1
UniqueRandDigits = s
End Function
n = Int(Rnd()*10) returns a value between 0 and 9, since Rnd returns a value between 0 and 1 and Int converts it to an integer, aka, a natural number.
Then If InStr(s, n) = 0 checks if the random number is already in your result: if not, it adds it using the string concatenation operator &.
This process loops (do) until i = x + 1 where x is your input argument, so you get a string of length x. Then the first part just fills rows with these random strings.
N.B. : I explained using the logical order of the code. Your friend function UniqRandDigits is defined after the "business logic", but it's the root of the code.
The code loops from row 2 to 18288 in Worksheet "arab". If first 6 characters in 12th column are "262015", then in 3rd column macro will fill cell with value "18" followed by result of function UniqueRandDigits(5) which generates 5 unique digits (0-9).
About the UniqueRandDigits function, the most important is that Rnd() returns a value lesser than 1 but greater than or equal to zero.
Int returns integer value, so Int(Rnd() * 10) will generate a random integer number from 0 to 9.
If InStr(s, n) = 0 Then makes sure than generated integer value doesn't exist in already generated digits of this number, because as the function name says, they must be unique.

vba excel - How to delineate a cell with multiple times in it?

I am writing a macro to separate some data in one cell to multiple columns using the text to columns function. The problem I am running into is figuring out a way to separate a cell with multiple times in it, like so: "9:0011:008:0012:30".
I would like to separate it out into: "9:00" "11:00" etc. If I separate by ":" I'm going to get 9, 00, 11, 00. If I do it by ":**" I'm only going to get 9, 11, 8, 12, cutting off the 12:30 time.
Thanks in advance!
This is my golf attempt:
Option Explicit
Public Sub TestMe()
Dim strInput As String
Dim counter As Long
Dim strCurrent As String
strInput = "9:0011:008:0012:30"
For counter = 1 To Len(strInput) - 2
If Mid(strInput, counter, 1) = ":" Then
Debug.Print strCurrent & Mid(strInput, counter, 3)
counter = counter + 2
strCurrent = vbNullString
Else
strCurrent = strCurrent & Mid(strInput, counter, 1)
End If
Next counter
End Sub
It nicely returns:
9:00
11:00
8:00
12:30
It assumes that the minutes are always with two digits. You can easily change it to a function, returning Array().
The TextToColumns() function requires a Delimiter that is essentially "sacrificed", and you do not have one in those strings! Therefore, the TextToColumns() approach is not viable.
I suggest you use VBA string manipulation functions instead:
Find the position of the first ":" in the string; call it "p"
Extract your first item (the characters from the 1st to the p+2) into an output variable, call it x
Remove the x from the original string
Go to Step 1

VBA: adding random numbers to a grid that arent already in the grid

Sub FWP()
Dim i As Integer
Dim j As Integer
Dim n As Integer
n = Range("A1").Value
For i = 1 To n
For j = 1 To n
If Cells(i + 1, j) = 0 Then
Cells(i + 1, j).Value = Int(((n ^ 2) - 1 + 1) * Rnd + 1)
ElseIf Cells(i + 1, j) <> 0 Then
Cells(i + 1, j).Value = Cells(i + 1, j).Value
End If
Next j
Next i
I am trying to do a part of a homework question that asks to fill in missing spaces in a magic square in VBA. It is set up as a (n x n) matrix with n^2 numbers in; the spaces I need to fill are represented by zeros in the matrix. So far I have some code that goes through checking each individual cell value, and will leave the values alone if not 0, and if the value is 0, it replaces them with a random number between 1 and n^2. The issue is that obviously I'm getting some duplicate values, which isn't allowed, there must be only 1 of each number.
How do I code it so that there will be no duplicate numbers appearing in the grid?
I am attempting to put in a check function to see if they are already in the grid but am not sure how to do it
Thanks
There are a lot of approaches you can take, but #CMArg is right in saying that an array or dictionary is a good way of ensuring that you don't have duplicates.
What you want to avoid is a scenario where each cell takes progressively longer to populate. It isn't a problem for a very small square (e.g. 10x10), but very large squares can get ugly. (If your range is 1-100, and all numbers except 31 are already in the table, it's going to take a long time--100 guesses on average, right?--to pull the one unused number. If the range is 1-40000 (200x200), it will take 40000 guesses to fill the last cell.)
So instead of keeping a list of numbers that have already been used, think about how you can effectively go through and "cross-off" the already used numbers, so that each new cell takes exactly 1 "guess" to populate.
Here's one way you might implement it:
Class: SingleRandoms
Option Explicit
Private mUnusedValues As Scripting.Dictionary
Private mUsedValues As Scripting.Dictionary
Private Sub Class_Initialize()
Set mUnusedValues = New Scripting.Dictionary
Set mUsedValues = New Scripting.Dictionary
End Sub
Public Sub GenerateRange(minimumNumber As Long, maximumNumber As Long)
Dim i As Long
With mUnusedValues
.RemoveAll
For i = minimumNumber To maximumNumber
.Add i, i
Next
End With
End Sub
Public Function GetRandom() As Long
Dim i As Long, keyID As Long
Randomize timer
With mUnusedValues
i = .Count
keyID = Int(Rnd * i)
GetRandom = .Keys(keyID)
.Remove GetRandom
End With
mUsedValues.Add GetRandom, GetRandom
End Function
Public Property Get AvailableValues() As Scripting.Dictionary
Set AvailableValues = mUnusedValues
End Property
Public Property Get UsedValues() As Scripting.Dictionary
Set UsedValues = mUsedValues
End Property
Example of the class in action:
Public Sub getRandoms()
Dim r As SingleRandoms
Set r = New SingleRandoms
With r
.GenerateRange 1, 100
Do Until .AvailableValues.Count = 0
Debug.Print .GetRandom()
Loop
End With
End Sub
Using a collection would actually be more memory efficient and faster than using a dictionary, but the dictionary makes it easier to validate that it's doing what it's supposed to do (since you can use .Exists, etc.).
Nobody is going to do your homework for you. You would only be cheating yourself. Shame on them if they do.
I'm not sure how picky your teacher is, but there are many ways to solve this.
You can put the values of the matrix into an array.
Check if a zero value element exists, if not, break.
Then obtain your potential random number for insertion.
Iterate through the array with a for loop checking each element for this value. If it is not present, replace the zero element.