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
Related
I have a column where almost every cell is made of a combination of numbers and letters and symbols ("TS-403" or "TSM-7600"). I want every char that's not an integer to be deleted/replaced with an empty string, so that I'm left only with numbers ("403").
I've thought up of two approaches:
I think the best one is to create an array of integers with the numbers 0-9, and then iterate through the cells with a for loop where if the string in a cell contains a char that's not in the array, then that symbol (not the entire cell) should be erased.
Sub fixRequestNmrs()
Dim intArr() as Integer
ReDim intArr(1 to 10)
For i = 0 to 9
intArr(i) = i
Next i
Dim bRange as Range
Set bRange = Sheets(1).Columns(2)
For Each cell in bRange.Cells
if cell.Value
// if cell includes char that is not in the intArr,
// then that char should be deleted/replaced.
...
End Sub()
Perhaps the second approach is easier, which would be to use the Split() function as the '-' is always followed by the numbers, and then have that first substring replaced with "". I'm very confused on how to use the Split() function in combination with a range and a replace funtion though...
For Each cell in bRange.Cells
Cells.Split(?, "-")
...
Digits to Integer Using the Like Operator
The Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns an integer composed from the digits of a string.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function DigitsToInteger(ByVal SearchString As String) As Long
Dim ResultString As String
Dim Char As String
Dim n As Long
For n = 1 To Len(SearchString)
Char = Mid(SearchString, n, 1)
If Char Like "[0-9]" Then ResultString = ResultString & Char
Next n
If Len(ResultString) = 0 Then Exit Function
DigitsToInteger = CLng(ResultString)
End Function
A Worksheet Example
Sub DigitsToIntegerTEST()
Const FIRST_ROW As Long = 2
' Read: Reference the (single-column) range.
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
Dim LastRow As Long: LastRow = ws.Cells(ws.Rows.Count, "B").End(xlUp).Row
If LastRow < FIRST_ROW Then Exit Sub ' no data
Dim rg As Range: Set rg = ws.Range("B2", ws.Cells(LastRow, "B"))
Dim rCount As Long: rCount = rg.Rows.Count
' Read: Return the values from the range in an array.
Dim Data() As Variant
If rCount = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
Else
Data = rg.Value
End If
' Modify: Use the function to replace the values with integers.
Dim r As Long
For r = 1 To rCount
Data(r, 1) = DigitsToInteger(CStr(Data(r, 1)))
Next r
' Write: Return the modifed values in the range.
rg.Value = Data
' To test the results in the column adjacent to the right, instead use:
'rg.Offset(, 1).Value = Data
End Sub
In VBA (Simple)
Sub DigitsToIntegerSimpleTest()
Const S As String = "TSM-7600sdf"
Debug.Print DigitsToInteger(S) ' Result 7600
End Sub
In Excel
=DigitsToInteger(A1)
If you have the CONCAT function, you can do this with a relatively simple formula -- no VBA needed:
=CONCAT(IFERROR(--MID(A1,SEQUENCE(LEN(A1)),1),""))
If you prefer a non-VBA solution in an earlier version of Excel, there is a more complex formula available, but I'd have to go back through my files to locate it.
A tricky function GetVal()
The following function
translates a string into a single characters array arr via help function String2Arr()
isolates them into numeric (category code 6) or non-numeric categories (other) via a tricky execution of Application.Match (here without its 3rd argument which is mostly used for precise search, and by comparing two arrays)
finds the starting position in the original string via Instr()
returns the value of the right substring via Val() (~> see note).
Function GetVal(ByVal s As String) As Double
Dim arr: arr = String2Arr(s): Debug.Print Join(arr, "|")
Dim chars: chars = Split(" ,',+,-,.,0,A", ",")
Dim catCodes: catCodes = Application.Match(arr, chars) 'No 3rd zero-argument!!
Dim tmp$: tmp = Join(catCodes, ""): Debug.Print Join(catCodes, "|")
Dim pos&: pos = InStr(tmp, 6) ' Pos 6: Digits; pos 1-5,7: other symbols/chars
GetVal = Val(Mid(s, pos)) ' calculate value of right substring
End Function
Notes
The Val function can translate a (sub)string with starting digits to a number, even if there are following non-numeric characters.
Help function String2Arr()
Atomizes a string into a single characters array:
Function String2Arr(ByVal s As String)
s = StrConv(s, vbUnicode)
String2Arr = Split(s, vbNullChar, Len(s) \ 2)
End Function
Example call
Dim s As String
s = "aA+*&$%(y#,'/\)!-12034.56blabla"
Debug.Print GetVal(s) ' ~~> 12034.56
I have to store numbers contained in a string into arrays in a special way.
The string contains comma and hyphen.
The comma-separated numbers should be stored individually
Numbers separated by a hyphen should be stored as a range of values.
For example, my string is:
Reg. No 556002,556010-556013,556039 Cancelled
The array should store the numbers as:
(0) 556002 - Single
(1) 556010 ---------|
(2) 556011 Range of
(3) 556012 values
(4) 556013 ---------|
(5) 556039 - Single
I tried the following code:
Dim i, str
Dim array() As Char = str.ToCharArray()
Dim rnoarray() As Integer = New Integer() {}
Dim rno = ""
Dim nosta As Boolean
Dim j = 0
str = "Reg. No 556002,556010-556013,556039 Cancelled"
nosta = False
ReDim rnoarray(Len(str) + 2)
For i = 0 To Len(str)-1
If IsNumeric(array(i)) Then
rno = rno & array(i)
nosta = True
Else
If nosta = True Then
rnoarray(j) = Val(rno)
j = j + 1
nosta = False
rno = ""
End If
End If
Next
For x = 0 To j - 1
MessageBox.Show(rnoarray(x))
Next
But the result only includes four numbers:
556002
556010
556013
556039
Some steps to consider:
Extract the numbers from the input string, preserving the hyphen when present
Verify whether one of the parts contains a hyphen:
In this case, Split() the string into two parts
Convert to Integer the two parts
Take the minimum and the maximum values
Create a range of numbers between the minimum and maximum values
Add the range of numbers to a List(Of Integer)
Convert strings that do not contain a hyphen to Integer
Add the converted numbers to a List(Of Integer)
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text.RegularExpressions
Dim input = "Reg. No 556002,556010-556013,556039 Cancelled"
Dim numbers As New List(Of Integer)
Dim matches = Regex.Matches(input, "\d+-*\d*").OfType(Of Match)
For Each m As Match In matches
If m.Value.Contains("-") Then
Dim parts = m.Value.Split("-"c).Select(Function(s) Integer.Parse(s)).ToArray()
Dim nStart As Integer = Math.Min(parts(0), parts(1))
Dim nEnd As Integer = Math.Max(parts(0), parts(1))
numbers.AddRange(Enumerable.Range(nStart, nEnd - nStart + 1))
Else
numbers.Add(Integer.Parse(m.Value))
End If
Next
Without Regular Expression (assuming the input string format presented here matches the original):
For Each part As String In input.Split(","c)
If part.Contains("-") Then
Dim nValues = part.Split("-"c).Select(Function(s) Integer.Parse(s)).ToArray()
Dim nStart As Integer = Math.Min(nValues(0), nValues(1))
Dim nEnd As Integer = Math.Max(nValues(0), nValues(1))
numbers.AddRange(Enumerable.Range(nStart, nEnd - nStart + 1))
Else
Dim sValue = String.Concat(part.Where(Function(c) Char.IsDigit(c)))
numbers.Add(Integer.Parse(sValue))
End If
Next
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
I have an array prLst that is a list of integers. The integers are not sorted, because their position in the array represents a particular column on a spreadsheet. I want to know how I find a particular integer in the array, and return its index.
There does not seem to be any resource on showing me how without turning the array into a range on the worksheet. This seems a bit complicated. Is this just not possible with VBA?
Dim pos, arr, val
arr=Array(1,2,4,5)
val = 4
pos=Application.Match(val, arr, False)
if not iserror(pos) then
Msgbox val & " is at position " & pos
else
Msgbox val & " not found!"
end if
Updated to show using Match (with .Index) to find a value in a dimension of a two-dimensional array:
Dim arr(1 To 10, 1 To 2)
Dim x
For x = 1 To 10
arr(x, 1) = x
arr(x, 2) = 11 - x
Next x
Debug.Print Application.Match(3, Application.Index(arr, 0, 1), 0)
Debug.Print Application.Match(3, Application.Index(arr, 0, 2), 0)
EDIT: it's worth illustrating here what #ARich pointed out in the comments - that using Index() to slice an array has horrible performance if you're doing it in a loop.
In testing (code below) the Index() approach is almost 2000-fold slower than using a nested loop.
Sub PerfTest()
Const VAL_TO_FIND As String = "R1800:C8"
Dim a(1 To 2000, 1 To 10)
Dim r As Long, c As Long, t
For r = 1 To 2000
For c = 1 To 10
a(r, c) = "R" & r & ":C" & c
Next c
Next r
t = Timer
Debug.Print FindLoop(a, VAL_TO_FIND), Timer - t
' >> 0.00781 sec
t = Timer
Debug.Print FindIndex(a, VAL_TO_FIND), Timer - t
' >> 14.18 sec
End Sub
Function FindLoop(arr, val) As Boolean
Dim r As Long, c As Long
For r = 1 To UBound(arr, 1)
For c = 1 To UBound(arr, 2)
If arr(r, c) = val Then
FindLoop = True
Exit Function
End If
Next c
Next r
End Function
Function FindIndex(arr, val)
Dim r As Long
For r = 1 To UBound(arr, 1)
If Not IsError(Application.Match(val, Application.Index(arr, r, 0), 0)) Then
FindIndex = True
Exit Function
End If
Next r
End Function
array of variants:
Public Function GetIndex(ByRef iaList() As Variant, ByVal value As Variant) As Long
Dim i As Long
For i = LBound(iaList) To UBound(iaList)
If value = iaList(i) Then
GetIndex = i
Exit For
End If
Next i
End Function
a fastest version for integers (as pref tested below)
Public Function GetIndex(ByRef iaList() As Integer, ByVal value As Integer) As Integer
Dim i As Integer
For i = LBound(iaList) To UBound(iaList)
If iaList(i) = value Then: GetIndex = i: Exit For:
Next i
End Function
' a snippet, replace myList and myValue to your varible names: (also have not tested)
a snippet, lets test the assumption the passing by reference as argument means something. (the answer is no) to use it replace myList and myValue to your variable names:
Dim found As Integer, foundi As Integer ' put only once
found = -1
For foundi = LBound(myList) To UBound(myList):
If myList(foundi) = myValue Then
found = foundi: Exit For
End If
Next
result = found
to prove the point I have made some benchmarks
here are the results:
---------------------------
Milliseconds
---------------------------
result0: 5 ' just empty loop
result1: 2702 ' function variant array
result2: 1498 ' function integer array
result3: 2511 ' snippet variant array
result4: 1508 ' snippet integer array
result5: 58493 ' excel function Application.Match on variant array
result6: 136128 ' excel function Application.Match on integer array
---------------------------
OK
---------------------------
a module:
Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
#If VBA7 Then
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) 'For 64 Bit Systems
#Else
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 'For 32 Bit Systems
#End If
Public Function GetIndex1(ByRef iaList() As Variant, ByVal value As Variant) As Long
Dim i As Long
For i = LBound(iaList) To UBound(iaList)
If value = iaList(i) Then
GetIndex = i
Exit For
End If
Next i
End Function
'maybe a faster variant for integers
Public Function GetIndex2(ByRef iaList() As Integer, ByVal value As Integer) As Integer
Dim i As Integer
For i = LBound(iaList) To UBound(iaList)
If iaList(i) = value Then: GetIndex = i: Exit For:
Next i
End Function
' a snippet, replace myList and myValue to your varible names: (also have not tested)
Public Sub test1()
Dim i As Integer
For i = LBound(iaList) To UBound(iaList)
If iaList(i) = value Then: GetIndex = i: Exit For:
Next i
End Sub
Sub testTimer()
Dim myList(500) As Variant, myValue As Variant
Dim myList2(500) As Integer, myValue2 As Integer
Dim n
For n = 1 To 500
myList(n) = n
Next
For n = 1 To 500
myList2(n) = n
Next
myValue = 100
myValue2 = 100
Dim oPM
Set oPM = New PerformanceMonitor
Dim result0 As Long
Dim result1 As Long
Dim result2 As Long
Dim result3 As Long
Dim result4 As Long
Dim result5 As Long
Dim result6 As Long
Dim t As Long
Dim a As Long
a = 0
Dim i
't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
Next
result0 = oPM.TimeElapsed() ' GetTickCount - t
a = 0
't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
a = GetIndex1(myList, myValue)
Next
result1 = oPM.TimeElapsed()
'result1 = GetTickCount - t
a = 0
't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
a = GetIndex2(myList2, myValue2)
Next
result2 = oPM.TimeElapsed()
'result2 = GetTickCount - t
a = 0
't = GetTickCount
oPM.StartCounter
Dim found As Integer, foundi As Integer ' put only once
For i = 1 To 1000000
found = -1
For foundi = LBound(myList) To UBound(myList):
If myList(foundi) = myValue Then
found = foundi: Exit For
End If
Next
a = found
Next
result3 = oPM.TimeElapsed()
'result3 = GetTickCount - t
a = 0
't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
found = -1
For foundi = LBound(myList2) To UBound(myList2):
If myList2(foundi) = myValue2 Then
found = foundi: Exit For
End If
Next
a = found
Next
result4 = oPM.TimeElapsed()
'result4 = GetTickCount - t
a = 0
't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
a = pos = Application.Match(myValue, myList, False)
Next
result5 = oPM.TimeElapsed()
'result5 = GetTickCount - t
a = 0
't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
a = pos = Application.Match(myValue2, myList2, False)
Next
result6 = oPM.TimeElapsed()
'result6 = GetTickCount - t
MsgBox "result0: " & result0 & vbCrLf & "result1: " & result1 & vbCrLf & "result2: " & result2 & vbCrLf & "result3: " & result3 & vbCrLf & "result4: " & result4 & vbCrLf & "result5: " & result5 & vbCrLf & "result6: " & result6, , "Milliseconds"
End Sub
a class named PerformanceMonitor
Option Explicit
Private Type LARGE_INTEGER
lowpart As Long
highpart As Long
End Type
Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As LARGE_INTEGER) As Long
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As LARGE_INTEGER) As Long
Private m_CounterStart As LARGE_INTEGER
Private m_CounterEnd As LARGE_INTEGER
Private m_crFrequency As Double
Private Const TWO_32 = 4294967296# ' = 256# * 256# * 256# * 256#
Private Function LI2Double(LI As LARGE_INTEGER) As Double
Dim Low As Double
Low = LI.lowpart
If Low < 0 Then
Low = Low + TWO_32
End If
LI2Double = LI.highpart * TWO_32 + Low
End Function
Private Sub Class_Initialize()
Dim PerfFrequency As LARGE_INTEGER
QueryPerformanceFrequency PerfFrequency
m_crFrequency = LI2Double(PerfFrequency)
End Sub
Public Sub StartCounter()
QueryPerformanceCounter m_CounterStart
End Sub
Property Get TimeElapsed() As Double
Dim crStart As Double
Dim crStop As Double
QueryPerformanceCounter m_CounterEnd
crStart = LI2Double(m_CounterStart)
crStop = LI2Double(m_CounterEnd)
TimeElapsed = 1000# * (crStop - crStart) / m_crFrequency
End Property
Here's another way:
Option Explicit
' Just a little test stub.
Sub Tester()
Dim pList(500) As Integer
Dim i As Integer
For i = 0 To UBound(pList)
pList(i) = 500 - i
Next i
MsgBox "Value 18 is at array position " & FindInArray(pList, 18) & "."
MsgBox "Value 217 is at array position " & FindInArray(pList, 217) & "."
MsgBox "Value 1001 is at array position " & FindInArray(pList, 1001) & "."
End Sub
Function FindInArray(pList() As Integer, value As Integer)
Dim i As Integer
Dim FoundValueLocation As Integer
FoundValueLocation = -1
For i = 0 To UBound(pList)
If pList(i) = value Then
FoundValueLocation = i
Exit For
End If
Next i
FindInArray = FoundValueLocation
End Function
Is this what you are looking for?
public function GetIndex(byref iaList() as integer, byval iInteger as integer) as integer
dim i as integer
for i=lbound(ialist) to ubound(ialist)
if iInteger=ialist(i) then
GetIndex=i
exit for
end if
next i
end function
Taking care of whether the array starts at zero or one.
Also, when position 0 or 1 is returned by the function, making sure that the same is not confused as True or False returned by the function.
Function array_return_index(arr As Variant, val As Variant, Optional array_start_at_zero As Boolean = True) As Variant
Dim pos
pos = Application.Match(val, arr, False)
If Not IsError(pos) Then
If array_start_at_zero = True Then
pos = pos - 1
'initializing array at 0
End If
array_return_index = pos
Else
array_return_index = False
End If
End Function
Sub array_return_index_test()
Dim pos, arr, val
arr = Array(1, 2, 4, 5)
val = 1
'When array starts at zero
pos = array_return_index(arr, val)
If IsNumeric(pos) Then
MsgBox "Array starting at 0; Value found at : " & pos
Else
MsgBox "Not found"
End If
'When array starts at one
pos = array_return_index(arr, val, False)
If IsNumeric(pos) Then
MsgBox "Array starting at 1; Value found at : " & pos
Else
MsgBox "Not found"
End If
End Sub
'To return the position of an element within any-dimension array
'Returns 0 if the element is not in the array, and -1 if there is an error
Public Function posInArray(ByVal itemSearched As Variant, ByVal aArray As Variant) As Long
Dim pos As Long, item As Variant
posInArray = -1
If IsArray(aArray) Then
If not IsEmpty(aArray) Then
pos = 1
For Each item In aArray
If itemSearched = item Then
posInArray = pos
Exit Function
End If
pos = pos + 1
Next item
posInArray = 0
End If
End If
End Function
The only (& even though cumbersome but yet expedient / relatively quick) way I can do this, is to concatenate the any-dimensional array, and reduce it to 1 dimension, with "/[column number]//\|" as the delimiter.
& use a single-cell result multiple lookupall macro function on the this 1-d column.
& then index match to pull out the positions. (usuing multiple find match)
That way you get all matching occurrences of the element/string your looking for, in the original any-dimension array, and their positions. In one cell.
Wish I could write a macro / function for this entire process. It would save me more fuss.