How to use nextind in a while loop [duplicate] - julia

This question already has answers here:
While without global
(2 answers)
Closed 3 years ago.
nextind doesn't work inside a while loop
It works executing step by step at julia> command line but no within a while loop
mezclado = "AαBβ"
tamaño = sizeof(mezclado)
i = 1
while i ≤ tamaño
print(mezclado[i], " ")
i = nextind(mezclado, i)
end
I expect A α B β
I obtain UndefVarError: i not defined

write
i = 1
while i ≤ tamaño
print(mezclado[i], " ")
global i = nextind(mezclado, i)
end
or
let i = 1
while i ≤ tamaño
print(mezclado[i], " ")
i = nextind(mezclado, i)
end
end
to get what you want.
(the problem is not related to nextind but to Julia scoping rules in global scope, see https://docs.julialang.org/en/latest/manual/variables-and-scoping/#Local-Scope-1 for a detailed explanation). In short: i inside a while loop is a local variable unless you declare it global (the first approach). The second approach (with let) creates an enclosing local scope which defines i as a local variable and you can update it in a nested local scope.

Related

iterating 2D array in Elixir

I am new to Elixir language and I am having some issues while writing a piece of code.
What I am given is a 2D array like
list1 = [
[1 ,2,3,4,"nil"],
[6,7,8,9,10,],
[11,"nil",13,"nil",15],
[16,17,"nil",19,20] ]
Now, what I've to do is to get all the elements that have values between 10 and 20, so what I'm doing is:
final_list = []
Enum.each(list1, fn row ->
Enum.each(row, &(if (&1 >= 10 and &1 <= 99) do final_list = final_list ++ &1 end))
end
)
Doing this, I'm expecting that I'll get my list of numbers in final_list but I'm getting blank final list with a warning like:
warning: variable "final_list" is unused (there is a variable with the same name in the context, use the pin operator (^) to match on it or prefix this variable with underscore if it is not meant to be used)
iex:5
:ok
and upon printing final_list, it is not updated.
When I try to check whether my code is working properly or not, using IO.puts as:
iex(5)> Enum.each(list1, fn row -> ...(5)> Enum.each(row, &(if (&1 >= 10 and &1 <= 99) do IO.puts(final_list ++ &1) end))
...(5)> end
...(5)> )
The Output is:
10
11
13
15
16
17
19
20
:ok
What could I possibly be doing wrong here? Shouldn't it add the elements to the final_list?
If this is wrong ( probably it is), what should be the possible solution to this?
Any kind of help will be appreciated.
As mentioned in Adam's comments, this is a FAQ and the important thing is the message "warning: variable "final_list" is unused (there is a variable with the same name in the context, use the pin operator (^) to match on it or prefix this variable with underscore if it is not meant to be used)" This message actually indicates a very serious problem.
It tells you that the assignment "final_list = final_list ++ &1" is useless since it just creates a local variable, hiding the external one. Elixir variables are not mutable so you need to reorganize seriously your code.
The simplest way is
final_list =
for sublist <- list1,
n <- sublist,
is_number(n),
n in 10..20,
do: n
Note that every time you write final_list = ..., you actually declare a new variable with the same name, so the final_list you declared inside your anonymous function is not the final_list outside the anonymous function.

Julia throws undefined error when the variable is defined

I have this simple while loop that uses i = 1 as an index.
global i = 1
n = rows
while i <= n
if prod(isa.(collect((y)[i,:]),Number))==0
delete!(y,i)
x_axis = x_axis[1:end .!= i]
n -= 1
end
i += 1
end
but I'm getting this error:
UndefVarError: i not defined
top-level scope#Local: 23
I even made my i global as per the suggestion on some similar questions on SO but the error persists. I am running this on Pluto.jl so maybe it could be an environment issue.
Firstly, note that if you use Julia v1.5+ then you don't need to make i a global (example below with current stable version v1.5.4):
julia> i = 1
1
julia> while i < 5
println(i)
i += 1 # <----- this works in Julia v1.5+
end
1
2
3
4
However, it seems you are using Julia v1.4 of older, in which case I think Logan Kilpatrick gave you the answer: You need to make i a global from inside the while loop's scope. As Logan mentioned, try adding global where you increment i, like in this example from the while function's docs:
julia> i = 1 ;
julia> while i < 5
println(i)
global i += 1 # <------------ Try this!
end
1
2
3
4
Note also that you don't need to specify it's a global if your while loop is inside a function, as in
julia> function foo(istart)
i = istart
while i < 5
println(i)
i += 1 # <-- 'global' not needed inside a function!
end
end
foo (generic function with 1 method)
julia> foo(1)
1
2
3
4
You have hitted the "ambiguous soft scope case".
In short: the assignment of a local variable inside a (soft) local scope depends if the code is inside a "REPL context"
or not.
For "REPL context" I mean the REPL and all environments that behaves as the REPL in this case, for example
Jupyter:
julia> i = 0
julia> while i < 3
i += 1
#info i
end
[ Info: 1
[ Info: 2
[ Info: 3
Instead code from non interactive context like file, eval and Pluto acts like that:
julia> code = """
i = 0
while i < 3
i += 1
#info i
end
"""
julia> include_string(Main, code)
┌ Warning: Assignment to `i` in soft scope is ambiguous because a global variable by the same name exists: `i` will be treated as a new local. Disambiguate by using `local i` to suppress this warning or `global i` to assign to the existing global variable.
└ # string:3
ERROR: LoadError: UndefVarError: i not defined
All of this has been designed to ensure both the convenience of REPL usage and to avoid unwanted side effects of using julia on a large scale.
Full details here.
To fix the problem you may be use global as already suggested or enclose your code inside a function.
Pluto implicitly wraps a cell into a function, see https://github.com/fonsp/Pluto.jl/pull/720, therefore the global annotation or explicit wrapping into a function should not be required.
Putting the following into a Pluto cells works for me:
begin
i = 1
n = 100
while i<=n
if i % 2 == 0
n -= 1
end
i += 1
end
end
The implicit function wrapping is disabled when macros are used inside a cell (which prevents Pluto to gather reactivity information), therefore the following does not work in Pluto due to the Julia scoping rules:
begin
i = 1
n = 100
while i<=n
if i % 2 == 0
n -= 1
end
#show i += 1
end
end
Throws:
UndefVarError: i not defined
top-level scope#Local: 5[inlined]
top-level scope#none:0

Evaluate expression with local variables

I'm writing a genetic program in order to test the fitness of randomly generated expressions. Shown here is the function to generate the expression as well a the main function. DIV and GT are defined elsewhere in the code:
function create_single_full_tree(depth, fs, ts)
"""
Creates a single AST with full depth
Inputs
depth Current depth of tree. Initially called from main() with max depth
fs Function Set - Array of allowed functions
ts Terminal Set - Array of allowed terminal values
Output
Full AST of typeof()==Expr
"""
# If we are at the bottom
if depth == 1
# End of tree, return function with two terminal nodes
return Expr(:call, fs[rand(1:length(fs))], ts[rand(1:length(ts))], ts[rand(1:length(ts))])
else
# Not end of expression, recurively go back through and create functions for each new node
return Expr(:call, fs[rand(1:length(fs))], create_single_full_tree(depth-1, fs, ts), create_single_full_tree(depth-1, fs, ts))
end
end
function main()
"""
Main function
"""
# Define functional and terminal sets
fs = [:+, :-, :DIV, :GT]
ts = [:x, :v, -1]
# Create the tree
ast = create_single_full_tree(4, fs, ts)
#println(typeof(ast))
#println(ast)
#println(dump(ast))
x = 1
v = 1
eval(ast) # Error out unless x and v are globals
end
main()
I am generating a random expression based on certain allowed functions and variables. As seen in the code, the expression can only have symbols x and v, as well as the value -1. I will need to test the expression with a variety of x and v values; here I am just using x=1 and v=1 to test the code.
The expression is being returned correctly, however, eval() can only be used with global variables, so it will error out when run unless I declare x and v to be global (ERROR: LoadError: UndefVarError: x not defined). I would like to avoid globals if possible. Is there a better way to generate and evaluate these generated expressions with locally defined variables?
Here is an example for generating an (anonymous) function. The result of eval can be called as a function and your variable can be passed as parameters:
myfun = eval(Expr(:->,:x, Expr(:block, Expr(:call,:*,3,:x) )))
myfun(14)
# returns 42
The dump function is very useful to inspect the expression that the parsers has created. For two input arguments you would use a tuple for example as args[1]:
julia> dump(parse("(x,y) -> 3x + y"))
Expr
head: Symbol ->
args: Array{Any}((2,))
1: Expr
head: Symbol tuple
args: Array{Any}((2,))
1: Symbol x
2: Symbol y
typ: Any
2: Expr
[...]
Does this help?
In the Metaprogramming part of the Julia documentation, there is a sentence under the eval() and effects section which says
Every module has its own eval() function that evaluates expressions in its global scope.
Similarly, the REPL help ?eval will give you, on Julia 0.6.2, the following help:
Evaluate an expression in the given module and return the result. Every Module (except those defined with baremodule) has its own 1-argument definition of eval, which evaluates expressions in that module.
I assume, you are working in the Main module in your example. That's why you need to have the globals defined there. For your problem, you can use macros and interpolate the values of x and y directly inside the macro.
A minimal working example would be:
macro eval_line(a, b, x)
isa(a, Real) || (warn("$a is not a real number."); return :(throw(DomainError())))
isa(b, Real) || (warn("$b is not a real number."); return :(throw(DomainError())))
return :($a * $x + $b) # interpolate the variables
end
Here, #eval_line macro does the following:
Main> #macroexpand #eval_line(5, 6, 2)
:(5 * 2 + 6)
As you can see, the values of macro's arguments are interpolated inside the macro and the expression is given to the user accordingly. When the user does not behave,
Main> #macroexpand #eval_line([1,2,3], 7, 8)
WARNING: [1, 2, 3] is not a real number.
:((Main.throw)((Main.DomainError)()))
a user-friendly warning message is provided to the user at parse-time, and a DomainError is thrown at run-time.
Of course, you can do these things within your functions, again by interpolating the variables --- you do not need to use macros. However, what you would like to achieve in the end is to combine eval with the output of a function that returns Expr. This is what the macro functionality is for. Finally, you would simply call your macros with an # sign preceding the macro name:
Main> #eval_line(5, 6, 2)
16
Main> #eval_line([1,2,3], 7, 8)
WARNING: [1, 2, 3] is not a real number.
ERROR: DomainError:
Stacktrace:
[1] eval(::Module, ::Any) at ./boot.jl:235
EDIT 1. You can take this one step further, and create functions accordingly:
macro define_lines(linedefs)
for (name, a, b) in eval(linedefs)
ex = quote
function $(Symbol(name))(x) # interpolate name
return $a * x + $b # interpolate a and b here
end
end
eval(ex) # evaluate the function definition expression in the module
end
end
Then, you can call this macro to create different line definitions in the form of functions to be called later on:
#define_lines([
("identity_line", 1, 0);
("null_line", 0, 0);
("unit_shift", 0, 1)
])
identity_line(5) # returns 5
null_line(5) # returns 0
unit_shift(5) # returns 1
EDIT 2. You can, I guess, achieve what you would like to achieve by using a macro similar to that below:
macro random_oper(depth, fs, ts)
operations = eval(fs)
oper = operations[rand(1:length(operations))]
terminals = eval(ts)
ts = terminals[rand(1:length(terminals), 2)]
ex = :($oper($ts...))
for d in 2:depth
oper = operations[rand(1:length(operations))]
t = terminals[rand(1:length(terminals))]
ex = :($oper($ex, $t))
end
return ex
end
which will give the following, for instance:
Main> #macroexpand #random_oper(1, [+, -, /], [1,2,3])
:((-)([3, 3]...))
Main> #macroexpand #random_oper(2, [+, -, /], [1,2,3])
:((+)((-)([2, 3]...), 3))
Thanks Arda for the thorough response! This helped, but part of me thinks there may be a better way to do this as it seems too roundabout. Since I am writing a genetic program, I will need to create 500 of these ASTs, all with random functions and terminals from a set of allowed functions and terminals (fs and ts in the code). I will also need to test each function with 20 different values of x and v.
In order to accomplish this with the information you have given, I have come up with the following macro:
macro create_function(defs)
for name in eval(defs)
ex = quote
function $(Symbol(name))(x,v)
fs = [:+, :-, :DIV, :GT]
ts = [x,v,-1]
return create_single_full_tree(4, fs, ts)
end
end
eval(ex)
end
end
I can then supply a list of 500 random function names in my main() function, such as ["func1, func2, func3,.....". Which I can eval with any x and v values in my main function. This has solved my issue, however, this seems to be a very roundabout way of doing this, and may make it difficult to evolve each AST with each iteration.

Can I use <- instead of = in Julia?

Like in R:
a <- 2
or even better
a ← 2
which should translate to
a = 2
and if possible respect method overloading.
= is overloaded (not in the multiple dispatch sense) a lot in Julia.
It binds a new variable. As in a = 3. You won't be able to use ← instead of = in this context, because you can't overload binding in Julia.
It gets lowered to setindex!. As in, a[i] = b gets lowered to setindex!(a, b, i). Unfortunately, setindex! takes 3 variables while ← can only take 2 variables. So you can't overload = with 3 variables.
But, you can use only 2 variables and overload a[:] = b, for example. So, you can define ←(a,b) = (a[:] = b) or ←(a,b) = setindex!(a,b,:).
a .= b gets lowered to (Base.broadcast!)(Base.identity, a, b). You can overload this by defining ←(a,b) = (a .= b) or ←(a,b) = (Base.broadcast!)(Base.identity, a, b).
So, there are two potentially nice ways of using ←. Good luck ;)
Btw, if you really want to use ← to do binding (like in 1.), the only way to do it is using macros. But then, you will have to write a macro in front of every single assignment, which doesn't look very good.
Also, if you want to explore how operators get lowered in Julia, do f(a,b) = (a .= b), for example, and then #code_lowered f(x,y).
No. = is not an operator in Julia, and cannot be assigned to another symbol.
Disclaimer: You are fully responsible if you will try my (still beginner's) experiments bellow! :P
MacroHelper is module ( big thanks to #Alexander_Morley and #DanGetz for help ) I plan to play with in future and we could probably try it here :
julia> module MacroHelper
# modified from the julia source ./test/parse.jl
function parseall(str)
pos = start(str)
exs = []
while !done(str, pos)
ex, pos = parse(str, pos) # returns next starting point as well as expr
ex.head == :toplevel ? append!(exs, ex.args) : push!(exs, ex)
end
if length(exs) == 0
throw(ParseError("end of input"))
elseif length(exs) == 1
return exs[1]
else
return Expr(:block, exs...) # convert the array of expressions
# back to a single expression
end
end
end;
With module above you could define simple test "language":
julia> module TstLang
export #tst_cmd
import MacroHelper
macro tst_cmd(a)
b = replace("$a", "←", "=") # just simply replacing ←
# in real life you would probably like
# to escape comments, strings etc
return MacroHelper.parseall(b)
end
end;
And by using it you could probably get what you want:
julia> using TstLang
julia> tst```
a ← 3
println(a)
a +← a + 3 # maybe not wanted? :P
```
3
9
What about performance?
julia> function test1()
a = 3
a += a + 3
end;
julia> function test2()
tst```
a ← 3
a +← a + 3
```
end;
julia> test1(); #time test1();
0.000002 seconds (4 allocations: 160 bytes)
julia> test2(); #time test2();
0.000002 seconds (4 allocations: 160 bytes)
If you like to see syntax highlight (for example in atom editor) then you need to use it differently:
function test3()
#tst_cmd begin
a ← 3
a ← a + a + 3 # parser don't allow you to use "+←" here!
end
end;
We could hope that future Julia IDEs could syntax highlight cmd macros too. :)
What could be problem with "solution" above? I am not so experienced julian so many things. :P (in this moment something about "macro hygiene" and "global scope" comes to mind...)
But what you want is IMHO good for some domain specific languages and not to redefine basic of language! It is because readability very counts and if everybody will redefine everything then it will end in Tower of Babel...

VB: how to get the index(es) of an array element if used in For Each

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 ?
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.
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.
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.
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
a pointer (memory address) - as e.g. C/C++ does; then you have to
de-reference the pointer to get at the element which you can even
change; positional info is optainable by pointer arithmetic)
a reference (alias) (as e.g. Perl does; that allows modification,
but obviously no computing of positions (unless the element
accidentially contains such info))
a copy (as e.g. Python or VBScript do; neither modification nor
retrieval of meta info is possible (unless some kind and clever
souls like AutomatedChaos or AnthonyWJones work their heart out to
implement a C/C++ alike solution by submitting a loop variable to
DIVs and MODs resp. to design a class that allows to augment the
plain/essential data values with meta info)
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
data: it's important that Mary scored 9 for the eval test, but
whether that info is stored in row 5 or 96 is not an inherent
property of the data [Implies that you should think twice before you
embark on the (taken by itself: impressive) idea of AutomatedChaos
to create objects that combine (essential) data and (accidential)
info about positions in a (n arbitrary) structure.]
processing: some computations - especially those that involve the
whole dataset - can be done with no regard to rows or colums (e.g.
average of all scores); some may even require a
restructuring/reordering (e.g. median of all scores); many
computations - all that involve selection/grouping/subsets of the
data - just can't be done without intimate knowledge about the
positions of the data items. armen, however, may not be interested
in processing at all - perhaps all he needs the indices for is to
identify the elements while displaying them. [So it's futile to
speculate about questions like "Shouldn't Excel/the database do the
processing?", "Will the reader be content with 'D5: 9' or does he
whish to see 'Mary/eval: 9' - and would such info be a better
candidate for AutomatedChaos' class?", "What good is a general 'For
Each' based function/sub that handles arrays of every dimension, if
assignments - a(i)=, b(i,j)=, c(i,j,k)= ... - can't be
parameterized?"]
structure/layout: the choice of how you put your data into rows and
columns is determined by convenience (vertical scrolling perfered),
practical considerations (append new data 'at the end'), and
technical reasons (VBScript's 'ReDim Preserve' can grow (dynamic)
arrays in the last dimension only) - so for each layout that makes
sense for a given context/task there are many other structures that
are better in other circumstances (or even the first context).
Certainly there is no 'natural order of indexers'.
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:
Mr. Stumblegates type system hides the fact that these two arrays
have a very different nature (and the difference between fixed and
dynamic arrays is ignored too)
You can create all kinds of arrays in VBScript as long as they are
zero-based (no chance of creating and/or restructuring Range-born
arrays and keep their (accidential!) one-based-ness)
Getting one set of data with (necessarily) one layout via two
different methods will deliver the data with two different
structures
If you ask "For Each" to enumerate the data, the sequence you get is
determined by the iterator and not predictable (you have to
check/experiment). (Accentuating the freedom/role of the iterator is
the one nugget in AutomatedChaos' first answer)
[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?
]
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

Resources