PDA

View Full Version : elements in an array



troelsi
11-24-2007, 02:53 AM
Hi

What is the fastest and most elegant way to examnine how many elements there is in an array?
After I've loaded my data into an array I want to make a for next loop that runs over each element in the array. Therefor I need to know how many elements (non zeros) that my array contains.

Thanks

Bob Phillips
11-24-2007, 03:12 AM
For i = LBound(myArray) To UBound(myArray)



__________________________________________
UK Cambridge XL Users Conference 29-30 Nov
http://www.exceluserconference.com/UKEUC.html

troelsi
11-24-2007, 03:30 AM
Thanks but that's not exactly what I'm looking for.

Dim myarray(999)

For i = 1 to 10

troelsi
11-24-2007, 03:36 AM
thanks but that's not exactly what I'm looking for.

Dim myarray(999)

for i = 1 to 10
myarray(i) = 1
next i

This array contains 10 elements. Is there a shortcut to finding index number 11 as the first nonzero (null) value. Or do I have to use a for next loop..
for i = 1 to 999
if myarray(i)=0 then
upperbound = i-1
exit for
end if
next i

Bob Phillips
11-24-2007, 08:25 AM
You will have to loop through the items, but see my earlier post, arrays might start at 0.


__________________________________________
UK Cambridge XL Users Conference 29-30 Nov
http://www.exceluserconference.com/UKEUC.html

JonPeltier
11-24-2007, 07:51 PM
Arrays in VB and VBA may start at any value, so use the LBound to UBound approach.

TonyJollans
11-25-2007, 03:23 AM
Dim myarray(999)

for i = 1 to 10
myarray(i) = 1
next i

This array contains 10 elements.

No. The array contains, by default, 1000 elements.

If you want to work with a dynamic array you should explicitly create it that way, for example ..

Dim myarray()

' To add a single element
If (Not Not myarray) = 0 Then
ReDim myarray(1 To 1)
Else
ReDim myarray(1 To UBound(myarray) + 1)
End If
myarray(UBound(myarray)) = 2.71828

' add more as you need them

MsgBox "Array starts with element #" & LBound(myarray) & _
vbCr & " and ends with element #" & UBound(myarray)

unmarkedhelicopter
11-25-2007, 04:30 AM
As for the fastest way ...
You could save 'some' time with binary division.
But it's a bit hit and miss.
How did you build the array ? (there is usually a clue in that)
Why whould you EVER create an array with 999/1000 elements if you were only going to populate (say) 400 ?

If you know that your array starts at 0 and goes to 1023 (say, adapt as required, this is 1024 values) you test 1024/2 (i.e. 512) if that is zero, then you know that your upper limit is greater than 512 (you've just saved half the test), if it was zero you know it was less than 512.
You then test 512 +/- 1024/4 (plus if > 512, minus if < 512) etc. etc.
In theory you could find ANY result in ONLY 10 tests (within 1024 !)

mdmackillop
11-25-2007, 04:39 AM
I would be looking at the data to be loaded. Surely this can be flagged to find the last value > 0 added to the array.

unmarkedhelicopter
11-25-2007, 06:50 AM
MD, yeah that's why I said "How did you build the array ? (there is usually a clue in that)" and In my testit would actually be 11 or 12 tests as you don't have a less than, greater than equal to so the final one would (depending on your coding and efficiency) need a supplimentary test, still 11 from a possible 1023 is an improvement :)

mikerickson
11-25-2007, 05:12 PM
This works for any dimension array (1D, 2D, 3D..., any UBound, LBound):

Dim xVal As Variant, countOfZeros As Double
countOfZeros = 0
For Each xVal In myArray
countOfZeros = countOfZeros + Abs(xVal = 0)
Next xVal

TonyJollans
11-26-2007, 01:30 AM
That would be pretty inefficient in this instance - individually counting around 990 zeroes when you knew after the first how many there would be.

Bob Phillips
11-26-2007, 03:46 AM
and why use another method, Abs, when a simple subtraction works fine

troelsi
11-26-2007, 06:03 AM
Thanks to all for your suggestions. I'm gonna go wtih the dynamic arrays.

TonyJollans
11-26-2007, 08:11 AM
Very sensible, if I may say so :)

unmarkedhelicopter
11-28-2007, 03:23 AM
As an experiment, assuming that (for some reason) it was impossible to capture where the array turns to zero when you populate it, I wrote some code to do a comparison between testing with a loop and testing using binary division.
I've not spent a great deal of time with either so both methods 'could' possibly be improved upon but it shows a general trend.
To meet the OP's (original poster's) criteria, the code defaults to test an array of 999 elements to find where the values stop and zero's commence.
There are dialog boxes so that you can easily change the size of the array and the number of comparisons to be run.
Basically for a 999 array (1000 elements), for the loop you need on average 1503 lines of code to be executed (X*3 + 3, where X is the number of times through the loop, which should average to 500 for a 1000 element array), for the binary division you need 99 lines pretty much regardless of where the boundary occurs.
This show that though 'sometimes' the loop beats binary division it is rare and only occurs in this example when the value is less than 39.
Further, with a random sampling of values, binary division takes about 9% (or less) of the time required by the loop.
Have a play :)

Edit: Found a bug in the code which 'sometimes' prevented small arrays being solved !!!

unmarkedhelicopter
11-28-2007, 10:39 AM
2 views were taken of the original file.
Here's the updated version.
On average (on my machine at least) the bin div code runs quiker than loop for any array bigger than 38 elements.

Paul_Hossler
11-28-2007, 08:25 PM
If you start load data at the "beginning" of the array, you could use MATCH() to find the first element that contains zero.


Sub PHH1()
'general case of bounds
Dim A(100 To 999) As Long
Dim i As Long, N As Long

'fill the first part with data
For i = LBound(A) To 499
A(i) = i + 100
Next i

'get the element number of the first element = 0
N = Application.WorksheetFunction.Match(0, A, 0) + LBound(A) - 1

MsgBox "The first element = 0 in the array A(" & LBound(A) & "," & UBound(A) & ") is element number " & N

End Sub



Paul

unmarkedhelicopter
11-29-2007, 02:11 AM
... And the timing to achieve this ?

Edit 1:
I've run some tests and for 'some' reason Match does not return the correct result.

lnA = Application.WorksheetFunction.Match(0, lnar, 0)

when used in the array in my example :- lnar
with the values filled to 227. i.e. 227 = 42 and 228 = 0, Match returns 229 !!!!
Edit 3: (Duh ! Match would find the first zero, i.e. element 229 which when you consider 0 is element 1 is correct,
you find the first 0, add the Lbound and Subtract 2 !!!). God I'm stupid today !

Edit 2:
Well I've also done some timings ...
Match 'sometimes' returns a 1004 error 'Unable to get the Match property of the WorksheetFunction class'
But when it does run it takes approx 300 - 400% of the time required by the loop

mdmackillop
11-29-2007, 04:16 AM
Then there is always the problem of zero in the data.
e.g.
1,12,22,0,15,45,0,0,0,0,0,..........

unmarkedhelicopter
11-29-2007, 04:34 AM
Then there is always the problem of zero in the data.
e.g.
1,12,22,0,15,45,0,0,0,0,0,..........True ...
I consider 0 to be valid data ... but ...
Given that this is the case ALL of the above solutions would fail !!!
The OP asked for the first non-zero value to determine the 'valid' size of his array upto 999

Edit: for bad spelling :(

rory
11-29-2007, 05:01 AM
Match returns a 1-based index into an array so you would need to adjust for whatever the Lbound of the array actually is. The reason you get an error is because sometimes your array does not contain a zero.
I suspect that there is no definitive answer to this - just as with sorting arrays, the quickest way will depend on how large the array is.

unmarkedhelicopter
11-29-2007, 05:43 AM
Yeah, Paul had adjusted ... just not quite enough and I was blind enough not to see it also (I edited my post when I did)
As for the array possibly not containing a zero, I hadn't thought of that, well spotted, so that's the cause for the 1004 error !
I suppose you could just extend the array and add a zero, just in case :)
or just test the last value, if it's zero :- do the test, if it's not :- you've found the answer :) :) :)

unmarkedhelicopter
11-29-2007, 07:20 AM
Found another problem with Match.
As it is a worksheet function, the array it expects seems to have an upper limit of 65536 elements, I wonder where that comes from ??? :)
It may just be my Excel 2002 but I would guess that anyone without 2007 would have the same problem having said that I've done a 20,000,000 array size with loops and bin. div. No problem - try that in 2007 with Match ... and backward compatibility :- forget it !
Still looking for a faster way on a 999 array (as OP requested) than bin. div. :) :) :)

rory
11-29-2007, 07:44 AM
Faster way: = don't define an array larger than you actually need! Then testing time = 0... :)

unmarkedhelicopter
11-29-2007, 11:52 AM
Rory, first:- which of the 6 counties ?
Yeah ! I too have many loops to test for inclusion of data, if it's good redim preserve the array at ubound + 1 etc. and thinking about it that way it must take ages over the course of building the array so if you know the upper limit build it that big and trim it afterwards, having said that I know when I exit the loop so I know exactly how far I've gone so again, don't need to test. It's a lose - lose situation when it comes to testing, but this is more of an interlectual exercise ;)

rory
11-29-2007, 04:40 PM
My family is from Antrim originally. (I'm not really from anywhere in particular)

Zack Barresse
11-30-2007, 02:14 AM
For multi-dimension array's..

Private Function CountNumOfElement(ByVal buf) As String
Dim iDims As Long, iInt As Long, iRows As Long, iCols As Long, lb As Long
Dim DummyArr() As Variant
iDims = 1: iRows = 1
If TypeName(buf) Like "Variant()" Then
DummyArr = buf
iDims = GetNumberOfDim(DummyArr)
End If
For iInt = 1 To iDims
lb = LBound(buf, iInt)
iCols = UBound(buf, iInt)
iRows = iRows * (iCols - lb + 1)
Next
CountNumOfElement = CStr(iRows & "," & iCols)
End Function

Private Function GetNumberOfDim(ByRef vMultiDimArr As Variant) As Long
'http://puremis.net/excel/code/076.shtml
Dim i As Long, j As Long, k As Long
If VarType(vMultiDimArr) & vbArray = 0 Then
GetNumberOfDim = -1
Exit Function
End If
RtlMoveMemory i, ByVal VarPtr(vMultiDimArr) + 8, 4
RtlMoveMemory j, ByVal i, 4
RtlMoveMemory k, ByVal j, 2 'Get Number of Dimensions
GetNumberOfDim = k
End Function

I know it's kind of redundant, but I was playing one day with unique array items...
Option Explicit

Declare Sub RtlMoveMemory Lib "kernel32" ( _
Destination As Any, _
Source As Any, _
ByVal Length As Long)

Sub testUL()
Dim x, y, s As String
If Selection.Cells.Count = 1 Then
s = Selection
GoTo OnlyOneItem
End If
x = UniqueList(Selection)
For Each y In x
s = s & y & vbNewLine
Next y
OnlyOneItem:
MsgBox s
End Sub

Sub testUL2()
Dim x As Variant, y As Variant, s As String
x = UniqueList(Array("a", "a", "b", "b", "B", 1, 2, 3))
For Each y In x
s = s & y & vbNewLine
Next y
MsgBox s
End Sub
Function UniqueList(varInput As Variant) As Variant
Dim Dic As Object, dicItem As Object, blnArray As Boolean
Dim aTemp As Variant, arrUnique() As Variant, vItem As Variant
Dim iRow As Long, iCol As Long, iDim() As String, iCnt As Long
Set Dic = CreateObject("Scripting.Dictionary")
Dic.CompareMode = vbTextCompare
If TypeName(varInput) = "Range" Then aTemp = varInput.value
If IsEmpty(aTemp) Then aTemp = varInput: blnArray = True
iDim = Split(CountNumOfElement(aTemp), ",")
iCnt = 1
For iCol = 1 To iDim(UBound(iDim))
If blnArray = True Then
If Not Dic.exists(aTemp(iCol)) Then
If IsEmpty(aTemp(iCol)) = False Then
Dic.Add aTemp(iCol), iCnt
End If
iCnt = iCnt + 1
End If
Else
For iRow = 1 To iDim(LBound(iDim)) / iDim(UBound(iDim))
If Not Dic.exists(aTemp(iRow, iCol)) Then
If IsEmpty(aTemp(iRow, iCol)) = False Then
Dic.Add aTemp(iRow, iCol), iCnt
End If
iCnt = iCnt + 1
End If
Next iRow
End If
Next iCol
iCnt = 1
For Each vItem In Dic
ReDim Preserve arrUnique(1 To iCnt)
arrUnique(iCnt) = vItem
iCnt = iCnt + 1
Next vItem
UniqueList = arrUnique
End Function

Private Function CountNumOfElement(ByVal buf) As String
Dim iDims As Long, iInt As Long, iRows As Long, iCols As Long, lb As Long
Dim DummyArr() As Variant
iDims = 1: iRows = 1
If TypeName(buf) Like "Variant()" Then
DummyArr = buf
iDims = GetNumberOfDim(DummyArr)
End If
For iInt = 1 To iDims
lb = LBound(buf, iInt)
iCols = UBound(buf, iInt)
iRows = iRows * (iCols - lb + 1)
Next
CountNumOfElement = CStr(iRows & "," & iCols)
End Function

Private Function GetNumberOfDim(ByRef vMultiDimArr As Variant) As Long
'http://puremis.net/excel/code/076.shtml
Dim i As Long, j As Long, k As Long
If VarType(vMultiDimArr) & vbArray = 0 Then
GetNumberOfDim = -1
Exit Function
End If
RtlMoveMemory i, ByVal VarPtr(vMultiDimArr) + 8, 4
RtlMoveMemory j, ByVal i, 4
RtlMoveMemory k, ByVal j, 2 'Get Number of Dimensions
GetNumberOfDim = k
End Function

HTH