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
Related
I have a number (say 5) which I would first like to convert to binary (101) and then split into an array of bits {1,0,1} or Booleans {True,False,True} in VBA
Is there a way to do this without looping?
I can convert to Binary without looping in my code with the worksheet formula as follows
myBinaryNum = [DEC2BIN(myDecInteger,[places])]
But I've been told that worksheet functions are very inefficient, and this one is particularly limited.
I'm not sure how to split into an array without looping through the digits with MID. Is there anything like strConv for numbers?
You could first convert the value to a "01" string with WorksheetFunction.Dec2Bin.
Then replace each "0","1" with the code 0 or 1 and cast the result to a Byte array :
Public Function ToBitArray(ByVal value As Long) As Byte()
Dim str As String
str = WorksheetFunction.Dec2Bin(value) ' "101"
str = Replace(Replace(str, "0", ChrW(0)), "1", ChrW(1)) ' "\u0001\u0000\u0001"
ToBitArray = StrConv(str, vbFromUnicode) ' [1, 0, 1]
End Function
But Dec2Bin is limited to 511 and working with strings is rather expensive. So if your goal is to get the best performance, then you should use a loop to read each bit:
Public Function ToBitArray(ByVal value As Long) As Byte()
Dim arr(0 To 31) As Byte, i As Long
i = 32&
Do While value
i = i - 1
arr(i) = value And 1
value = value \ 2
Loop
ToBitArray = MidB(arr, i + 1) ' trim leading zeros
End Function
I found this neat code on another question here at SO. Basically, you can be sure your string is ASCII due to the fact it's 1's and 0's.
What you do is you use
Dim my_string As String
my_string = CStr("your binary number")
To turn your binary number into a string
And then
Dim buff() As String
buff = Split(StrConv(my_string, vbUnicode), Chr$(0))
ReDim Preserve buff(UBound(buff) - 1
To split that string into an array where buff is your array
I think you've probably got everything you need above from other answers, but if you want a simple function that takes the decimal and returns the array..
Function dec_to_binary_array(decNum As Integer)
Dim arr() As String, NumAsString As String
NumAsString = Application.Dec2Bin(decNum)
arr = Split(StrConv(NumAsString, vbUnicode), vbNullChar)
ReDim Preserve arr(UBound(arr) - 1)
dec_to_binary_array = arr
End Function
Invoking Application.Dec2Bin(n) isn't realy expensive, it only costs a late bound call. Use the function below to transform any integer into an arrays of bits:
Function Bits(n as long)
Dim s As String: s = Application.Dec2Bin(n)
Dim ar: ar = Split(StrConv(s, vbUnicode), vbNullChar)
Bits = ar
End Function
p.s.: s will only contain 0 and 1 which are ASCII characters, so the split technique is perfectly valid.
Function d2bin(dec As Integer, bits As Integer) As Integer()
Dim maxVal As Integer
maxVal = 2 ^ (bits)-1
If dec > maxVal Then Exit Function
Dim i As Integer
Dim result() As Integer
ReDim result(0 To bits - 1)
For i = bits - 1 To 0 Step -1
result(bits - i - 1) = -(dec > (2 ^ (i) - 1))
If result(bits - i - 1) Then dec = dec - (2 ^ i)
Next i
d2bin = result
End Function
Please check this code if this is what you need:
You can replace the the digit 5 by any cell value reference, this is just and example:
Sub dectobinary()
Dim BinaryString As String
BinaryString = "5"
tempval = Dec2Bin(BinaryString)
MsgBox tempval
End Sub
Function Dec2Bin(ByVal DecimalIn As Variant) As String
Dec2Bin = ""
DecimalIn = Int(CDec(DecimalIn))
Do While DecimalIn <> 0
Dec2BinTemp = Format$(DecimalIn - 2 * Int(DecimalIn / 2))
If Dec2BinTemp = "1" Then
Dec2Bin = "True" & "," & Dec2Bin
Else
Dec2Bin = "False" & "," & Dec2Bin
End If
DecimalIn = Int(DecimalIn / 2)
Loop
End Function
Just change lngNumber value to your desired number
Public Sub sChangeNumberToBinaryArray()
Dim strBinaryNumber As String
Dim strBinaryArray() As String
Dim lngNumber As Long
lngNumber = 5
strBinaryNumber = DecToBin(lngNumber)
strBinaryArray() = Split(strBinaryNumber, "|")
End Sub
Function DecToBin(ByVal varDecimalIn As Variant) As String
Dim lngCounter As Long
DecToBin = ""
varDecimalIn = Int(CDec(varDecimalIn))
lngCounter = 1
Do While varDecimalIn <> 0
If lngCounter = 1 Then
DecToBin = Format$(varDecimalIn - 2 * Int(varDecimalIn / 2)) & DecToBin
lngCounter = lngCounter + 1
Else
DecToBin = Format$(varDecimalIn - 2 * Int(varDecimalIn / 2)) & "|" & DecToBin
lngCounter = lngCounter + 1
End If
varDecimalIn = Int(varDecimalIn / 2)
Loop
End Function
I am writing a Ceasaer Function that takes a string and runs it through a variant of the Ceasear cipher, and returns the encoded text. For some reason I am getting an Index Out Of Range error on an Array declared with no specific bounds. Why am I getting this exception, and how do I fix it?
VB.NET Code:
Public Shared Function Ceaser(ByVal str As String) As String
Dim r As String = ""
Dim ints() As Integer = {}
Dim codeints As Integer() = {}
Dim codedints As Integer() = {}
Dim ciphertext As String = ""
For i As Integer = 0 To str.Length - 1
Dim currentch As Integer = Map(str(i))
ints(i) = currentch 'Where exception is happening
Next
Dim primes As Integer() = PrimeNums(ints.Length)
For i As Integer = 0 To primes.Length - 1
codeints(i) = ints(i) + primes(i) - 3
Next
For i As Integer = 0 To codeints.Length - 1
Dim currentnum As Integer = codeints(i) Mod 27
codedints(i) = currentnum
Next
For i As Integer = 0 To codedints.Length - 1
Dim letter As String = rMap(codeints(i))
ciphertext += letter
Next
Return ciphertext
End Function
You have to specify the array bounds before you can acces its elements:
Dim ints As Integer(str.length-1)
will instantiate the array with n elements where n = length of string str.
(Take care: VB .NET array lengths are zero-based, so an array with 1 element is instantiated with array(0)).
You have to adopt the other arrays accordingly.
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
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