2

次の VB スクリプトを .asp ファイルに記述しています。

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 を使用すると、すべての配列項目を反復処理できます。問題は、各次元のインデックスを取得する方法です。

例: 2 と 4 である 10 回目のループの後に k インデックスを取得するにはどうすればよいですか?

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 に送信して C/C++ に似たソリューションを実装するために熱心に取り組んでいない限り) MODs resp. プレーン/必須データ値をメタ情報で拡張できるようにするクラスを設計する)

私の回答の残りの部分は無視しても問題ありません。議論の文脈を提供する次のテキストを削除したくありません。

問題は、それまで対処できません。

(1) armen は、現実世界の問題のコンテキストを現実世界の用語で説明します - 配列がどこから来るのか、可能な次元数、次元構造 (行/列/...) を決定するもの、実行する必要がある操作For Each ループでは、これらの操作でインデックスが重要な理由/方法

(2) すべての貢献者は、ディメンションのセレクターを正しく取得します。

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

またはそのバリエーションは明らかに間違っている/誤解を招くものです。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 の情報に基づく)

更新: まだ解決策ではありませんが、検討の余地があります

アーメン (これまで) は彼の問題の本当の話を提供していなかったので、私は架空のものを提供しようとします (行と列、およびあなたが 3 次元のものと呼ぶものに文脈を与えるために):

魔法のプログラミングを教える学校、ホグモンドがあるとしましょう。VBScript は簡単です (ただし、犬小屋のようなものです)。そのため、テストは 3 つしかなく、学生は学期の途中で入学できます (すべてのペニーがカウントされます)。JScript はより難しいため、フルコースを受講する必要があり、学期中に生徒の数が多い場合は追加のテストがスケジュールされる可能性があります。F# はより複雑であるため、各テストは複数の基準で判断する必要があり、そのうちのいくつかは期間中に合意される可能性があります (教師はまだ学習中です)。C# は非常に「優れた」言語であるため、テストは 1 つしかありません。

そのため、学期の終わりに校長である Bill 'Sauron' Stumblegates 氏は、シートを含む .xls を持っています。

VBScript テストのスコア

(ドリーンは学期の最後の週に受け入れられました)とシート:

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

(エクセルで3次元以上のデータを扱うことについて何も知らないので)。

これで、考えるコンテキストができました

  1. データ: Mary が eval テストで 9 点を獲得したことは重要ですが、その情報が行 5 または 96 に格納されているかどうかは、データ固有のプロパティではありません [つまり、(それ自体が印象的です: 印象的な) (必須) データと (n 任意の) 構造内の位置に関する (偶然の) 情報を組み合わせたオブジェクトを作成する AutomatedChaos のアイデア]
  2. 処理: 一部の計算 (特にデータセット全体を含む計算) は、行や列に関係なく実行できます (たとえば、すべてのスコアの平均)。再構築/並べ替えが必要な場合もあります (たとえば、すべてのスコアの中央値)。多くの計算 - データの選択/グループ化/サブセットを含むすべて - は、データ項目の位置に関する詳細な知識なしでは実行できません。しかし、armen は処理にまったく関心がない可能性があります。おそらく、彼がインデックスを必要とするのは、要素を表示しながら要素を識別することだけです。[したがって、「Excel/データベースが処理を行うべきではありませんか?」、「読者は 'D5: 9' で満足するでしょうか、それとも 'Mary/eval: 9' を見たいと思うでしょうか?そのような情報は、AutomatedChaos のクラスのより良い候補になるでしょうか?","
  3. 構造/レイアウト: データを行と列に配置する方法の選択は、利便性 (垂直スクロールが推奨される)、実用的な考慮事項 (「最後に」新しいデータを追加する)、および技術的な理由 (VBScript の「ReDim Preserve」が大きくなる可能性がある) によって決定されます。 (動的)配列は最後の次元のみ)-特定のコンテキスト/タスクに適した各レイアウトには、他の状況(または最初のコンテキスト)でより優れた他の多くの構造があります。確かに「インデクサーの自然な順序」はありません。

現在、ほとんどのプログラマーは、ストーリーを読むことよりもコードを書くこと/コードを書くことを愛しています (コードについて考えたり、計画したり、設計したりすることよりも、コードを書くことの方が好きな場合もあります)。 one-fits-all-dimensions 'For Each' 戦略は、以下に対処する必要があります。

Excel シートからデータを切り取ることができる 2 つの関数があるとします。

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() 関数をロールするか、ファイル仕様をハードコーディングします)

および表示サブ(ループカウンター変数を導入するためにアーメンの非常に良いアイデアを使用します):

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. Mr. Stumblegates の型システムは、これら 2 つの配列が非常に異なる性質を持っているという事実を隠しています (固定配列と動的配列の違いも無視されています)。
  2. ゼロベースである限り、VBScript ですべての種類の配列を作成できます (Range-born 配列を作成および/または再構築し、それらの (偶発的な!) 1 ベース性を維持する可能性はありません)。
  3. 2 つの異なる方法で (必然的に) 1 つのレイアウトで 1 つのデータ セットを取得すると、2 つの異なる構造を持つデータが配信されます。
  4. 「For Each」にデータを列挙するように依頼すると、得られるシーケンスは反復子によって決定され、予測できません (確認/実験する必要があります)。(イテレータの自由/役割を強調することは、AutomatedChaos の最初の回答の 1 つのナゲットです)

[衒学的な批判に興味がない/耐えられない場合は、これを読まないでください:

これは、AnthonyWJones の貢献よりも優れたスコアを持っています。これは、.ArrayList への参照のために、質問も回答も認めていない少なくとも 1 人が賛成票を投じているためです。 ArrayList を多次元にする方法 (つまり、al(1,2,3) に相当するものでアクセス可能)。はい、「IEnumerable」(純粋な .NET の概念) と「マルチスレッド」は印象的なキーワードであり、「オンザフライ」の変更を反映する「ライブ」コレクション (oFolder.Files など) がありますが、(シングル!) スレッドの量はありません。ループ中に謙虚な VBScript 配列を変更できます - Mr. 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 読者にとって危険でさえあるという事実を変えるものではありません。コード/テキストの品質は、コンテンツだけの本質的な特性であり、誰がそれを書いたかは単なる偶然です。しかし、「ジョン・ドウのコード」が次のような行を参照する自然な方法であるコンテキストで「客観的」になる方法

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 を見たとき、私が優越感を持っていたことです。ミスター・スタンブルゲイツで、しかしチェックしてください:

  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

For Each の代わりに、ネストされた For ループを使用する

for i = 0 to ubound(myArr)
     for y = 0 to ubound(myArr, 2)
        ' do something here...
    next
next
于 2012-06-07T13:01:55.843 に答える