I'm searching a way to check if an array contains all the elements of another array.
This is the situation: I have two bytes arrays Bytes(): one contains the bytes of a file, and another contains the bytes to compare.
For example, if the file contains these bytes: 4D 5A 90 00 03 and the string to compare is 00 03, I want the function to return true. Else it will obviously return false. So, all bytes in the string to compare must be present in the file too.
I've already searched on the web for this. Tried the old good Contains() function, but for arrays it works only to compare a single byte. You know, one byte only is too little to identify a file!
If possible, I'd like to do this as fast as possible.
I'm working in VB.NET WinForms, VS 2013, .NET 4.5.1
Thanks in advance,
FWhite
EDIT:
Now I have a List(Of Bytes()) like this:
00 25 85 69
00 41 52
00 78 96 32
These are three Bytes() arrays. How do I check if my file bytes array contains all of these values (the file must contains 00 25 85 69, 00 41 52 and 00 78 96 32? I've tried with this code, but it doesn't work:
Dim BytesToCompare As List(Of Byte()) = StringToByteArray(S.Split(":")(3))
For Each B As Byte() In BytesToCompare
If FileBytes.All(Function(c) B.Contains(c)) Then
'Contains
TempResults.Add("T")
Else
TempResults.Add("F")
End If
Next
If CountResults(TempResults) Then
Return S
Exit For
End If
The code in CountResults is this:
Public Function CountResults(Input As List(Of String)) As Boolean
Dim TrueCount As Integer = 0
Dim FalseCount As Integer = 0
Dim TotalCount As Integer = Input.Count
For Each S In Input
If S = "T" Then
TrueCount = TrueCount + 1
ElseIf S = "F" Then
FalseCount = FalseCount + 1
End If
Next
If TrueCount = TotalCount Then
Return True
ElseIf FalseCount > TrueCount Then
Return False
End If
End Function
Tell me if you didn't understand and I'll try to better explain.
Thank you,
FWhite
I was thinking that maybe something other than the brute-force method would work and discovered the Boyer-Moore search algorithm. Shamelessly translating the C and Java code found at Boyer–Moore string search algorithm into VB.NET, I arrived at
Public Class BoyerMooreSearch
' from C and Java code at http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm
Private Shared Function SuffixLength(needle As Byte(), p As Integer) As Integer
Dim len As Integer = 0
Dim j = needle.Length - 1
Dim i = 0
While i >= 0 AndAlso needle(i) = needle(j)
i -= 1
j -= 1
len += 1
End While
Return len
End Function
Private Shared Function GetOffsetTable(needle As Byte()) As Integer()
Dim table(needle.Length - 1) As Integer
Dim lastPrefixPosition = needle.Length
For i = needle.Length - 1 To 0 Step -1
If Isprefix(needle, i + 1) Then
lastPrefixPosition = i + 1
End If
table(needle.Length - 1 - i) = lastPrefixPosition - i + needle.Length - 1
Next
For i = 0 To needle.Length - 2
Dim slen = SuffixLength(needle, i)
table(slen) = needle.Length - 1 - i + slen
Next
Return table
End Function
Private Shared Function Isprefix(needle As Byte(), p As Integer) As Boolean
Dim j = 0
For i = p To needle.Length - 1
If needle(i) <> needle(j) Then
Return False
End If
j += 1
Next
Return True
End Function
Private Shared Function GetCharTable(needle As Byte()) As Integer()
Const ALPHABET_SIZE As Integer = 256
Dim table(ALPHABET_SIZE - 1) As Integer
For i = 0 To table.Length - 1
table(i) = needle.Length
Next
For i = 0 To needle.Length - 2
table(needle(i)) = needle.Length - 1 - i
Next
Return table
End Function
Shared Function IndexOf(haystack As Byte(), needle As Byte()) As Integer
If needle.Length = 0 Then
Return 0
End If
Dim charTable = GetCharTable(needle)
Dim offsetTable = GetOffsetTable(needle)
Dim i = needle.Length - 1
While i < haystack.Length
Dim j = needle.Length - 1
While j >= 0 AndAlso haystack(i) = needle(j)
i -= 1
j -= 1
End While
If j < 0 Then
Return i + 1
End If
i += Math.Max(offsetTable(needle.Length - 1 - j), charTable(haystack(i)))
End While
Return -1
End Function
End Class
And to test it (suspecting that the LINQ code presented by #OneFineDay would demolish it for performance):
Imports System.IO
Imports System.Text
Module Module1
Dim bytesToCheck As List(Of Byte())
Dim rand As New Random
Function GetTestByteArrays() As List(Of Byte())
Dim testBytes As New List(Of Byte())
' N.B. adjust the numbers used in CreateTestFile according to the quantity (e.g. 10) of testData used
For i = 1 To 10
testBytes.Add(Encoding.ASCII.GetBytes("ABCDEFgfdhgf" & i.ToString() & "sdfgjdfjFGH"))
Next
Return testBytes
End Function
Sub CreateTestFile(f As String)
' Make a 4MB file of test data
' write a load of bytes which are not going to be in the
' judiciously chosen data to search for...
Using fs As New FileStream(f, FileMode.Create, FileAccess.Write)
For i = 0 To 2 ^ 22 - 1
fs.WriteByte(CByte(rand.Next(128, 256)))
Next
End Using
' ... and put the known data into the test data
Using fs As New FileStream(f, FileMode.Open)
For i = 0 To bytesToCheck.Count - 1
fs.Position = CLng(i * 2 ^ 18)
fs.Write(bytesToCheck(i), 0, bytesToCheck(i).Length)
Next
End Using
End Sub
Sub Main()
' the byte sequences to be searched for
bytesToCheckFor = GetTestByteArrays()
' Make a test file so that the data can be inspected
Dim testFileName As String = "C:\temp\testbytes.dat"
CreateTestFile(testFileName)
Dim fileData = File.ReadAllBytes(testFileName)
Dim sw As New Stopwatch
Dim containsP As Boolean = True
sw.Reset()
sw.Start()
For i = 0 To bytesToCheckFor.Count - 1
If BoyerMooreSearch.IndexOf(fileData, bytesToCheckFor(i)) = -1 Then
containsP = False
Exit For
End If
Next
sw.Stop()
Console.WriteLine("Boyer-Moore: {0} in {1}", containsP, sw.ElapsedTicks)
sw.Reset()
sw.Start()
Dim temp As New List(Of Byte)
Array.ForEach(bytesToCheckFor.ToArray, Sub(byteArray) Array.ForEach(byteArray, Sub(_byte) temp.Add(_byte)))
Dim result = fileData.All(Function(_byte) temp.Contains(_byte))
sw.Stop()
Console.WriteLine("LINQ: {0} in {1}", result, sw.ElapsedTicks)
Console.ReadLine()
End Sub
End Module
Now, I know that the byte sequences to match are in the test file (I confirmed that by using a hex editor to search for them) and, assuming (oh dear!) I am using the other method correctly, the latter doesn't work whereas mine does:
Boyer-Moore: True in 23913
LINQ: False in 3224
I did also test the first code example by OneFineDay for searching for small vs large patterns to match, and for less than seven or eight bytes that code was faster than Boyer-Moore. So, if you would care to test it for the size of data you're searching in and the size of the patterns you're looking for, Boyer-Moore might be a better fit to your "If possible, I'd like to do this as fast as possible."
EDIT
Further to the OP's uncertainty as to whether or not my suggested method works, here is a test with very small sample data:
Sub Test()
bytesToCheckFor = New List(Of Byte())
bytesToCheckFor.Add({0, 1}) ' the data to search for
bytesToCheckFor.Add({1, 2})
bytesToCheckFor.Add({0, 2})
Dim fileData As Byte() = {0, 1, 2} ' the file data
' METHOD 1: Boyer-Moore
Dim containsP As Boolean = True
For i = 0 To bytesToCheckFor.Count - 1
If BoyerMooreSearch.IndexOf(fileData, bytesToCheckFor(i)) = -1 Then
containsP = False
Exit For
End If
Next
Console.WriteLine("Boyer-Moore: {0}", containsP)
' METHOD 2: LINQ
Dim temp As New List(Of Byte)
Array.ForEach(bytesToCheckFor.ToArray, Sub(byteArray) Array.ForEach(byteArray, Sub(_byte) temp.Add(_byte)))
Dim result = fileData.All(Function(_byte) temp.Contains(_byte))
Console.WriteLine("LINQ: {0}", result)
Console.ReadLine()
End Sub
Outputs:
Boyer-Moore: False
LINQ: True
Also, I renamed the variables in my original Main() method to hopefully make them more meaningful.
You can use the All function to check for that. It returns a Boolean.
Dim orgByteArray() As Byte = {CByte(1), CByte(2), CByte(3)}
Dim testByteArray() As Byte = {CByte(1), CByte(2)}
Dim result = orgByteArray.All(Function(b) testByteArray.Contains(b))
'output for this case returns False
For comparing a List(Of Byte()) to a Byte() where the Byte() is the complte list of all sub arrays in the List(Of byte()).
Dim filebytes() As Byte = {CByte(1), CByte(2), CByte(3), CByte(3), CByte(4), CByte(5), CByte(6), CByte(7), CByte(8)}
Dim bytesToCheck As New List(Of Byte())
bytesToCheck.Add(New Byte() {CByte(1), CByte(2), CByte(3)})
bytesToCheck.Add(New Byte() {CByte(3), CByte(4), CByte(5)})
bytesToCheck.Add(New Byte() {CByte(6), CByte(7), CByte(8)})
Dim temp As New List(Of Byte)
Array.ForEach(bytesToCheck.ToArray, Sub(byteArray) Array.ForEach(byteArray, Sub(_byte) temp.Add(_byte)))
Dim result = filebytes.All(Function(_byte) temp.Contains(_byte))
'output = True
Related
I have been trying to read a networkstream into an array. The code below works fine but is very slow:
Private Function ReadBytes(ByVal NetworkStream As System.Net.Sockets.NetworkStream) As Byte()
Dim Bytes As Byte() = {}
Dim Index As Integer = 0
While NetworkStream.DataAvailable = True
Array.Resize(Bytes, Index + 1)
Bytes(Index) = NetworkStream.ReadByte()
Index += 1
End While
Return Bytes
End Function
Thanks for any help.
The problem with the code is each time the array is resized the array is actually being copied as well. This code should solve your problem:
Private Function ReadBytes(ByVal NetworkStream As System.Net.Sockets.NetworkStream) As Byte()
Dim BlockSize As Integer = 65536
Dim Bytes(BlockSize) As Byte
Dim Position As Integer = 0
Dim DataRead As Integer = 0
Do
ReDim Preserve Bytes(Position + BlockSize)
DataRead = NetworkStream.Read(Bytes, Position, BlockSize)
Position += BlockSize
If DataRead = 0 Then
For i = Bytes.Length - 1 To 0 Step -1
If Not Bytes(i) = 0 Then
ReDim Preserve Bytes(i)
Exit Do
End If
Next
Exit Do
End If
Loop
Return Bytes
End Function
I was in the shower yesterday, and an idea hit me. It's a mathematical pattern or set of rules to find a list of integers.
The practical application of this pattern or whatever you could call it is up to the super mathematicians, I just wanted to see if I could make a program that could find it.
At the moment I'm using VB.net as it's what I HAVE to use for college right now, but im open to C++ as well.
So, the rules of this pattern are:
You get an Integer as your "factor", and multiply it by itself as many times as you like. This creates a list of numbers that you use as namespaces or what I'd call "containers".
If the factor was say, 2, the list would look like this - 4, 8, 16, 32, 64 etc. (excluding the base factor).
Now under each item of the list you place all integers that could fit inside it. If the number of the container is 8 for example, every number from 1 to 7 would be listed.
However the rules go further than that.
A number inside the container cannot be listed if;
it's = 1 or 2
it's = any number in the container before
it's = the factor before
or is divisible by any number in the container before.
For example
[4]-[8]-[16]-[32]
3 7 13 31
5 12 29
11 26
9 25
6 23
4 19
3 17
14
10
7
5
As you can see the numbers you get are always different. So I was working on a form in VS 2015, that allowed you to enter the factor, and the number of iterations (how long the list went) in the list.
The issue is, as you might of guessed by the fact that I'm actually doing this in the first place, is that my logic isn't all that great. As a programer I am a begginer, and as a logical thinker I am not the best.
See below what I've tried out so far. It's just what was in between the button sub:
Dim iteration As Integer = EtrIterationCount.Text
Dim factor As Integer = EtrNumericFactor.Text
Dim Container()() As Integer = New Integer(iteration)() {}
ReDim Container(iteration)(1)
Dim Contents() As Integer = New Integer() {}
Dim Base As Integer = factor
For X = 1 To iteration - 1
Container(X)(1) = New Integer()
Base = Base * factor
Container(X)(0) = Base
Next
Container(0)(0) = factor
For X = 0 To iteration - 1
Dim Test As Integer = Container(X)(0)
Dim Num = Test
For Num = Num To 0 Step -1
If X = 0 Then
For W = Num To 0 Step -1
If Not Test = 1 Then
Contents(W) = Test
End If
Next
Array.ConstrainedCopy(Contents, Contents.Length, Container, Container(X)(1), Contents.Length)
ElseIf X >= 1 Then
For W = Num To 0 Step -1
For J = 0 To Contents.Length - 1
If Not Contents(J) Mod Container(X - 1)(1) = 0 Then
If Not Test = 1 And Test = Container(X)(0) And Contents.Contains(Test) Then
Contents(W) = Test
End If
End If
Next
Next
Array.ConstrainedCopy(Contents, Contents.Length, Container, Container(X)(1), Contents.Length)
End If
Test = Test - 1
MsgBox(Container(X)(1))
Next
Next
What I have basically tried to do, is something I've not done before, and create a jagged/nested array, the first array acting as the container listing all the iterations and its first row containing the factored numbers, the second row containing the array with the list of values.
It was suggested by a first that I use Vectors, or a class of vectors to sort out this problem, but I haven't got the foggiest how to do that.
If anything I would like this to be practice on how to deal with arrays and maybe teach me some logic that I'm missing.
There are probably several ways to do this, many of them better than what I came up with, but a Dictionary(Of Integer, List(Of Integer)) will suffice to hold the data:
Option Infer On
Option Strict On
Module Module1
Sub Main()
Console.Write("Factor: ")
Dim factor = CInt(Console.ReadLine())
Console.Write("Iterations: ")
Dim iters = CInt(Console.ReadLine())
Dim result As New Dictionary(Of Integer, List(Of Integer))
Dim thisSection = factor
Dim prevSection = 0
For i = 2 To iters + 1
thisSection = thisSection * factor
result.Add(thisSection, New List(Of Integer))
For j = 3 To thisSection - 1
Dim isDivisible = False
If prevSection > 0 Then
If j Mod prevSection = 0 Then
isDivisible = True
End If
If Not isDivisible Then
For Each n In result(prevSection)
If j Mod n = 0 Then
isDivisible = True
Exit For
End If
Next
End If
End If
If Not isDivisible Then
result(thisSection).Add(j)
End If
Next
prevSection = thisSection
Next
For Each de In result
Console.WriteLine("[" & de.Key.ToString() & "] " & String.Join(", ", de.Value.OrderByDescending(Function(x) x)))
Next
Console.ReadLine()
End Sub
End Module
Sample output:
Factor: 2
Iterations: 4
[4] 3
[8] 7, 5
[16] 13, 12, 11, 9, 6, 4, 3
[32] 31, 29, 25, 23, 19, 17, 14, 10, 7, 5
A List is pretty much like an array, but you can add to it without knowing the number of entries in advance (it increases its capacity automatically).
The variable names I used could probably be more descriptive.
Incidentally, in your example you have 26 in the [32] column, but 13 (=26/2) is in the [16] column.
Also check this out. It uses the Chain pattern. More easy to add new rule.
Module Module1
Sub Main()
Dim results As New Dictionary(Of Integer, List(Of Integer))
Dim initialValue As Integer = 2
Dim multiplier As Integer = 4
For i As Integer = 2 To multiplier + 1
results.Add(Convert.ToInt32(initialValue ^ i), New List(Of Integer))
Next
For dictionaryIndex As Integer = 0 To results.Count - 1
Dim currentFactor As Integer = results.Keys(dictionaryIndex)
For testValue As Integer = 1 To currentFactor - 1
Dim chain As New ChainOfRules()
chain.AddRule(New NotInValuesRule(testValue, 1, 2))
If (dictionaryIndex > 0) Then
Dim previousFactor As Integer = results.Keys(dictionaryIndex - 1)
chain.AddRule(New NotInValuesRule(testValue, results(previousFactor).ToArray))
chain.AddRule(New NotInValuesRule(testValue, previousFactor))
chain.AddRule(New NotDivisibleListRule(testValue, results(previousFactor).ToArray))
End If
If (chain.Process) Then
results(currentFactor).Add(testValue)
End If
Next
Next
For Each pair As KeyValuePair(Of Integer, List(Of Integer)) In results
Console.WriteLine()
Console.WriteLine("Factor: {0}", pair.Key)
Console.WriteLine("-------------------")
For Each i As Integer In pair.Value
Console.WriteLine("Value: {0}", i)
Next
Next
Console.ReadLine()
End Sub
' Rules
Public Interface IRule
Function Apply() As Boolean
End Interface
Public Class NotInValuesRule
Implements IRule
Private Property Value As Integer
Private Property ValuesList As List(Of Integer)
Public Sub New(value As Integer, ParamArray valuesList As Integer())
Me.Value = value
Me.ValuesList = valuesList.ToList()
End Sub
Public Function Apply() As Boolean Implements IRule.Apply
Return Not ValuesList.Contains(Value)
End Function
End Class
Public Class NotDivisibleListRule
Implements IRule
Private Property Value As Integer
Private Property ValuesList As List(Of Integer)
Public Sub New(value As Integer, ParamArray valuesList As Integer())
Me.Value = value
Me.ValuesList = valuesList.ToList
End Sub
Public Function Apply() As Boolean Implements IRule.Apply
Dim result As Boolean = True
For Each previousValue As Integer In ValuesList
result = result And (Value Mod previousValue <> 0)
Next
Return result
End Function
End Class
Public Class ChainOfRules
Private Property Rules As New List(Of IRule)
Public Sub AddRule(rule As IRule)
Rules.Add(rule)
End Sub
Public Function Process() As Boolean
Dim result As Boolean = True
For Each Rule As IRule In Rules
result = result And Rule.Apply()
Next
Return result
End Function
End Class
End Module
What's an equivalent of the following .NET code in plain old VB6?
byte[] reversedContents = fileContents.Reverse().ToArray();
You can do it with a for loop
For i = 0 To UBound(a) \ 2
k = a(i) : a(i) = a(UBound(a) - i) : a(UBound(a) - i) = k
Next i
If we can assume a dynamic Byte array and a non-DBCS locale, then this is usually fast (as well as a one-liner):
Bytes = StrConv(StrReverse(StrConv(Bytes, vbUnicode)), vbFromUnicode)
Even when not faster it isn't a lot slower. However it is slower when the array is short.
Time comparison testbed:
Option Explicit
Private Sub Easy()
Dim Bytes() As Byte
Dim Iterate As Long
Bytes = StrConv("abcdefghijklmnopqrstuvwxyz", vbFromUnicode)
For Iterate = 1 To 100000
Bytes = StrConv(StrReverse(StrConv(Bytes, vbUnicode)), vbFromUnicode)
Next
End Sub
Private Sub Hard()
Dim Bytes() As Byte
Dim Iterate As Long
Dim I As Long
Dim B As Byte
Bytes = StrConv("abcdefghijklmnopqrstuvwxyz", vbFromUnicode)
For Iterate = 1 To 100000
For I = 0 To UBound(Bytes) \ 2
B = Bytes(I)
Bytes(I) = Bytes(UBound(Bytes) - I)
Bytes(UBound(Bytes) - I) = B
Next
Next
End Sub
Private Sub Main()
Easy
Hard
End Sub
But in general the "hard" approach is safer.
The other answers were helpful for me, but I came to find out my array is built using a custom Split routine that returns a 1-based array. So the assumption that the array was 0-based didn't work out for me. This routine can reverse 0-based, or 1-based arrays. I realize the original question was for reversing a byte array, but the same logic will apply.
Private Sub Reverse(ByRef s() As String, Optional ByVal base As Integer = 0)
Dim i As Long
Dim sSwap As String
For i = base To (UBound(s) + base) \ 2
sSwap = s(i)
s(i) = s((UBound(s) + base) - i)
s((UBound(s) + base) - i) = sSwap
Next
End Sub
Is it possible to divide an Array?
Example:
array(2) As String
array(1) = "test1"
array(2) = "test2"
~ Now Split
array1 (contains test1) & array 2 (contains test2)
I want to implement a Binarysearch
You can split like this
Sub split_array()
Dim array1(1 To 2) As String
Dim array2(1 To 2) As String
Dim array3(1 To 2) As String
array1(1) = "Test1"
array1(2) = "Test2"
array2(1) = array1(1)
array3(1) = array1(2)
End Sub
But I suspect that is not the best way to do it. I think you would do much better using 3 (probably long integer) variables to represent positions in the array. 1 to represent the 1st element, 1 to represent the last element and 1 to represent the mid element.
Dim lLowerSearchElement As Long
Dim lUpperSearchElement As Long
Dim lMiddleSearchElement As Long
Dim array1(1 to 999) as string
lLowerSearchElement = 1
lUpperSearchElement = 999
lMiddleSearchElement = (lUpperSearchElement + lLowerSearchElement) / 2
You can then check if the if the element is equal to, greater or less then the middle element and proceed accordingly.
Also remember that you will need to sort your data before attempting to use a binary search and it would be useful if you know about recursive calling.
You also need to test your implementation rigorously as a small mistake could result in the search not working probably.
Edit 22/08/13
The implementation I use for a binary search is given below:
Function bCheckSamplePoint(ByRef lSamplePointArray() As String, ByRef bfound As Boolean, _
ByVal lSamplePoint As String) As Boolean
'byref used for the array as could be slow to keep copying the array, bFound is used by calling procedure
Dim lLowerSearchElement As Long
Dim lUpperSearchElement As Long
Dim lMiddleSearchElement As Long
bfound = False 'False until found
'Set initial limits of the search
lLowerSearchElement = 0
lUpperSearchElement = UBound(lSamplePointArray())
Do While lLowerSearchElement <= lUpperSearchElement And bfound = False
lMiddleSearchElement = (lUpperSearchElement + lLowerSearchElement) / 2
If StrComp(lSamplePointArray(lMiddleSearchElement), lSamplePoint, vbTextCompare) = -1 Then
' 'Must be greater than middle element
lLowerSearchElement = lMiddleSearchElement + 1
ElseIf (lSamplePointArray(lMiddleSearchElement) = lSamplePoint) Then
bfound = True
Else
'must be lower than middle element
lUpperSearchElement = lMiddleSearchElement - 1
End If 'lSamplePointArray(lmiddlesearchlelemnt) < lSamplePoint
Loop 'While lLowerSearchElement <= lUpperSearchElement
ErrorExit:
bCheckSamplePoint = bReturn
Exit Function
As you can see this binary search is only checking to see wether a string is found in an array of strings, but it could be modified for other purposes.
You don't need a split function to do binary search
My VBA version of binary search can be found at
http://fastexcel.wordpress.com/2011/08/02/developing-faster-lookups-part-3-a-binary-search-udf/
Split Array into chunks
Public Function splitArray(ByVal initial_array As Variant, Optional chunk_size As Long = 1) As Variant()
Dim split_array() As Variant
Dim chunk() As Variant
Dim chunk_index As Integer: chunk_index = 0
Dim array_index As Integer: array_index = 1
If UBound(initial_array) > chunk_size Then
For i = 0 To UBound(initial_array)
If (i + 1) / (chunk_size * array_index) = 1 Or i = UBound(initial_array) Then
ReDim Preserve chunk(chunk_index)
chunk(chunk_index) = initial_array(i)
ReDim Preserve split_array(array_index - 1)
split_array(array_index - 1) = chunk
chunk_index = 0
array_index = array_index + 1
Else
ReDim Preserve chunk(chunk_index)
chunk(chunk_index) = initial_array(i)
chunk_index = chunk_index + 1
End If
Next i
splitArray = split_array
Else
ReDim Preserve split_array(0)
split_array(0) = initial_array
splitArray = split_array
End If
End Function
I don't understand why for each loop in vba doesn't return the good number of element when i use dynamic array.
For exemple, my array size is 4, and i have 5 iteration in for each loop ...
Public Sub test()
Dim t_direction() As String
Dim t_nextDirection() As String
Dim myDirection As Variant
Dim test As Integer
Var = 0
ReDim t_direction(4)
t_direction(0) = "N"
t_direction(1) = "S"
t_direction(2) = "E"
t_direction(3) = "W"
t_nextDirection = randomizeArray(t_direction)
For Each myDirection In t_nextDirection
Var = Var + 1
Next myDirection
MsgBox (UBound(t_nextDirection))
MsgBox (Var)
End Sub
Public Function randomizeArray(ByVal t_array As Variant) As String()
Dim i As Integer
Dim j As Integer
Dim tmp As String
Dim numItems As Integer
numItems = UBound(t_array) - 1
' Randomize the array.
For i = 0 To numItems
' Pick a random entry.
j = Rand(0, numItems)
' Swap the numbers.
tmp = t_array(i)
t_array(i) = t_array(j)
t_array(j) = tmp
Next i
'MsgBox (UBound(t_array))
randomizeArray = t_array
End Function
Public Function Rand(ByVal Low As Long, _
ByVal High As Long) As Integer
Rand = Int((High - Low + 1) * Rnd) + Low
End Function
At the moment you are creating a 5 element array with
ReDim t_direction(4)
as the first element occurs as t_direction(0)
You should either
create a 4 element array ReDim t_direction(3) (ie 0 to 3) and then use numItems consistent with that, or
create a 4 element array ReDim t_direction with a base of 1 (ie 1 to 4) and then use numItems consistent with that (ie numItems = UBound(t_array)). The Option Base 1 below forces the first element to be 1 (which is then ensured anyow by using ReDim t_direction(1 To 4)
The code below uses the later approach. It returns 4 and 4 rather than your current 4 and 5
Option Base 1
Public Sub test()
Dim t_direction() As String
Dim t_nextDirection() As String
Dim myDirection As Variant
Dim test As Integer
Var = 0
ReDim t_direction(1 To 4)
t_direction(1) = "N"
t_direction(2) = "S"
t_direction(3) = "E"
t_direction(4) = "W"
t_nextDirection = randomizeArray(t_direction)
For Each myDirection In t_nextDirection
Var = Var + 1
Next myDirection
MsgBox (UBound(t_nextDirection))
MsgBox (Var)
End Sub
Public Function randomizeArray(ByVal t_array As Variant) As String()
Dim i As Integer
Dim j As Integer
Dim tmp As String
Dim numItems As Integer
numItems = UBound(t_array)
' Randomize the array.
For i = 1 To numItems
' Pick a random entry.
j = Rand(1, numItems)
' Swap the numbers.
tmp = t_array(i)
t_array(i) = t_array(j)
t_array(j) = tmp
Next i
'MsgBox (UBound(t_array))
randomizeArray = t_array
End Function
Public Function Rand(ByVal Low As Long, _
ByVal High As Long) As Integer
Rand = Int((High - Low + 1) * Rnd) + Low
End Function
ReDim t_direction(4) actually declares t_direction as 0 To 4
Its better to be explicit:
ReDim t_direction(0 To 3)
In the absence of a specified lower bound (using the To clause), then the default lower bound is used.
This default can be set to 0 or 1 by using Option Base {0|1} at module level.
In the absence of Option Base then the default default is 0
Notes:
In VBA you are not limited to 0 or 1 as the lower bound, you can use any value you want.
To iterate over an array use
For i = LBound(arr) To UBound(arr)
To calculate the number of items in an array use
numItems = UBound(arr) - LBound(arr) + 1
This way you are not making any assumptions on what the lower bound is