空腹おやじのログと備忘録

VBA(主にExcel)でいろいろな実験的な事とか、Linuxのコマンドとか設定とかについて忘れないように、あれこれと・・・

【VBA】任意のRangeの先頭1行を除いたRangeを簡単に取得する

Excelでデータを入力していると、1行目にヘッダ行があって、実際のデータは2行目からということはよくあると思います。
VBAで何らかの処理を行っている際に、ヘッダ行を含んだRangeは取得済みだけど、ヘッダ行を除いた部分をRangeとして取得したい場合、今までは、最終行を取得して2行目から最終行までと指定していたのですが、別のアプローチがあることに気が付いてしまいました。

2019/5/22 修正
ヘッダ行を含むデータのRangeに対して、
1.ヘッダ行分、下にOffset
2.ヘッダ行分、行数をマイナスResize
でいけるんじゃない?

コメント欄で、jinoji さんからのご指摘にあるように、上記の順番では特定の条件下でエラーになるため、以下のようにするべきでしたので訂正しました。

ヘッダ行を含むデータのRangeに対して、

  1. ヘッダ行分、行数をマイナスResize
  2. ヘッダ行分、下にOffset
Public Sub hoge()

    Dim r   As Range

    Set r = Range("A1:B10")

    Debug.Print "Original Range   : " & r.Address(False, False)

'    Debug.Print "ヘッダ行除外 Range : " & r.Offset(1, 0).Resize(r.Rows.Count - 1).Address(False, False)
    Debug.Print "ヘッダ行除外 Range : " & r.Resize(r.Rows.Count - 1).Offset(1, 0).Address(False, False)

End Sub

実行結果

call hoge
Original Range   : A1:B10
ヘッダ行除外 Range : A2:B10

ほら、大丈夫!A1がA2に変わってる。
最終行を取得しなくてもいいし、1行で取得できるし、ちょっとお得な気分。

途中に結合されたセルがあっても大丈夫みたい。
でも、最初のRangeの行数が1だと、実行時エラーになりますからね。

Offset と Resize の指定を変えれば、列方向でも出来そうだ。

【VBA】Excelで、製品の販売価格の更新情報を持つマスタから、いろいろな販売日の価格を取得する

難しく書いたけど、要するに
ある製品の販売価格が、不定期に変わっていて、その履歴が残っている。
じゃあ、「任意のタイミングでの販売価格はいくらか?」を取得するというもの。

元ネタはこちら
https://twitter.com/ia02003812/status/1122857099669598209

 ワークシート関数だけでも出来そうだけど、データ件数が多いとなるとレスポンスが悪そう。
 VLookUpでは、複数条件で絞り込めなさそうなので多分NG.
 MAXIFS を使えば出来そうだけど、うちのExcelは 2013 なので使えない。
 他にも方法はあるのだろうけど、今回は(今回も?)VBAで・・・



データは下に載せたような感じ。
いつもの如く、乱数使いまくって・・・
データ作成のコードも下の方に載せてあるので、興味のある方はどうぞ。
同じデータは作れませんけど、近いものは作れるはずなので・・・


価格履歴:各製品の販売価格の更新情報を持つマスタ(製品名、販売価格、適用開始年月日)
f:id:Z1000S:20190508213737j:plain
重要:価格履歴データは適用開始日が昇順でソートされていないと、掲載したコードは正しく動きません。


販売トラン:各製品の販売実績データ(販売日、製品名、販売数)
f:id:Z1000S:20190508213750j:plain
「データの名称が、下記のコード中の名称と違う」とか突っ込まないで下さい。

問題点としては、販売トランにある販売日が、価格履歴の価格適用開始日と一致する保証がないので、通常の完全一致の検索が出来ない。
これを解決する手段が必要。



これについては、Dictionary を使用して、以下のようにデータを格納し、その中から該当データを取得出来るようにした。
Key:製品名
Item:「販売価格」、「適用開始年月日」をペアにした物の配列

Dictionary への動的配列の格納は、以前やっているので特に問題はない。
z1000s.hatenablog.com




テスト用データ生成コードで使っている定数とかは、上の「販売価格 更新処理コード」に記載されているので、上のコードと同じモジュールに貼り付けて使って下さい。

実行結果

f:id:Z1000S:20190509222234j:plain
製品種類数:200
価格履歴件数:8,072
販売データ件数:10,000

所要時間:0.8 ~ 0.9 秒

でした。

遅くはないと思うけど、比べる物が無いので・・・


おまけ

多分、見たことのない人が多いであろう物

dicItemInfo_.Item(sItemName)(0)(0)
vaItemInfo(0)(0)

配列の要素が配列なので、こういう記述になります。ハイ。

例えば
a(2)
という配列があるとして、
a(0)の要素が、b という配列の場合、b(0)を参照するには
a(0)(0)
という指定をします。

Public Sub foo()

    Dim a(2)
    Dim b(1)

    b(0) = "B00"
    b(1) = "B01"

    a(0) = b

    b(0) = "B10"
    b(1) = "B11"

    a(1) = b

    Debug.Print "A00 : " & a(0)(0)
    Debug.Print "A01 : " & a(0)(1)

    Debug.Print "A10 : " & a(1)(0)
    Debug.Print "A11 : " & a(1)(1)

End Sub

実行結果

call foo
A00 : B00
A01 : B01
A10 : B10
A11 : B11


本当は、Dictionary の Item には、構造体(ユーザー定義型)を入れたかったんですよ。
でも、出来ないみたいだったので、やむなく配列で代用しました。
その結果がこれです。

【VBA】Excelはアクティブシートが変わると、処理時間が変わる場合があるようだ

いつもの如く、怪しげな事(?)をゴニョゴニョとやっていて、
「あれ、さっきより処理速くね?」となったので・・・

経緯

ワークシートに50万件ほどのデータがあって、そいつにフィルターを掛けて外しての繰り返しをやっていたら、何かの拍子に処理時間が早くなったんですよ。
調べてみたら、フィルター処理を行う時に、

フィルターを適用するワークシートをアクティブにしているより、
フィルターを適用しないワークシートをアクティブにしておいた方が

速かったんですよ。

もちろん

With Application
    .ScreenUpdating = False
    .EnableEvents = False
    .Calculation = xlCalculationManual
End With

は適用した上ですよ。
ブックには、式が1つもないので、下2つは、ほとんど意味がないような気がするけど、お約束なので・・・

結果

とりあえず、というか、いきなり結果から。
速度順に並べたが、「フィルターを適用するシート以外のシート」をアクティブにしたケースが全て上位になっている。率にして、15%以上の差が出ており、誤差と言える値ではなさそうである。
「平均処理時間」は、5回実行した結果の平均値。

アクティブシートScreenUpdatingWindowState平均処理時間(秒)備  考
非フィルター適用シートFalsexlMinimized16.31
非フィルター適用シートFalsexlNormal16.32Worksheet.Visible = False
非フィルター適用シートFalsexlNormal16.35
非フィルター適用シートTruexlMinimized16.46
非フィルター適用シートTruexlNormal16.65Worksheet.Visible = False
非フィルター適用シートTruexlNormal16.67
フィルター適用シートFalsexlNormal18.85
フィルター適用シートFalsexlMinimized18.86
フィルター適用シートTruexlMinimized21.04
フィルター適用シートTruexlNormal25.13

コード

Private Const KEY_COL           As Long = 1

Private Const DATA_SHEET_NAME   As String = "Data"
Private Const KEY_SHEET_NAME    As String = "Key"

Private Const HEADER_ROWS       As Long = 1

Private Const MAX_KEYS_COUNTS   As Long = 50
Private Const MAX_DATAS_COUNTS  As Long = 500000


Public Sub queryByAutoFilter()

    Dim ws          As Worksheet
    Dim r           As Range
    Dim lEndRow     As Long
    Dim sgStart     As Single
    Dim sgStop      As Single
    Dim vKeys()     As Variant
    Dim i           As Long

    Dim lMatchCount As Long

With Application
    .ScreenUpdating = False
'    .ScreenUpdating = True
    .EnableEvents = False
    .Calculation = xlCalculationManual

    .WindowState = xlNormal
'    .WindowState = xlMinimized
End With

sgStart = Timer

    Set ws = Worksheets(DATA_SHEET_NAME)

    lEndRow = MAX_DATAS_COUNTS + HEADER_ROWS

    '検索対象キー値取得
    Call getKeys(vKeys)

    ReDim sKeys(LBound(vKeys) To UBound(vKeys))

    For i = LBound(vKeys) To UBound(vKeys)
        sKeys(i) = CStr(vKeys(i))
    Next i

    With ws
        'Filter対象レンジを設定
        Set r = .Range(.Cells(1, KEY_COL), .Cells(lEndRow, KEY_COL))
    End With

    With r
        For i = LBound(sKeys) To UBound(sKeys)
            'AutoFilterクリア
            .AutoFilter

            'Filter実行
            .AutoFilter Field:=1, Criteria1:=sKeys(i), Operator:=xlFilterValues

            If .SpecialCells(xlCellTypeVisible).Count > HEADER_ROWS Then
                lMatchCount = lMatchCount + .SpecialCells(xlCellTypeVisible).Count - HEADER_ROWS
            End If
        Next i

        'AutoFilter解除
        .AutoFilter
    End With

sgStop = Timer

With Application
    .ScreenUpdating = True
    .EnableEvents = True
    .Calculation = xlCalculationAutomatic

    .WindowState = xlNormal
End With

    Debug.Print Format$(sgStop - sgStart, "0.00")

End Sub


Private Sub getKeys(ByRef vKeys() As Variant)

    Dim lEndRow   As Long
    Dim lItems  As Long
    Dim lValue  As Long
    Dim i       As Long

    ReDim vKeys(MAX_KEYS_COUNTS - 1)

    lEndRow = MAX_KEYS_COUNTS + HEADER_ROWS

    With Worksheets(KEY_SHEET_NAME)
        For i = HEADER_ROWS + 1 To lEndRow
            vKeys(i - HEADER_ROWS - 1) = .Cells(i, 1).Value
        Next i
    End With

End Sub

まとめ(のようなもの)

  • フィルターを適用するワークシートをアクティブにしない方が速かった。
  • ScreenUpdateing = False は、フィルターを適用するワークシートがアクティブな場合には、効果が大きいが、そうでない場合は、効果がほとんど認められなかった。
  • Windowを最小化しても、あまり高速化へのメリットはなかった。

今回のテストでは、ScreenUpdatingの状態に関わらず、フィルターを適用するワークシートをアクティブにしない方が速くなっている。

では、フィルターを適用するワークシートが「表示されていなければ、いいのか?」というと、
「Windowを最小化」により該当ワークシートを非表示状態にして効果が認められるのは、
フィルターを適用するワークシートをアクティブにして、かつ ScreenUpdating = True の場合だけだったので、表示されていなければ、効果があるわけではないようだ。

一方で、「該当ワークシートを非表示にする」というのは、別のワークシートをアクティブ化するという事と同等なので、これは「効果はある」ことになる。


結局の所、ScreenUpdatingがFalseになっていても、フィルターを適用するワークシートがアクティブの場合には、アクティブシートであるが故の何かが、裏で動いていると考えるのが妥当なのかもしれない。
というか、私の頭ではそれしか思い浮かばない。
誰か、わかる方がいらっしゃいましたら教えて下さい。



フィルター以外でも効果があるのかは、要確認。
でも、きっと・・・
忘れて何もしないような気がする。

追記

行削除(2019/5/5)

行の削除処理で比較してみた。

Public Sub deleteRows(ByVal acsh As Long, ByVal doScreenUpdating As Boolean)

    Const END_ROW   As Long = 10000

    Dim ws      As Worksheet
    Dim sgStart As Single
    Dim sgStop  As Single
    Dim i       As Long

sgStart = Timer

    Application.ScreenUpdating = doScreenUpdating

    If acsh = 1 Then
        Worksheets(1).Activate
    Else
        Worksheets(2).Activate
    End If

    Set ws = Worksheets(1)

    With ws
        .Cells.ClearContents

        .Cells(1, 1).Value = 1
        .Cells(2, 1).Value = 2

        .Range("A1:A2").AutoFill Destination:=.Range("A1:A" & CStr(END_ROW)), Type:=xlFillDefault

        For i = END_ROW To 1 Step -2
            .Rows(i).Delete
        Next i
    End With

    Worksheets(1).Activate

    Application.ScreenUpdating = True

sgStop = Timer

    Debug.Print "Active Sheet:" & CStr(acsh), Format$(sgStop - sgStart, "0.00")

End Sub

結果

Active Sheet: 1 ScreenUpdating: True Time[sec]: 3.64
Active Sheet: 1 ScreenUpdating: False Time[sec]: 0.97
Active Sheet: 2 ScreenUpdating: True Time[sec]: 0.89
Active Sheet: 2 ScreenUpdating: False Time[sec]: 0.84

やっぱり、処理するシート以外をアクティブシートにした方が速い。
その場合には、ScreenUpdating = False としても、大きな効果は認められない。(見えない所で処理しているのだから、当たり前といえば当たり前か・・・)
結構使えるかもしれない・・・

【VBA】割り算を使わないで、数値の2進数表示を取得する(負値対応済み)

最初に断っておきますが、この記事は半分ネタです。
あえて、面倒くさいことしてます。
私がやりたかっただけです。

基本に則って2進表示を求めたい方は、

とか、して下さい。

但し、10進数の2進数表示した文字列を取得する関数は、探せば結構出てきますが
大体は、2で割って余りを求めて・・・
でも、負の数まで考慮されているものはほとんど無いようです。
私が見つけた1件でも、値によってはオーバーフローして、十分な検証はされていないようでした。

WorksheetFunction.Dec2Bin は、

数値 < -512 または数値 > 511 の場合、エラー値 #NUM! が返されます。

WorksheetFunction.Dec2Bin メソッド (Excel)

と、あまり使い物にならないかもしれません。

コード その1

割り算を使った方法は、上位の桁から処理していくが、割り算を使用しないということは、その逆で、下位から処理を行っていきます。
2^0 の桁 から Integerなら、2^15 の桁に向かって処理を行います。

流れとしては
最初に、変換したいデータと 1 ( = 2^0 )のAnd を取る。

奇数の場合、2^0の桁は 1 なので
1 And 1 ===> 1
となり、
偶数の場合、2^0の桁は 0 なので
0 And 1 ===> 0
となる。

And を取った結果が、2^0 の桁の値となる。

これを桁を、2^1の桁、2^2の桁と、上位方向にずらして繰り返していく。

「And演算子って何?」とか、
「なんで数値同士でAndなの?」とか
「A And B って、A かつ B じゃないの?」
という方は、以下のリンクをどうぞ。
解説の下の方に書いてあります。)
docs.microsoft.com


以下が、その処理を行うコードです。
但し、Integer も Long も符号付き故に、大抵は、最上位bitでの処理に苦しみます。(多分、私だけではないと思う。)
結局は、それを回避するためにゴチャゴチャと・・・

Long(32bit 符号付き)版のコードは、必要最小限にコメントを省いているので、コメントが必要な方は、Integer(16bit 符号付き)版のコメントを参照して下さい。
微妙に違うところはありますが、ほぼ分かると思います。

コード その2

VBAで、エンディアンの変換をやろうとしたら、やっぱりハマった - 空腹おやじのログと備忘録 のおまけで書いたコードと似たような手法。

最初に変換したい値を16進の文字列に変換して、16進数1文字につき、2進数4文字に変換していく。
値の正負を意識する必要がない。
オーバーフローの心配もなく、手っ取り早い。

お気軽に使いたいなら、こちらの方がおすすめ。

Integer(16bit)版のみ掲載
Long(32bit)版が欲しい方は、適当にアレンジして下さい。そんなに難しくはないと思いますので。


あぁ、今日も自己満足の世界に・・・

【VBA】For ループの罠(?)

次のコードを実行するとどうなると思いますか?

Public Sub foo()

    Dim i   As Long

    For i = &H7FFFFFFE To &H7FFFFFFF
        Debug.Print i
    Next i

End Sub

もうひとつ

Public Sub bar()

    Dim i   As Long

    i = &H7FFFFFFE

    Do
        Debug.Print i

        i = i + 1
    Loop Until i > &H7FFFFFFF

End Sub

ちなみに、
&H7FFFFFFE => 2147483646
&H7FFFFFFF => 2147483647
です。

f:id:Z1000S:20190429072820j:plain

どちらも、

 2147483646 
 2147483647 

となると思った方

   ・
   ・
   ・
   ・
   ・

残念ですが、半分だけ正解です。

bar の方が、予想はしやすいと思いますが・・・



どちらも 2147483646 と 2147483647 は出力されます。
でもその後に、どちらも、オーバーフローが発生します。

f:id:Z1000S:20190428223821j:plain
f:id:Z1000S:20190428223837j:plain

f:id:Z1000S:20190428223856j:plain
f:id:Z1000S:20190428223916j:plain

上記Forループの場合、変数 i がLongなので、Longの上限値である &H7FFFFFFF までループ可能かと思っていたら、内部で&H7FFFFFFF + 1 してからループを終了するみたいです。
( i = &H7FFFFFFF の処理を終了後、i をインクリメントして i > &H7FFFFFFF となったことでループ終了と判断していると推測される)
ただ、For ループの方のスクリーンショットで見える -2147483648 は、Long の最小値(&H80000000)であるので、この状況についてはよくわからない。
Longの上限(&H7FFFFFFF)を超えた &H80000000(正の値)が、Longの変数領域に負の値の &H80000000 として存在しているためなのか?
内部でのBitは &H80000000 で一致していても、実際には、Longの上限(&H7FFFFFFF)+1(2147483648 > 0)となるためオーバーフローと判断されたのだろうか?

ちなみに、イミディエイトウィンドウで

? &H7FFFFFFF + 1

とやっても、オーバーフローが発生する。



上記のbarの方は予想はできるけど、For の挙動は予想できなかった。

別の作業で同様の処理をやっていて、順調に進んでいった最後のところで
「えっ、なんでオーバーフローすんの?」って驚いた。
その原因がこんな感じでした。

結  論
  • For ループは、ループ変数の型の上限値までループさせるとオーバーフローする。
  • ループ変数の型は、余裕のあるサイズにしよう!!!
  • 境界には気をつけよう

あぁ、今日も余計な手間で疲れた・・・

回答が・・・

後からブロググループのページを見て・・・
がっかり orz
f:id:Z1000S:20190429073335j:plain
なんのために結果を隠したんだよ・・・
最初から答えが出てるじゃん。

当該画像の前に別の画像を追加しても、ブログページに表示される画像は変わらないんですね。
しょうがないので、最初から隠さずに全部表示することにしました。


つまらん!

VBAのDictionary の Key について実験してみた

VBAのDictionaryのKeyには、数値、文字(数字を含む)、オブジェクト等が使えます。
そんな中で、ちょっと気になったことがあったので、実験してみました。

データ型が異なる同一値の数値を指定してみる

データ型が異なる別々の値」をKeyとしてDictionaryに追加した後、それぞれのItemを、「値が同じで、異なるデータ型」のKeyを指定して呼び出してみる。

1.呼び出し検証コード

Public Sub 数値型追加テスト()

    Dim dic As New Dictionary
    Dim v   As Variant

    dic.Add CByte(0), "Byte"
    dic.Add 1, "Integer"
    dic.Add 2&, "Long"
    dic.Add 3@, "Currency"
    dic.Add 4!, "Single"
    dic.Add 5#, "Double"

    For Each v In dic.Keys
        Debug.Print TypeName(v), v
    Next v

    Debug.Print "----- " & "Integer" & " -----"
    Debug.Print dic(0)
    Debug.Print dic(1)
    Debug.Print dic(2)
    Debug.Print dic(3)
    Debug.Print dic(4)
    Debug.Print dic(5)

    Debug.Print "----- " & "Long" & " -----"
    Debug.Print dic(0&)
    Debug.Print dic(1&)
    Debug.Print dic(2&)
    Debug.Print dic(3&)
    Debug.Print dic(4&)
    Debug.Print dic(5&)

    Debug.Print "----- " & "Currency" & " -----"
    Debug.Print dic(0@)
    Debug.Print dic(1@)
    Debug.Print dic(2@)
    Debug.Print dic(3@)
    Debug.Print dic(4@)
    Debug.Print dic(5@)

    Debug.Print "----- " & "Single" & " -----"
    Debug.Print dic(0!)
    Debug.Print dic(1!)
    Debug.Print dic(2!)
    Debug.Print dic(3!)
    Debug.Print dic(4!)
    Debug.Print dic(5!)

    Debug.Print "----- " & "Double" & " -----"
    Debug.Print dic(0#)
    Debug.Print dic(1#)
    Debug.Print dic(2#)
    Debug.Print dic(3#)
    Debug.Print dic(4#)
    Debug.Print dic(5#)

    Debug.Print "----- " & "Type Name" & " -----"
    Debug.Print TypeName(dic.Keys(0))
    Debug.Print TypeName(dic.Keys(1))
    Debug.Print TypeName(dic.Keys(2))
    Debug.Print TypeName(dic.Keys(3))
    Debug.Print TypeName(dic.Keys(4))
    Debug.Print TypeName(dic.Keys(5))

End Sub

実行結果

call 数値型追加テスト
Byte           0 
Integer        1 
Long           2 
Currency       3 
Single         4 
Double         5 
----- Integer -----
Byte
Integer
Long
Currency
Single
Double
----- Long -----
Byte
Integer
Long
Currency
Single
Double
----- Currency -----
Byte
Integer
Long
Currency
Single
Double
----- Single -----
Byte
Integer
Long
Currency
Single
Double
----- Double -----
Byte
Integer
Long
Currency
Single
Double
----- Type Name -----
Byte
Integer
Long
Currency
Single
Double
  • 同一値であれば、データ型が異なっていてもItemを呼び出しできる。
  • 呼び出したItemは、Addした際のデータ型を保持している。

おまけのテスト

Public Sub hoge()

    Dim v1
    Dim v2

    v1 = 10
    v2 = 10&

    Debug.Print v1 = v2
    Debug.Print TypeName(v1) = TypeName(v2)

    Debug.Print 10 = 10&
    Debug.Print TypeName(10) = TypeName(10&)

End Sub

実行結果

call hoge
True
False
True
False

これなら、上のような結果になるのは納得。


2.追加検証コード
データ型が異なる同一値をKeyとしてDictionaryに追加してみる。

Public Sub 数値型追加テスト2()

    Dim dic As New Dictionary
    Dim v   As Variant

    dic.Add CByte(0), "Byte"
    dic.Add 0, "Integer"
    dic.Add 0&, "Long"
    dic.Add 0@, "Currency"
    dic.Add 0!, "Single"
    dic.Add 0#, "Double"

End Sub

実行結果
f:id:Z1000S:20190422215754j:plain
f:id:Z1000S:20190422215819j:plain

  • データ型が異なっていても、値が同一であれば追加はできない。
Nullを指定してみる

検証コード

Public Sub Null追加テスト()

    Dim dic As New Dictionary

    dic.Add Null, "NULL"

    Debug.Print dic.Exists(Null), dic.Item(Null)

End Sub

実行結果

call Null追加テスト
True          NULL
  • NullはKeyとして追加できる。
  • NullがKeyとして存在するか確認できる。
  • Nullをキーとして、Itemを呼び出せる。
空文字列を指定してみる
Public Sub NullString追加テスト()

    Dim dic As New Dictionary

    dic.Add "", "Null String"

    Debug.Print dic.Exists(""), dic.Item("")

End Sub

実行結果

call NullString追加テスト
True          Null String
  • 空文字はKeyとして追加できる。
  • 空文字がKeyとして存在するか確認できる。
  • 空文字をKey指定してItemを呼び出せる。
Binaryを指定してみる

検証コード

Public Sub Binary追加テスト()

    Dim dic As New Dictionary

    dic.Add vbTab, "Tab"
    dic.Add vbCr, "CR"
    dic.Add vbLf, "LF"
    dic.Add vbCrLf, "CRLF"
    dic.Add vbNullChar, "\0"

    Debug.Print dic.Exists(vbTab), dic.Item(vbTab)
    Debug.Print dic.Exists(vbCr), dic.Item(vbCr)
    Debug.Print dic.Exists(vbLf), dic.Item(vbLf)
    Debug.Print dic.Exists(vbCrLf), dic.Item(vbCrLf)
    Debug.Print dic.Exists(vbNullChar), dic.Item(vbNullChar)

End Sub

実行結果

call Binary追加テスト
True          Tab
True          CR
True          LF
True          CRLF
True          \0
  • BinaryはKeyとして追加できる。
  • BinaryがKeyとして存在するか確認できる。
  • BinaryをKey指定してItemを呼び出せる。

テスト後に気が付いたけど、Binaryって、要はByteデータなので、当たり前なのかも。

配列を指定してみる

検証コード

Public Sub 配列追加テスト()

    Dim dic As New Dictionary
    Dim ar11(1)     As Long

    ar11(0) = 11
    ar11(1) = 12

    dic.Add ar11(0), ar11(0) * 10
    dic.Add ar11(1), ar11(1) * 10

    Debug.Print dic.Exists(ar11(0)), dic.Item(ar11(0))
    Debug.Print dic.Exists(ar11(1)), dic.Item(ar11(0))
    Debug.Print dic.Exists(11), dic.Item(11)
    Debug.Print dic.Exists(12), dic.Item(12)


    dic.Add ar11, ar11(0) * 100 + ar11(1) * 100

    Debug.Print dic.Exists(ar11), dic.Item(ar11)

End Sub

実行結果
f:id:Z1000S:20190424210945j:plain
f:id:Z1000S:20190424210959j:plain

  • 配列の要素は、Keyとして追加できる。
  • 配列の要素がKeyとして存在するか確認できる。
  • 配列の要素をKey指定してItemを呼び出せる。
  • 配列は、Keyとして追加できない。

VBAの$付きの文字列関数

VBAの文字列関数には$付きの物がありますが、ネットで見るコードではほとんど見かけません。Microsoftのサイトで関数を検索しても、出てくるのは$なしばかり。

何が違うのと言われれば、

VBA の文字列関数の中には 2 種類のバリエーションを持つものがあります。1 つは文字列を返し、もう 1 つは文字列型 (String) のバリアント値を返します。文字列を返す関数名にはドル記号 ($) が含まれます。たとえば、Chr$、Format$、LCase$、Left$、LTrim$、Mid$、Right$、RTrim$、Space$、Trim$、UCase$ などがあります。文字列型のバリアント値を返す関数にはドル記号は含まれません。これらの関数名は、Chr、Format、Lcase、Left、Ltrim、Mid、Right、Rtrim、Space、Trim、Ucase などになります。前者の関数はすばやく結果を返しますが、Null 値を使用して呼び出すとエラーが発生します。文字列型のバリアント値を返す関数では、Null 値を問題なく使用できます。

文字列の一部を返す | Microsoft Docs

2つの違いをざっくり言うと、

  • 復帰値の型が異なる
  • パフォーマンスが異なる(通常なら無視できるレベル)
  • 文字列を取り出す元の文字列式で扱えるデータの型が異なる

です。

項目 $付き $なし
復帰値 文字列型 (String) 文字列型 (String) のバリアント値
Null値対応 使用不可 使用可

Nullをパラメータとして渡した場合の違いはこんな感じ。
$なしは、エラーの発生もなく、Null(文字列ではない)を返している。
   文字列ではないというのは、
   ? typename(Left(Null,2))
  と実行すれば
   Null
  と返ってくることで確認できる。
$ありでは、実行時エラー '94'が発生している。
f:id:Z1000S:20180930140219j:plain


$ありなしでのパフォーマンスの比較は、以下のサイトで行われていたが
基本的には記述通り、$あり > $なし となっている。
代入先の変数の型で状況が変わるのは、なるほどといった感じ。

■T'sWare Access Labo #32 〜$付き関数のメリットとは?〜



別に、$付きでも$なしでも、どっちを使ってもいいんですよ。最終的に、正しい結果が得られるのであれば・・・


ちなみに私は、$付き派です。

「だから、どうした?」とか「どうして?」と聞かれても困りますが・・・