2

我在 .asp 文件中编写了以下 VB 脚本

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

使用 For Each 我可以遍历所有数组项,问题是如何获取每个维度的索引?

例如:如何在第 10 个循环后获得 k 索引,即 2 和 4 ?

4

5 回答 5

2

有用的信息编号 1

首先考虑这一点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    

这将返回 "0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 :" 看起来不错,但请注意内部分配中索引器的顺序aaa(k, j, i)。如果我们使用更自然的aaa(i, j, k),我们会看到在我们看来是一个杂乱无章的订单返回。那是因为我们假设最左边的索引器是最重要的,但它不是最不重要的。

边界从 0 开始,然后对于第一个维度,索引 0..N 中的所有值在其他维度为 0 的情况下连续保存。然后下一个维度为 1,下一组 0..N 个成员的第一个维度跟随等等。

有用信息 2

给定一个未知维数的数组,以下代码返回维数:

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

解决方案

给定这样的数组结构。

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

这是一些能够确定任意维度和大小数组中每个项目的索引集的代码。

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

一个有趣的学术练习,不确定它有多大用处。

于 2012-06-08T00:07:23.113 回答
1

你不能。For each 被定义为在返回下一个对象时迭代对象而不必知道对象的数量(在IEnumerable接口中定义)(使多线程成为可能)。

也没有指定您接收对象的顺序与放置它们的顺序完全相同(尽管我从未经历过数组的其他顺序),这取决于为集合指定的 Enumerator Interface 对象。

幸运的是,还有其他技巧可以做您想做的事,但实施取决于您要解决的问题。
例如,您可以将数组与数组、System.Collections.ArrayList 中的 ArrayList 类一起使用,或者创建一个自己的类来存储您的值或对象。

请注意:关于此答案的正确性有一些讨论,请参阅下面的评论。我将研究该主题,并将分享我从他们那里获得的任何相关经验。

于 2012-06-07T08:41:31.760 回答
1

您可以像这样创建一个辅助对象:

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

免责声明:当我在非 Windows 机器上创建此代码时,我无法对其进行测试。您可能会发现一些语法或设计错误。命名不是很好,但这样更符合你的概念。

虽然,您似乎正在寻找一个简单的解决方案。不会引入更多“挑战”:当您想要传递数组中保留其内部索引的值时,您需要Set它们而不是仅仅分配它们:这会降低可移植性。
并且当您使用对象时,您需要了解对象引用与原语相比是如何工作的,否则您会得到一些出乎意料的值变化行为,而这是您不期望的。

于 2012-06-07T18:59:23.723 回答
1

更新

如果有人对 VBScript 与其他语言在数组、foreach 循环方面的比较,特别是获取有关“For Each”在循环中传递的元素位置的信息感兴趣的人,会提出如下问题:

VBScript 在数组、foreach 循环方面与其他语言相比如何,尤其是在循环集合中获取有关“For Each”传递的元素位置的信息?

那么很久以前就会有一个简短的答案:

foreach 循环构造可以提供

  1. 一个指针(内存地址)——就像 C/C++ 那样;然后你必须取消引用指针以获取你甚至可以更改的元素;位置信息可通过指针算术获得)
  2. 引用(别名)(例如 Perl 所做的;允许修改,但显然不计算位置(除非元素意外包含此类信息))
  3. 一个副本(例如 Python 或 VBScript 所做的;修改或检索元信息都是不可能的(除非像 AutomatedChaos 或 AnthonyWJones 之类的善良和聪明的灵魂通过向 DIV 提交循环变量和MODs 分别设计一个允许使用元信息增加普通/基本数据值的类)

您可以放心地忽略我的其余答案;我只是不想删除以下为讨论提供一些背景的文本。

问题无法解决,直到

(1) armen 用现实世界的术语描述现实世界问题的上下文——数组从哪里来,有多少是可能的,什么决定了维度结构(行/列/...),必须进行哪些操作在 For Each 循环中,为什​​么/如何索引对这些操作很重要

(2) 所有贡献者都获得了正确的维度选择器:

For i = LBound(myArr, 1) to UBound(myArr, 1)
    For j = LBound(myArr, 1) to UBound(myArr, 1)

或其变体显然是错误的/具有误导性的。如果不将一行中的 1 替换为 2,则不清楚代码的行/列结构是什么。

为了证明我愿意以更具建设性的方式做出贡献,我引入了一个函数来获取任意数组的(数量)维度:

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

(基于M. Harris 的代码来自 VBScript Docs 的信息

更新:仍然不是解决方案,但值得深思

由于 armen(到目前为止)没有提供他的问题的真实故事,我试着给你一个虚构的故事(给行和列加上上下文,以及你在第三维中可能称之为的东西):

假设有一所学校 - Hogmond - 教授魔法编程。VBScript 很容易(但在狗窝里),所以只有三个测试,学生在中期被录取(每一分钱都很重要)。JScript 更难,所以你必须完成完整的课程,如果学生证明很厚,可能会在学期期间安排额外的测试。F#比较复杂,所以每次考试都要根据多个标准来判断,其中一些可能会在学期中达成一致(老师还在学习中)。C# 是如此“好”的语言,以至于只有一个测试。

因此,在学期结束时,校长 - Bill 'Sauron' Stumblegates 先生 - 有一个 .xls,其中包含一张表:

VBScript 测试分数

(Doreen 在学期的最后一周被录取)和一张纸:

JScript 测试分数

(为了您的安心,隐藏了 120 个额外的测试);F# 结果保存在 .txt 文件中:

# 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

(因为我对在 Excel 中处理三维数据一无所知)。

现在我们有一个上下文要考虑

  1. 数据:玛丽在 eval 测试中得了 9 分很重要,但该信息是否存储在第 5 行或第 96 行并不是数据的固有属性[暗示您在开始之前应该三思而后行(单独考虑:令人印象深刻)AutomatedChaos 的想法,用于创建将(基本)数据和(偶然)信息组合在(n 任意)结构中的位置的对象。]
  2. 处理:一些计算——尤其是那些涉及整个数据集的计算——可以在不考虑行或列的情况下完成(例如所有分数的平均值);有些甚至可能需要重组/重新排序(例如所有分数的中位数);许多计算 - 所有涉及数据的选择/分组/子集 - 如果没有对数据项位置的深入了解,就无法完成。然而,armen 可能对处理根本不感兴趣——也许他所需要的只是在显示元素时识别元素。[因此,推测诸如“Excel/数据库不应该进行处理吗?”、“读者会满足于'D5:9'还是他希望看到'Mary/eval:9'这样的问题是徒劳的-和这样的信息会更适合 AutomatedChaos 的课程吗?", "
  3. 结构/布局:如何将数据放入行和列的选择取决于便利性(首选垂直滚动)、实际考虑因素(在“末尾”附加新数据)和技术原因(VBScript 的“ReDim Preserve”可以增长(仅在最后一维中的(动态)数组) - 因此对于给定上下文/任务有意义的每个布局,有许多其他结构在其他情况下(甚至第一个上下文)更好。当然,没有“索引器的自然顺序”。

现在大多数程序员更喜欢编写/编写代码而不是阅读故事(有些人更喜欢思考/计划/设计代码),所以这里只是一个例子来展示我们的白日梦/魔法有哪些不同的野兽(数组,“迭代器”)千篇一律的“For Each”策略必须应对:

给定两个可让您从 Excel 工作表中剪切数据的函数:

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

(滚动您自己的 resolvePath() 函数或硬编码文件规范)

和一个显示子(使用armen的非常好的想法来引入一个循环计数器变量):

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

你可以使用类似的代码

  showAFE "VTA according to XlsRange:", getXlsRange("VTA", "B3:D4")
  showAFE "VTA according to AdoRows:",  getAdoRows("SELECT * FROM [VTA$B3:D4]")

让你的周末惊喜:

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

和绝望:

  1. Stumblegates 先生的类型系统隐藏了这两个数组具有非常不同的性质的事实(并且也忽略了固定数组和动态数组之间的区别)
  2. 您可以在 VBScript 中创建各种数组,只要它们是从零开始的(没有机会创建和/或重组 Range-born 数组并保持它们(偶然!)从一开始)
  3. 通过两种不同的方法获取具有(必然)一种布局的一组数据将提供具有两种不同结构的数据
  4. 如果您要求“For Each”枚举数据,那么您获得的序列由迭代器确定并且不可预测(您必须检查/实验)。(强调迭代器的自由/角色是 AutomatedChaos 的第一个答案中的一个重点)

[不要读这个,如果你不感兴趣/不能忍受迂腐的诽谤

它仍然比 AnthonyWJones 的贡献得分更高,因为至少有一个人承认既没有回答也没有回答问题,因为对 .ArrayList 的引用 - 这与 armen 的问题完全不相关,因为没有使 ArrayList 多维的方法(即:可通过等价于 al(1,2,3) 访问)。是的,“IEnumerable”(纯 .NET 概念)和“多线程”是令人印象深刻的关键字,并且有反映“动态”修改的“实时”集合(例如 oFolder.Files),但没有多少(单!)线程将让您在循环时修改一个不起眼的 VBScript 数组 - Stumblegates 先生是一位严厉的大师:

  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

输出:

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

笼统声明的另一种阐述:即使是优秀的程序员也会犯错误,特别是如果他们渴望帮助,必须在不利的条件下工作(Stumblegates 先生尽最大努力确保您不能在没有广泛测试的情况下使用/发布 VBScript 代码),有工作和生活,或者只是一个糟糕的时刻。

然而,这并没有改变这样一个事实,即某些代码片段/语句对于 SO 读者来说是无用的,甚至是危险的,因为他们认为他们已经找到了解决问题的方法。代码/文本的质量是内容本身的基本属性,谁写的只是偶然的。但是如何在“Jon Doe 的代码”是指代以下行的自然方式的上下文中“客观”

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]

内容的投票是根据人的声誉来总结的吗?(在没有声誉系统的情况下,是否会列出数百万个答案?如果没有积分,我会在我的贡献上投入更少的时间/工作吗?我希望/猜不是,但我也是人。)

因此,我鼓励您(至少)在我的 getDimensions() 函数中更正 60 的限制之前对这个精心设计的内容投反对票。你不能伤害我的感情;我认为我无可指责,因为我所做的只是依赖文档:

数组变量的维度;最多可以声明 60 个多维。

(我有点惭愧的是,当我看到别人代码中的 999 或 60000 时,我有一种优越感——正如我所说:我也只是人类;并且:不要相信你在 Stumblegates 先生中,但请检查:

  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

输出:

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
...

)

不要以破坏性模式得出结论:我仍然希望我对“嵌套循环中用于指定索引器的边界(尤其是不同范围的范围)”主题的反复强调会神奇地导致这里的许多代码行在不久的将来被更改 -或者我们不都是霍格蒙德的学生吗?

]

于 2012-06-07T22:07:35.667 回答
0

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
于 2012-06-07T13:01:55.843 回答