Reputation: 1283
i have the following VB Script written in an .asp file
Dim myArr(5, 6) '// a 6 * 7 array
For i = LBound(myArr, 1) to UBound(myArr, 1)
For j = LBound(myArr, 1) to UBound(myArr, 1)
myArr(i, j) = "i:" & i & ", j:" & j
next
next
Dim i
i = 0
For Each k In myArr
Response.Write("i:" & i ", k:" & k & "<br>")
i = i + 1
Next
using For Each i can iterate through all the array items, and the question is how can i get the index for each dimension ?
for example: how can i get k index after 10th loop that is 2 and 4 ?
Upvotes: 2
Views: 12377
Reputation: 38755
UPDATED
If a person interested in how VBScript compares to other languages with regard to arrays, foreach looping, and especially obtaining information about the position of the element delivered by "For Each" in the collection looped over, would pose a question like:
How does VBScript compare to other languages with regard to arrays, foreach looping, and especially obtaining information about the position of the element delivered by "For Each" in the collection looped over?
then a short answer would have been available long ago:
A foreach loop construct can deliver
You may safely ignore the rest of my answer; I just don't want to delete the following text which provides some context for the discussion.
The problem can't be dealt with, until
(1) armen describes the context of the real world problem in real world terms - where do the arrays come from, how many dimensions are possible, what determines the dimensional structure (row/column/...), which operations must be done in the For Each loop, why/how are the indices important for these operations
(2) all contributors get their selectors for the dimensions right:
For i = LBound(myArr, 1) to UBound(myArr, 1)
For j = LBound(myArr, 1) to UBound(myArr, 1)
or variations thereof are obviously wrong/misleading. Without replacing the 1 in one line by 2, it's not clear, what row/column-structure the code is meant for.
To prove that I'm willing to contribute in a more constructive way, I throw in a function to get the (number of) dimensions for an arbitrary array:
Function getDimensions(aVBS)
Dim d : d = 0
If IsArray(aVBS) Then
For d = 1 To 60
On Error Resume Next
UBound aVBS, d + 1
If Err.Number Then Exit For
On Error GoTo 0
Next
End If
getDimensions = d
End Function ' getDimensions
(based on code by M. Harris and info from the VBScript Docs)
Update: Still not a solution, but some food for thought
As armen (upto now) didn't provide the real story of his problem, I try to give you a fictonal one (to put a context to the rows and columns and whatever you may call the thingies in the third dimension):
Let's say there is a school - Hogmond - teaching magical programming. VBScript is easy (but in the doghouse), so there are just three tests and students are admitted mid term (every penny counts). JScript is harder, so you have to do the full course and additional tests may be sheduled during the term, if pupils prove thick. F# is more complicated, so each test has to be judged in terms of multiple criteria, some of which may be be agreed upon during the term (the teachers are still learning). C# is such a 'good' language, that there is just one test.
So at the end of the term the principal - Mr. Bill 'Sauron' Stumblegates - has an .xls, containing a sheet:
(Doreen was accepted during the last week of the term) and a sheet:
(for your peace of mind, 120 additional tests are hidden); the F# results are kept in a .txt file:
# Results of the F# tests
# 2 (fixed) students, 3 (fixed) test,
# 4>5 (dynamic) criteria for each test
Students Ann Bill
Test TA TB TC TA TB TC
Criteria
CA 1 2 3 4 5 6
CB 7 8 9 10 11 12
CC 13 14 15 16 17 18
CD 19 20 21 22 23 24
# CE 25 26 27 28 29 30
(because I know nothing about handling three+-dimensional data in Excel).
Now we have a context to think about
Now most programmers love writing/written code more than reading stories (and some more than to think about/plan/design code), so here is just one example to show what different beasts (arrays, 'iterators') our pipe dream/magical one-fits-all-dimensions 'For Each' strategy has to cope with:
Given two functions that let you cut data from Excel sheets:
Function getXlsRange(sSheet, sRange)
Dim oX : Set oX = CreateObject("Excel.Application")
Dim oW : Set oW = oX.Workbooks.Open(resolvePath("..\data\hogmond.xls"))
getXlsRange = oW.Sheets(sSheet).Range(sRange).Value
oW.Close
oX.Quit
End Function ' getXlsRange
Function getAdoRows(sSQL)
Dim oX : Set oX = CreateObject("ADODB.Connection")
oX.open Join(Array( _
"Provider=Microsoft.Jet.OLEDB.4.0" _
, "Data Source=" & resolvePath("..\data\hogmond.xls") _
, "Extended Properties=""" _
& Join(Array( _
"Excel 8.0" _
, "HDR=No" _
, "IMEX=1" _
), ";" ) _
& """" _
), ";")
getAdoRows = oX.Execute(sSQL).GetRows()
oX.Close
End Function ' getAdoRows
(roll your own resolvePath() function or hard code the file spec)
and a display Sub (that uses armen's very good idea to introduce a loop counter variable):
Sub showAFE(sTitle, aX)
Dim i, e
WScript.Echo "For Each:", sTitle
WScript.Echo "type:", VarType(aX), TypeName(aX)
WScript.Echo "dims:", getDimensions(aX)
WScript.Echo "lb :", LBound(aX, 1), LBound(aX, 2)
WScript.Echo "ub :", UBound(aX, 1), UBound(aX, 2)
WScript.Echo "s :", UBound(aX, 1) - LBound(aX, 1) + 1 _
, UBound(aX, 2) - LBound(aX, 2) + 1
i = 0
For Each e In aX
WScript.Echo i & ":", e
i = i + 1
Next
End Sub ' showAFE
you can use code like
showAFE "VTA according to XlsRange:", getXlsRange("VTA", "B3:D4")
showAFE "VTA according to AdoRows:", getAdoRows("SELECT * FROM [VTA$B3:D4]")
to get your surprise of the weekend:
For Each: VTA according to XlsRange:
type: 8204 Variant()
dims: 2
lb : 1 1
ub : 2 3
s : 2 3
0: 1
1: 2
2: 3
3: 4
4: 5
5: 6
For Each: VTA according to AdoRows:
type: 8204 Variant()
dims: 2
lb : 0 0
ub : 2 1
s : 3 2
0: 1
1: 3
2: 5
3: 2
4: 4
5: 6
and despair:
[Don't read this, if you aren't interested in/can't stand a pedantic diatribe:
which still has a better score than AnthonyWJones' contribution, because at least one person who admittedly has anderstood neither question nor answer upvotes it, because of the reference to .ArrayList - which isn't relevant at all to armen's question, because there is no way to make an ArrayList multi-dimensional (i.e.: accessible by the equivalent of al(1,2,3)). Yes "IEnumerable" (a pure .NET concept) and "multithread" are impressive keywords and there are 'live' collections (e.g. oFolder.Files) that reflect 'on the fly' modifications, but no amount of (single!)-threading will let you modify a humble VBScript array while you loop - Mr. Stumblegates is a harsh master:
Dim a : a = Array(1, 2, 3)
Dim e
WScript.Stdout.WriteLine "no problem looping over (" & Join(a, ", ") & ")"
For Each e In a
WScript.Stdout.Write " " & e
Next
ReDim Preserve a(UBound(a) + 1)
a(UBound(a)) = 4
WScript.Stdout.WriteLine
WScript.Stdout.WriteLine "no problem growing the (dynamic) array (" & Join(a, ", ") & ")"
WScript.Stdout.WriteLine "trying to grow in loop"
For Each e In a
WScript.Stdout.Write " " & e
If e = 3 Then
On Error Resume Next
ReDim Preserve a(UBound(a) + 1)
If Err.Number Then WScript.Stdout.Write " " & Err.Description
On Error GoTo 0
a(UBound(a)) = 5
End If
Next
WScript.Stdout.WriteLine
output:
no problem looping over (1, 2, 3)
1 2 3
no problem growing the (dynamic) array (1, 2, 3, 4)
trying to grow in loop
1 2 3 This array is fixed or temporarily locked 5
Another elaboration of a blanket statement: Even good programmers make mistakes, especially if they are eager to help, have to work under unfavorable conditions (Mr. Stumblegates did his utmost to make sure that you can't use/publish VBScript code without extensive testing), have a job and a live, or just a bad moment.
This, however, does not change the fact that some code fragments/statements are useless or even dangerous to SO readers who - because of votes - think they have found a solution to their problem. Quality of code/text is an essential property of the content alone, who wrote it is just accidential. But how to be 'objective' in a context where "Jon Doe's code" is the natural way to refer to lines like
for i = 0 to ubound(myArr)
for y = 0 to ubound(myArr, 1)
[UBound(a) and UBound(a, 1) are synonyms, so this will create havoc as soon
as the UBounds of the different dimensions are not (accidentially) the same]
and votes for content are summed up under the reputations of persons? (Would SO list millions of answers without the reputation system? Would I put less time/work in my contributions without the points? I hope/guess not, but I'm a human too.)
So I encourage you to downvote this elaborate (at least) until I correct the limit of 60 in my getDimensions() function. You can't hurt my feelings; I think I'm blameless, because all I did was to rely on the docs:
Dimensions of an array variable; up to 60 multiple dimensions may be declared.
(What I'm a bit ashamed of is that I had feelings of superiority, when I looked at the 999 or the 60000 in other people's code - as I said: I'm only human too; and: Don't put your trust in Mr. Stumblegates, but check:
Dim nDim
For Each nDim In Array(3, 59, 60, 62, 64, 65, 70)
ReDim aDim(nDim)
Dim sDim : sDim = "ReDim a" & nDim & "(" & Mid(Join(aDim, ",0"), 2) & ")"
WScript.Echo sDim
On Error Resume Next
Execute sDim
If Err.Number Then WScript.Echo Err.Description
On Error GoTo 0
Next
output:
ReDim a3(0,0,0)
...
ReDim a64(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0)
ReDim a65(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
Subscript out of range
...
)
Not to conclude in destructive mode: I still hope that my harping on the topic "bounds used in nested loops to specify indexers (especially such of different ranges)" will magically cause a lot of code lines here to be changed in the near future - or aren't we all students at Hogmond?
]
Upvotes: 1
Reputation: 1436
Use nested For loops, instead of For Each
for i = 0 to ubound(myArr)
for y = 0 to ubound(myArr, 2)
' do something here...
next
next
Upvotes: 0
Reputation: 189495
Useful info number 1
First of consider this bit of VBS:
Option Explicit
Dim aaa(1,1,1)
Dim s : s = ""
Dim i, j, k
For i = LBound(aaa, 3) To UBound(aaa, 3)
For j = LBound(aaa, 2) To UBound(aaa, 2)
For k = LBound(aaa, 1) To UBound(aaa, 1)
aaa(k, j, i) = 4 * i + 2 * j + k
Next
Next
Next
Dim x
For Each x in aaa
s = s + CStr(x) + " : "
Next
MsgBox s
This returns "0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 :" which looks good, but note the order of indexers in the inner assignment aaa(k, j, i)
. If we were to use the more natural aaa(i, j, k)
we'd see what appears to us to be a jubbled order returned. Thats because we assume that the left most indexer is the most significant but it isn't its the least significant.
Where bounds start at 0 then for the first dimension all the values in index 0..N are held contigiously where the other dimensions are 0. Then with the next dimension at 1, the next set of 0..N members of the first dimension follow and so on.
Useful info number 2
Given an array of unknown number of dimensions the following code returns the count of dimensions:
Function GetNumberOfDimensions(arr)
On Error Resume Next
Dim i
For i = 1 To 60000
LBound arr, i
If Err.Number <> 0 Then
GetNumberOfDimensions = i - 1
Exit For
End If
Next
End Function
Solution
Given an array construct like this.
Dim arr(3,3,3)
Dim s : s = ""
Dim i, j, k
For i = LBound(arr, 3) To UBound(arr, 3)
For j = LBound(arr, 2) To UBound(arr, 2)
For k = LBound(arr, 1) To UBound(arr, 1)
arr(k, j, i) = 16 * i + 4 * j + k
Next
Next
Next
Here is some code that is able to determine the set of indices for each item in an array of arbitary dimensions and sizes.
Dim dimCount : dimCount = GetNumberOfDimensions(arr)
Redim dimSizes(dimCount - 1)
For i = 1 To dimCount
dimSizes(i - 1) = UBound(arr, i) - LBound(arr, i) + 1
Next
Dim index : index = 0
Dim item
For Each item in arr
s = "("
Dim indexValue, dimIndex
indexValue = index
For dimIndex = 0 To dimCount - 1
s = s + CStr((indexValue mod dimSizes(dimIndex)) - LBound(arr, dimIndex + 1)) + ", "
indexValue = indexValue \ dimSizes(dimIndex)
Next
Response.Write Left(s, Len(s) - 2) + ") = " + Cstr(item) + "<br />"
index = index + 1
Next
An interesting acedemic exercise, not sure how useful it is.
Upvotes: 2
Reputation: 7490
You could create a helper object like this:
Option Explicit
dim myArr(5,6)
dim i, j, k
For i = LBound(myArr, 1) to UBound(myArr, 1)
For j = LBound(myArr, 2) to UBound(myArr, 2)
Set myArr(i, j) = [new LookupObject]("i:" & i & ", j:" & j, i, j)
next
next
For Each k In myArr
Response.Write("This is k:" & k & "<br>")
Response.Write("i index of k: " & k.I & "<br>")
Response.Write("j index of k: " & k.J & "<br>")
Next
Public Function [new LookupObject](value, i, j)
Set [new LookupObject] = (new cls_LookupObject).Init(value, i, j)
End Function
Class cls_LookupObject
Private value_, i_, j_
Public Function Init(value, i, j)
i_ = i
j_ = j
value_ = value
Set Init = me
End Function
Public Default Property Get Value()
Value = value_
End Property
Public Property Get I()
I = i_
End Property
Public Property Get J()
J = j_
End Property
End Class
DISCLAIMER: As I created this code on a non Windows machine, I couldn't test it. You could find some syntax or design errors. The naming is not very great, but this way it sticks more to your concept.
Although, it seems you are searching for a simple solution. Not one that will introduce more 'challenges': When you want to pass around values in the array that keep their internal indices, you need to Set
them instead of just assigning them: this decreases portability.
And when you use objects, you need to know how Object References work in contrast to primitives, otherwise you'll get some unexpected behavior of values changing when you don't expected it.
Upvotes: 1
Reputation: 7490
You can't. For each is defined to iterate over objects without having to know the amount of objects (as defined in the IEnumerable interface) at the moment the next object is returned (making multithreading possible).
It is also not specified that you'll receive your objects in exact the same order as you put them (although, I never experienced an other order for arrays), that depends on the Enumerator Interface object that is specified for the collection.
Fortunately, there are other tricks to do what you want, but the implementation depends on the problem you are trying to solve.
For example, you can use an array with arrays, the ArrayList class from System.Collections.ArrayList or create an own class where you store your values or objects.
Please note: There are some discussions about this correctness of this answer, see the comments below. I'll study the subject and will share any relevant experiences I got from them.
Upvotes: 1