見出しを除外したデータ範囲選択

Resizeプロパティ等を使用してデータ範囲のみを選択するコード

'見出し1行を除外した範囲選択
Range("A1").CurrentRegion.Resize(Range("A1").CurrentRegion.Rows.Count - 1, Range("A1").CurrentRegion.Columns.Count).Offset(1, 0).Select

表のデータを取得する際によく使用するのが、見出しの範囲を除外してデータの範囲のみだけを取得する処理です

コードを使用して取得したセル範囲
コードを使用して取得したセル範囲

このコードを使用すると、画像の様に見出しが1行の場合にその行以外のデータ範囲を選択状態にします
プロパティを複数使用する為、1行のコードが長くなってしまうので少しぱっと見わかりにくいかもしれませんが分割して確認すれば簡単な内容で取得出来る事が分かります

コードの解説

コードの解説を行うにあたって、このままでは少し分かりづらいので分割してコードを書いてみます
やっていることは全く同じですが、アクティブセル等の動的なセル指定を行っている点は少し違うことを認識しておいてください

分割して取得を行った時の動き
コードを分割して取得した時の動き

分割して実行していくと、コードは画像のように分割することができます
このコードを1行ずつ解説を行います
なお、最初のA1選択は便宜上のモノなので解説は割愛します

ActiveCell.CurrentRegion.Select

最初に全てのデータ範囲を取得しておきたいので、CurrentRegionプロパティを使用して表範囲全体を取得します
この状態では当然、見出しも含まれています

Selection.Resize(Selection.Rows.Count - 1, Selection.Columns.Count).Select

次にResizeプロパティを使用して範囲サイズを変更します
この例での表では見出しが1行なので、行数を1減らすことでデータ範囲の行数を取得する形になります

なので、ここの数値を変更すれば見出しの行数の変動にも対応出来ます

ただ、Resizeプロパティは範囲の左上からのサイズ変更になりますので、このままだとデータ範囲の最終行が除外されてしまいます

Selection.Offset(1, 0).Select

選択範囲を1行下に移動させることで見出し分を除外させます

ここまでの動きを記事コードでは一括で実行しています
コードが長くなり可読性が低いと思いますので、変数に代入させることで少しわかりやすくなるかもしれません

Dim zzデータ範囲 As Range
Set zzデータ範囲 = Range("A1").CurrentRegion

zzデータ範囲.Resize(zzデータ範囲.Rows.Count - 1, zzデータ範囲.Columns.Count).Offset(1, 0).Select

Set zzデータ範囲 = Nothing

このコードの様に表範囲をセル変数に代入させておけば、少しコードを短くすることができます

範囲を取得したいだけならクドくなりますが、この取得した範囲に対して処理を行う場合にはとても有効な方法です

セル範囲サイズを変更する

セル範囲を指定の行数・列数の範囲に変更するにはResizeプロパティを使用します

'A1から1行3列の範囲を選択
Range("A1").Resize(1, 3).Select

セル範囲サイズを減らしたり増やしたりする場合には「Resize」プロパティを使用します

このプロパティを使用すると、指定セル範囲の左上のセルから指定した行・列数のセル範囲に変更できます

例コードではA1セルを起点にセル範囲を変更しています
プロパティの引数1つ目が行数、2つめが列数になりますので、ここでは1行3列を指定することになります

つまり、「A1,B1,C1」の3つのセルが選択状態になります

使用時の動き

セル範囲サイズの変更の動き
コード実行時の動き

実際にコードを実行したときの動きの画像です
この動きを元に解説を行います

最初にA1セルを選択状態にしています

次に記事コードを実行します
例コードと同じものを実行してますので、解説した通りA1~C1セルが選択状態になります

言うなればこのコードは「Range(“A1:C1”).Select」と同じ動きをすることになります

そして上記でも解説したように、このプロパティで指定される起点となるのはセル範囲の左上のセルです
そのため、アクティブセルをC1に移動させてから実行したとしても範囲が移動することはありません

また、その際にセル範囲選択が再設定されるためアクティブセルは左上に移動してしまいます
この点については、アクティブセルを参照する様な処理作成を行っている場合には注意が必要です

このプロパティの大きな使い道は、特定の列だけに選択をさせたり、データ表範囲の見出しだけを除いて選択状態にする様な場合に有効なプロパティです

またこのプロパティを有効に使用すれば、配列データを出力する際の範囲選択も簡単に行うことが出来ます

セルのデータ取得時の情報について

Value・Text・Formula・Value2の各プロパティを使用するセルの情報取得時の種類の解説

Debug.Print Range("A1")

セルに入力されたデータを取得する際には通常上記のコードで対応します
このコードを実行すると、アクティブシートのA1セルの内容がイミディエイトに出力されます

A1セルに「100」と入力されていれば、出力されるのも「100」です
これは当然の話ですが、このコードには実際にはセルの入力値を指定するプロパティが省略されています

Debug.Print Range("A1").Value

省略しない形の場合は、このコードの様になります
省略した場合の既定のプロパティは「Value」プロパティです

これは入力されている値を取得するプロパティです
通常値の取得には、なんの問題もありませんので処理中ではほぼ省略する形で作成することが多いプロパティです

ただ、セルにあるデータをイメージしてください
セルには数値や文字列以外にも入力されるものがあります、日付やエラー値、関数などです
このあたりを取得したい場合にはValueプロパティでは対応できなくなります
その場合にはそれぞれ別々のプロパティがあります

取得情報によって切り替えるプロパティ

取得を行うセルの入力状態
取得を行うセル

このプロパティを説明するうえで一番分かり易い例が、関数で求められた日付データです
この画像のA2セルを対象に取得を行ってみます

同じセルを違うプロパティで取得した状態
別々のプロパティで取得した状態

先にこの画像を確認してください
これらの他にも取得プロパティは有りますが、とりあえずこれだけあればおおよそ問題ありません

Debug.Print Range("A2")
Debug.Print Range("A2").Value
→→→ #2020/05/19#

これはすでに解説したようにValueプロパティが省略されているので値が取得されます
なので、この2行のコードは同じものを意味しています
日付データを取得しているので、日付の両側に#が付与されています

Debug.Print Range("A2").Text
→→→ "2020年5月19日"

セルに表示されている状態のデータを取得するには、「Text」プロパティを使用します
これは基本的には、表示形式が適用された後のデータを取得します
Textなので、例え数値であっても文字列として取得されます

そして、このプロパティは「表示されている状態」の、と言ったように画面に見えている状態を取得します

例えば、入力値がセルの列幅より大きく表示できない場合Excelでは「#」の連続文字列で表現されることがありますが、それもしっかりその文字列を取得します

表示されているものを取得している状態
「#」が取得されている

この画像の様に「#」がイミディエイトに出力されます
実際は上記の内容と全く同じデータで「2020年5月19日」が入力されていますが、列幅を狭めた結果、表示できない表現として画像の様になります

Textプロパティはこの表現さえも素直に取得を行います
画面に表示されている状態というのは、こういうことになります

Textプロパティは表示形式が適用されたデータではない、という点に注意が必要です

ちなみに、この内容を反対に応用すれば入力したデータが列幅に収まっているかどうかを判定することも出来ますね

Debug.Print Range("A2").Formula
→→→ =A1+B1

数式を取得する場合は「Formula」プロパティを使用します
このプロパティであれば、関数の結果の値ではなく数式自体を取得することが可能です

VBAでセルに入力された数式を取得して作業を行うことは少ないのですが、配列への代入時などでは重宝するプロパティです

また、このプロパティはセル入力値が数値や文字列であれば、それをそのまま取得するので数式以外が取得できない、というものではありません

Debug.Print Range("A2").Value2
→→→ 43970

日付データが実際には内部数値で扱われている、という点はある程度Excelを勉強した方ならご存じかとは思います
その内部数値を取得することが出来るのが「Value2」プロパティです

このプロパティは、なんの加工も行われていない純粋なデータを取得するプロパティです
リファレンスでも書いてあることなのですが、その性質上セルのプロパティで処理速度が最高のプロパティです

処理速度は間違いなく速いです、そこでValueプロパティと比較してみました
リファレンスにもあり、結果が明らかですのでコードは記載しませんが、内容としては変数にセルの入力値を代入させるだけの処理をループ処理で実行しました

自分の環境になりますが、100万セルで0.1秒ほどの差でした
Value2プロパティの方がやはり速かったです

ですが、100万セルで0.1秒ほどなので相当量のビッグデータを扱う際に気にしてみる
程度で十分な差でしかありませんでした

ちなみに、これも明らかですがTextプロパティは圧倒的に遅かったです
これは文字列を扱っているので想定の範囲内ではあります
まあ、Textプロパティは目的があって使用する場面の方が多いのであまり気にしなくても良いかなとは思います

処理速度の優位性はありますが、内部数値が欲しい、という場面でValue2プロパティを使用すれば良いです

配列を並び替え(昇順・降順)する関数

Worksheetのソート機能を利用して配列を並び替えする関数

Sub zzz配列並替(ByRef zzh指定配列 As Variant, Optional zzh降順 As Boolean = False)
    Application.ScreenUpdating = False
    Dim zzループ用 As Variant, zz配列要素数 As Long
    Dim zzCell As Range: Set zzCell = Workbooks.Add.Worksheets(1).Range("A1")
    For Each zzループ用 In zzh指定配列
        zzCell = zzループ用
        Set zzCell = zzCell.Offset(1, 0)
    Next zzループ用
    Set zzCell = zzCell.Offset(-1, 0)
    If zzh降順 = False Then
        Range(zzCell.Parent.Range("A1"), zzCell).Sort zzCell, xlAscending
    Else
        Range(zzCell.Parent.Range("A1"), zzCell).Sort zzCell, xlDescending
    End If
    zz配列要素数 = LBound(zzh指定配列)
    For Each zzループ用 In Range(zzCell.Parent.Range("A1"), zzCell)
        zzh指定配列(zz配列要素数) = zzループ用
        zz配列要素数 = zz配列要素数 + 1
    Next zzループ用
    zzCell.Parent.Parent.Close False
    Set zzCell = Nothing
    Application.ScreenUpdating = True
End Sub

配列データはそのままでは並び替えを行えません
一度分解して、配列を再作成する必要があります
配列を昇順・降順の指定をして並び替えを実行する関数です

この関数はワークシートのソート機能を利用しています
ワークシートを新規作成してそこにデータを一度出力して並び替えをしています
なので、ワークシートを作成する処理時間が余分にはかかりますが、なんせデータ型への対応が柔軟なので汎用処理に向いています

この関数を使用する際に注意点があります
この関数では配列データをそのままセルに入力して取得しなおすだけの処理でありデータの型を認識していません
そのため、文字列データとしてある数値の場合に頭文字の0が抜けることがあります
「0123」→「123」という形に振り替えられます
これはExcelの仕様上の部分でもありますが、型を事前に取得して文字列として扱えば対応は可能です
もし、そういったデータを扱う際は少し改修して使用する必要があります
これに関しては、あまり遭遇しなかったので必要性が高そうなら改修コードを掲載しようとは思います

関数の書式

引数(太字は必須引数)
(zzh指定配列, zzh降順)

「zzh指定配列」は、並び替えを実行したい配列データを指定します
必須項目でここに指定した配列を加工します

「zzh降順」は、並び替えの順序の指定です、省略可能で省略時はFalseになります
Falseの場合は昇順に、Trueの場合は降順に並び替えされます

コードの使用方法

Call zzz配列並替(セル配列)

SubプロシージャなのでCallステートメントを使用して処理を呼び出します
その際引数には指定配列を指定します、2つ目の引数は省略しているのでこのコードの場合は配列は昇順に並び替えが実行されます

データ加工前の配列データ
加工前の配列データ

加工を行う前の数値で作成された配列データです
これを関数を使用して並び替えを行います

配列データを関数を使用して昇順に並び替えた状態
関数を実行した状態

上記のコードを実行して配列データが昇順に並び替えられた状態のデータです
昇順なので数字の小さいものから順番に並んでいることが確認できます

ただ、これだけならわざわざワークシート機能を利用しなくても出来ます
そのほうが遥かに高速に処理できます
それは数値を加工するからです、これを文字列も含むとなるとやっかいです
まして同じ関数内にそれを組み込むとなると大変です

複雑なデータ型になっている配列データ
いろいろなデータ型の配列

画像を確認してください、特に3列目の型の部分です
Valiant型ですが、内部的にはバラバラの型になっていることが分かります

これを数値の並び替えでは対応できませんし、数値専用・文字列専用の関数を別に作成するのも使用するのも面倒なのでワークシート機能を利用するのが結果楽になります

複雑な配列データを関数を使用して昇順に並び替えた状態
昇順に並び替えた状態

この画像は関数を使用して昇順に並び替えた状態のデータです
型ごとに昇順に並んでいることが確認できます

これは単にワークシートのソート機能を1回実行しているだけで可能です
この並び替え処理をVBAで書いたら、いったいどれだけのコードになるのか分かりません
Microsoftの開発者さんの凄まじさが良くわかります

複雑な配列データを関数を使用して降順に並び替えた状態
降順に並び替えた状態

関数の2つ目の引数にTrueを指定して降順に並び替えた状態です
こちらも型ごとにしっかり並び変わっていることが確認できます

Call zzz配列並替(セル配列, True)

こんな感じで2つ目の引数は省略せずにTrueを指定してください

こんな無茶苦茶な配列データなんか、ねえよ!ってのも聞こえてきますが、というか自分自身がなにより思いますが、このソート機能の凄まじい処理能力のためなら、少しの処理時間は犠牲にしてもええんちゃいますか?だめですか?

自分の環境では配列データ数が1000で0.2秒ほど、10000で0.5秒ほどです
処理がこれだけではなく組み込んで、他の処理もあることを考えても実用レベルかなとは思います

コード解説

Sub zzz配列並替(ByRef zzh指定配列 As Variant, Optional zzh降順 As Boolean = False)

~~ 中略 ~~

End Sub

関数のプロシージャ範囲です
この関数は引数の配列データを加工する関数なのでSubプロシージャで作成しています

引数は2つあります

「ByRef zzh指定配列 As Variant」は、関数で加工を行う配列の引数です
この配列データを加工するのでByRefキーワードを使用しています
これによりこの関数で行った加工を呼び出し元に戻すことが出来ます
また配列データなのでValiant型です

「Optional zzh降順 As Boolean = False」は、関数で使用する並び替え順序の指定を行う引数です
この引数はOptionalキーワードを使用していますので省略可能です
省略した場合はFalseが指定されます
Falseを指定した場合は昇順に、Trueを指定した場合は降順に並び替えます
フラグなのでBoolean型です

    Application.ScreenUpdating = False

この関数はすでに触れましたが、ワークブックを新規作成します
その処理上どうしてもその新規作成したものにフォーカスが移ってしまうので、画面遷移が発生しますので、処理速度向上のためにここで画面遷移を無効にします

    Dim zzループ用 As Variant, zz配列要素数 As Long

使用する変数の宣言箇所です、使用する変数は3つあります

「zzループ用 As Variant」は、指定した配列データをForループを実行する際に代入させる変数です
配列を代入させるのでValiant型になっています

「zz配列要素数 As Long」は、配列の要素数を指定する際の整数変数です
並び替えを実行した後に、配列を更新する際に使用します

    Dim zzCell As Range: Set zzCell = Workbooks.Add.Worksheets(1).Range("A1")

3つ目の変数は、宣言と同時に代入しているので1行が長くなっています

「zzCell As Range」は、新規作成したワークブックのA1セルを代入する変数です
このセルに配列データを入力していきます
新規作成した時点でのフォーカス移動は仕方ありませんが、ここで変数に代入させておくことで選択をしなくてもコードが分かり易くなるので変数を利用します

その後の後半部分が、ワークブックを新規作成してそのシートとセルを一括で指定しています
Addメソッドで完結してから、セルを代入させることもできますが、その場合ブックの指定がActivebookオブジェクトやらを使用する必要があるので、この1行が理解できるならこの方がスマートなコードになります

    For Each zzループ用 In zzh指定配列
        zzCell = zzループ用
        Set zzCell = zzCell.Offset(1, 0)
    Next zzループ用

指定した配列データを1要素ずつ全てをループしています
なので多次元配列であっても対応は可能ですが、ここから1次元配列として生まれ変わってしまいます

「zzCell = zzループ用」の箇所でセルに配列データを入力しています

「Set zzCell = zzCell.Offset(1, 0)」は1つ下のセルにセル変数を移動させています
これで改行を行っているような形です

    Set zzCell = zzCell.Offset(-1, 0)

ループが終了したら、セル変数を1つ上に移動させます
というのも、ここまででデータ全てを入力したあと改行しているので、データからはみ出した空白のセルが参照されている状態です

この後でデータの最下部を必要とするので、そこにセル変数を移動させておく必要があります

    If zzh降順 = False Then
        Range(zzCell.Parent.Range("A1"), zzCell).Sort zzCell, xlAscending
    Else
        Range(zzCell.Parent.Range("A1"), zzCell).Sort zzCell, xlDescending
    End If

ここで並び替えを実行しています
ワークシートのソート機能を実行しているだけです

「If zzh降順 = False Then」の部分で分かるように、引数の変数の指定によって処理を分岐させています

処理自体は上記にあるようにソートをかけているだけです
その際、セル範囲を指定して実行していますが、他にデータがある訳でもないのでどっちでもええっちゃええですね

    zz配列要素数 = LBound(zzh指定配列)

配列の下限数を取得しています

配列の個数は変わらないので要素数を再定義する必要はありません
ですが下限値は0とは限らないため、元々の下限値に合わせておく必要があります
下限値が合えば個数が変わらないため上限値も同時に確定するので、そちらは特に取得する必要はありません

    For Each zzループ用 In Range(zzCell.Parent.Range("A1"), zzCell)
        zzh指定配列(zz配列要素数) = zzループ用
        zz配列要素数 = zz配列要素数 + 1
    Next zzループ用

この箇所では並び替えを実行したセル範囲を1セルずつループにより元配列データに上書きをしていきます

その際の要素数の指定は先ほど取得していた下限値から始めていきます
元の配列データの中身に上書きしたら、要素数を更新してループを行います

    zzCell.Parent.Parent.Close False

配列の加工が終了したら新規作成したワークブックを閉じます

ここまでではブックの取得は行っていません、セルの取得のみです
しかしそのセルは対象のブック内のオブジェクトです

「Parent」プロパティを使用することで上位のオブジェクトを指定できます
ここでは2回連続で指定されています
これは、1回目のParentがワークシートになります、2回目がその上位のオブジェクトとなりワークブックが指定されます

このプロパティをもう1回連結すると、Excelを指定することができます

ワークブックのCloseメソッドを実行してブックを閉じますが、セルを編集しているため保存確認が表示されてしまうのでそれを回避するために、メソッドの引数にFalseを指定します
この指定を行うと、ブックが保存済みのステータスになるので終了時に確認が表示されなくなります

    Set zzCell = Nothing
    Application.ScreenUpdating = True
End Sub

最後にセル変数の解放と画面遷移を無効にしていた設定を元の有効な設定に戻します

少し回りくどいかもしれませんが、数値でも文字列でも対応できる柔軟さを考えるとこんな感じでExcelの機能を有効利用した方がええよね~

Removeメソッド(Dictionary)

Dictionaryオブジェクトのリストから指定の項目を削除するメソッドです

'指定keyデータを削除
zz辞書.Remove (2)

Dictionaryオブジェクトに作成したリストデータから特定keyのデータを削除するには「Remove」メソッドを使用します

引数に指定したkeyが存在しない場合は実行時エラーが発生します

メソッドの書式

引数(太字は必須引数)
Remove (key)

「key」は、必須引数で削除を行うkeyを指定します
また、itemを指定しての削除は行えません

実際の使用例

Dictionaryオブジェクトから項目を削除する動き
メソッドの動き

Dictionaryオブジェクトに3つの項目を作成してから、メソッドを実行して削除を行っています

削除にはkeyを指定するのでここでは「2」を指定して、そのkeyのデータをitemと一緒に削除します

コード実行後に値列の「2」が無くなっていることで削除の確認が出来ます

メソッドでは指定したkeyが見つからない場合は、実行時エラーが発生することは解説しましたが、このDictionaryオブジェクト全般の注意点として、値と文字列が別のデータとして認識される点には注意が必要です

この画像の例では、「2」はInteger型なので値として取得されています
ここで文字列の「2」を指定するとkeyが見つからずにエラーが発生しますのでkeyを指定する場合は文字列なのか値なのかは処理上、理解した上で運用するようにしてください

配列データを重複を除外して再作成する関数

配列データを重複しない配列データに加工する関数

Sub zzz重複除外(ByRef zzh指定配列 As Variant)
    Dim zz配列_Loop As Variant, zz作成配列() As Variant, zz配列要素数 As Long, zz判定辞書 As Object
    If IsObject(zzh指定配列) = True Then
        Err.Raise (13)
        Exit Sub
    Else: End If
    Set zz判定辞書 = CreateObject("Scripting.Dictionary")
    For Each zz配列_Loop In zzh指定配列
        If zz判定辞書.Exists(zz配列_Loop) = False Then
            zz判定辞書.Add zz配列_Loop, zz配列_Loop
            ReDim Preserve zz作成配列(zz配列要素数)
            zz作成配列(zz配列要素数) = zz配列_Loop
            zz配列要素数 = zz配列要素数 + 1
        Else: End If
    Next zz配列_Loop
    Erase zzh指定配列
    zzh指定配列 = zz作成配列
End Sub

配列データに一括でデータを取得した後に、そのデータから重複データを削除してユニークなリストを作成する場合に使用する関数です

この関数はユニークではないデータを加工する関数なので、オブジェクト型では実行できません
オブジェクトはそれ自体がユニークな存在なのでそもそも意味がありません

関数の書式

引数(太字は必須引数)
(zzh指定配列)

「zzh指定配列」は、重複を除外したいリストの配列データです
配列データなのでValiant型になります
後述しますが、オブジェクトの代入自体は可能ですが関数内でエラーが発生します

コードの使用方法

Dim zz1次元 As Variant
zz1次元 = Array(4, 1, 6, 3, 6, 7, 9, 7, 5, 4, 2, 2, 3, 7, 8, 8)
Call zzz重複除外(zz1次元)

このコードでは配列の変数宣言を行い、その変数に1次元配列として適当に入力した数値を代入しています
所々、数値がかぶっているのが確認できると思います

その代入後に、その配列データを重複を除外するようにしたい場合に関数を使用します、SubプロシージャになりますのでCallステートメントを使用して引数に配列データを指定して実行してください

関数を使用して重複が除外された配列データ
関数を実行した配列データ

画像を確認してください
この画像の上側にある青くなっている行の部分からが最初に作成された配列データです
要素のいくつかがかぶっているものがあるのが確認できると思います

次に画像下部の赤枠内のデータが関数を使用して重複したデータを除外した配列データになります
同じ数値が要素内に存在していないことを確認してください

これが関数が完了後に引数に指定した配列がデータが振り替わります
例コードでいくと、「zz1次元」が赤枠内の配列データに振り替わる形になります

はい、確認して皆さんふと思いませんでしたかね
重複してないけど順番ぐちゃぐちゃやん、と
そんなあなたにお送りする記事が以下にあります、また見てね

コード解説

Sub zzz重複除外(ByRef zzh指定配列 As Variant)

~~ 中略  ~~

End Sub

ここの関数はSubプロシージャになります
この処理の目的として、元々ある配列データから重複しないリストを作成したい場合に使用します

つまり、元々の配列データがその後の処理に必要な前提がほぼありません
なので、この関数でその元配列を加工するほうが目的に即しています

なので、ここでは引数を1つ設定しています
「zzh指定配列」は配列データを代入されるのでValiant型です
また、この引数の配列を加工して戻すので配列変数自体を受け取る必要があるのでByRefキーワードを使用しています
これにより参照渡しとなり、この関数内で行った加工を呼び出し元に戻すことができます

    Dim zz配列_Loop As Variant, zz作成配列() As Variant, zz配列要素数 As Long, zz判定辞書 As Object

プロシージャで使用する変数の宣言です
このプロシージャでは、3つの変数を使用します

「zz配列_Loop」は、Forループで引数の配列を1要素ずつ検証するための変数です
配列データを代入するのでValiant型です

「zz作成配列()」は、実際に作成する配列データです、重複している件数が初期時点で分からないため、動的配列でかつ都度要素数の再定義を行う必要があります
配列なのでValiant型です

「zz判定辞書」は、Dictionaryオブジェクトを代入する変数です
この重複検証にはいろいろ方法がありますが、DictionaryオブジェクトのExistsメソッドを使用するのが一番コードが分かり易いです
ここでもリストは作成していく形になりますが、あくまでも重複の検証判定用なのでデータとしては使用しません
実行時バインディングを行いますのでObject型です

    If IsObject(zzh指定配列) = True Then
        Err.Raise (13)
        Exit Sub
    Else: End If

ここでは引数に指定された配列データがObject型かどうかを判定しています
上記でも少し解説していますが、Object型はいわゆるRangeであったりするものです
これはそもそもがユニークな存在であり、重複しているものではありません
「Range(“A1”)」がワークシートの数だけ存在する、と思うかもしれませんが、実際の所、Excelからの指定が省略されているにすぎません
Addressプロパティが同じ、というだけに過ぎない訳です

なので、ユニークなリスト作成にそもそもオブジェクトは論外なのです

と、ごちゃごちゃ言いましたがこのIsObject関数がTrueを返せば、Object型になるので処理は行いません

その際実行時エラーを発生させています
それが「Err.Raise (13)」という箇所で、エラー13番を発生させるコードです
13番は型の不一致のエラーです、既存のものを使用しています

    Set zz判定辞書 = CreateObject("Scripting.Dictionary")

ここはDictionaryオブジェクトのインスタンスの作成です
DictionaryオブジェクトはVBAの標準機能ではありません
そういったものを利用する際はこのようにインスタンスの作成を行う必要があります
このコードの動きを実行時バインディングといいます
このあたりは以下の記事で解説していますので確認してください

    For Each zz配列_Loop In zzh指定配列

~~ 中略 ~~

    Next zz配列_Loop

配列の1要素ずつのForループです
ここで指定された配列の全ての要素を検証しています

全ての要素を検証するので、次元数は問いません
2次元であったとしても全て1要素ずつ検証できるので、多次元配列を処理することは可能です
ですが、出力は1次元配列なのでそこに問題が無ければ、という前提にはなります

        If zz判定辞書.Exists(zz配列_Loop) = False Then

~~ 中略 ~~

        Else: End If

重複の検証を行う箇所です

DictionaryオブジェクトのExistsメソッドを使用することでデータがオブジェクトに存在するかを検証しています

ここで存在が無い(Falseが返される)なら、Dictionaryオブジェクトと作成する配列にデータを取得させることになります
存在がある(Trueか返される)なら、何もせず次の要素の検証を行います

             zz判定辞書.Add zz配列_Loop, zz配列_Loop

まず、Dictionaryオブジェクトにデータの取得を行います
ここで取得を行うことで次に検証する材料を整えることが出来ます

            ReDim Preserve zz作成配列(zz配列要素数)
            zz作成配列(zz配列要素数) = zz配列_Loop
            zz配列要素数 = zz配列要素数 + 1

ここで作成する配列への取得を行っています

作成する配列は要素数が未確定のため都度再定義する必要があります
「ReDim Preserve」を使用することによって動的配列の要素数の再定義を行っていますが、その際に元々取得済みのデータは保持されます

要素数の再定義が完了したら、その要素数にデータを代入させます
そして次の要素数の再定義を可能にするために、要素数変数の更新を行っています

    Erase zzh指定配列
    zzh指定配列 = zz作成配列
End Sub

最後に引数配列に作成した配列を代入します
その際、引数配列はEraseステートメントを使用して一旦初期化します

初期化したのち作成配列のデータをそのまま代入させます
これでこの関数が完了し、呼び出し元では引数に指定した配列データが加工済みになっている状態になります

最後にこの関数の使用上の注意点として、数値の「1」と文字列の「1」はそれぞれをユニーク値として判定します

これは解説したように、判定方法がDictionaryオブジェクトのExistsメソッドを使用していることに起因します
このメソッドがそういった仕様になっています

ですが、セルをそのまま取得したようなデータでない限り問題は無いはずです
それに配列の中でそういった型の違うデータを重複しているとするかしないかはその時の処理次第な場合が多いような気もします

結局、数値と文字列で重複を判定したとしてもどちらを取得するかは、その処理次第ということです

セル範囲データを1次元配列化する関数

指定したセル範囲のデータを1次元配列として作成する関数

Function zzzセル範囲1次元配列化(ByVal zzhセル範囲 As Range, Optional zzhText取得 As Boolean = False) As Variant
    Dim zz動的配列() As Variant, zzセル_Loop As Range, zz配列要素数 As Long
    For Each zzセル_Loop In zzhセル範囲
        If zzセル_Loop <> "" Then
            ReDim Preserve zz動的配列(zz配列要素数)
            If zzhText取得 = False Then
                zz動的配列(zz配列要素数) = zzセル_Loop.Value
            Else
                zz動的配列(zz配列要素数) = zzセル_Loop.Text
            End If
            zz配列要素数 = zz配列要素数 + 1
        Else: End If
    Next zzセル_Loop
    zz配列要素数 = 0
    zzzセル範囲1次元配列化 = zz動的配列
End Function

引数に指定したセル範囲のデータを1次元配列に取得させる関数です

通常、セル範囲を配列に取得させると2次元配列になります
これは1列や1行しか指定しなかったとしても、必ず2次元配列になります
なぜならワークシートが2次元のデータだからです

セル範囲をそのまま配列に代入した場合の配列データ
関数を使用せず取得した場合

この画像は、配列にセル範囲をそのまま代入させた場合の配列データです
左のウィンドウにあるのが配列データです
+の記号を展開してもデータは1つしかありませんが、展開をしないとデータが見れません
これが2次元配列になっている状態である、ということです

この配列データでは、セル範囲データをリスト化などする際にデータの参照が少し煩雑になるし、この配列データに行データが1次元目なので、Preserveキーワードを使用して行方向にデータを追加することが出来ません

そんな色々と制約のある多次元配列では困ってしまう場合に使用するのがこの関数です

関数の書式

引数(太字は必須引数)
(zzhセル範囲, zzhText取得)
戻り値の型 Valiant型

「zzhセル範囲」は、配列データとするセル範囲の指定です
ここに指定したセル範囲が対象となります、セル範囲なので複数列や複数行もどちらも含むような範囲指定でも問題はありません

「zzhText取得」は、セルデータを値として取得するか、文字列として取得するかの指定になります
というのも、セルは表示形式という設定があり、値ではなく表示された見えている状態のデータを取得したい場合があります
その際に使用する設定です、ここにTrueを指定すると文字列として取得します
省略可能で、省略した場合はFalseが指定され値として取得を行います

コードの使用方法

Dim zz1次元 As Variant
zz1次元 = zzzセル範囲1次元配列化(Selection)

この関数が配列を返す関数なので、代入先はValiant型の変数を指定します
1行目の変数宣言の部分は、その変数を宣言しております

2行目で関数を使用して選択範囲を1次元配列化しています

関数を使用してセル範囲の値を1次元配列化したデータ
関数を使用して1次元配列化したデータ

画像の左側のウィンドウの配列データを上の画像と比べてみてもらうと分かりますが、+記号が無く、全てのデータが確認することが出来ることが分かると思います

なお、日付データの場合VBA上では画像の様に「#」で囲まれた状態になります
これが日付データであるという表現方法になります
また、3列目にDate型になっていることからも日付データとして取得できていることが確認できます

なお、この関数はデータをリスト化するなどの目的に使用することを前提としているため、空白のセルは無視します
画像では24行目まで選択されていますが、配列データが22番までとなっています
配列は0番から始まっているのでデータの総数は23個ということになります

選択範囲のセル数より少ない要素数になっているのはこのためです
これ以降にデータが存在しなければ、たとえA列全てを選択して100万セルを指定したとしても配列の要素数は23個というのが変わりません

Dim zz1次元 As Variant
zz1次元 = zzzセル範囲1次元配列化(Selection, True)

次に引数2つ目のzzhText取得にTrueを指定して処理を実行してみます

関数の2つ目の引数をTrueにしてセルの値を文字列として1次元配列化したデータ
引数「zzhText取得」をTrueで実行

画像の左側のウィンドウの配列データを確認してください
指定セル範囲は、上記のものと全く同じです
配列の個数も23個というのは同じです

ですが、取得しているデータには「#」が付いていません
代わりに「”」で囲まれています、これにより文字列で取得されていることが確認できます
同じように3列目を確認してみると、String型になっていることからも文字列として取得されていることが分かります

この文字列取得は、単純にデータを数値ではなく文字列で取得する
ということではなく、VBAのRangeオブジェクトのTextプロパティは表示された内容を文字列で取得する動きになります

例えば、金額の表示形式を設定すると「,」が3桁ごとに入力されます
「1500」は「1,500」という表示形式になります、表示形式なのでValueプロパティは「1500」を取得しますが、Textプロパティでは「1,500」という文字列を取得します

案外この表示形式後のデータで取得したい場合はあるので、その場合は引数2つ目をTrueに指定して関数を実行してください

コード解説

Function zzzセル範囲1次元配列化(ByVal zzhセル範囲 As Range, Optional zzhText取得 As Boolean = False) As Variant

~~ 中略 ~~

End Function

Functionプロシージャの処理範囲の始まりと終わりのコードです
Functionプロシージャは戻り値を持つプロシージャで、今回は配列データを返しますので、プロシージャの型はValiant型で宣言しています

この関数では、すでに解説したように2つの引数が指定できます
引数の内容に関してはすでに解説しましたので、割愛します
キーワードの解説のみ行います

1つ目の引数はByValキーワードが設定されています
これは引数の変数自体を値として受け取ることを意味しています
なので、このプロシージャ中でこの引数名の変数を変更しても呼び出し元に影響を与えない形になります、今回変更はしていないので省略しても構いません
また、セル範囲を指定してもらいたいので型はRange型になっています

2つ目の引数はOptionalキーワードが設定されています
これは引数が省略可能なことを意味しています
省略した場合は、Falseが指定されます
TrueとFalseの違いによる処理の分岐は後述します
2択の選択肢になるのでBoolean型になっています

    Dim zz動的配列() As Variant, zzセル_Loop As Range, zz配列要素数 As Long

使用する変数の宣言です
使用する変数は合計3つになります

「zz動的配列() As Variant」は、作成する配列データです
指定されたセル範囲にあるデータの個数で要素数が変化するので動的配列としています
またセル自体が何でも代入できるものなので、変数の型はValiant型で宣言します

「zzセル_Loop As Range」は、指定されたセル範囲をForループで使用するためのRange型の変数です

「zz配列要素数 As Long」は、動的配列の要素数を再定義する際に使用する整数です
この数字を1ずつ加算して要素数を増やしていきます

    For Each zzセル_Loop In zzhセル範囲

~~ 中略 ~~

    Next zzセル_Loop

引数に指定されたセル範囲を1セルずつループして検証を行います
全てのセルを参照するので、セルの個数が増えればそれだけ処理時間が伸びるので注意が必要ですが、そんなに大量なリストを作成する前提は考えていなので適宜対応を考えましょう

        If zzセル_Loop <> "" Then

~~ 中略 ~~

        Else: End If

この関数では空白はセル範囲に指定されていたとしても取得をしないようにしています
それがここのIf分岐になります
セルのデータが空白でなければデータを取得します

なお、関数などで空白が返されていた場合も取得は行いません
当然ですがデータリストに関数を取得させる必要が無いからです
関数は引数があって初めてデータとして成立するため、その関数構文を取得することに意味がありません

            ReDim Preserve zz動的配列(zz配列要素数)

動的配列の要素数の再定義です
取得するデータは空白を無視する仕様から、実際に全てセルの検証を終了してからしか要素数が分かりません

その為、動的配列でかつ都度再定義が必要になります
それをここで行っているわけです

「ReDim Preserve」は要素数の再定義を行い、その際取得済みのデータを保持するコードになります
要素数には整数値を指定しますが、Long型の変数を使用しています
Long型は初期値が0なので、そのまま変数を利用できます

            If zzhText取得 = False Then
                zz動的配列(zz配列要素数) = zzセル_Loop.Value
            Else
                zz動的配列(zz配列要素数) = zzセル_Loop.Text
            End If

ここが実際のデータの配列への取得箇所です
前提として、取得にはセルの値か表示形式の適用された見えているデータかを選択できるようにしていました
なので、ここでその選択によってIf分岐を使用して取得内容を変更しています

zzhText取得が省略されたり、Falseが指定されていれば値の取得を行います
なので、RangeオブジェクトのValueプロパティで取得します

逆にTrueが指定されていれば、RangeオブジェクトのTextプロパティを取得します
なお、このプロパティによって取得されるものは必ず文字列となります
数値であっても文字列になるのですが、その目的のための設定なので特にその動きで問題は無いはずです

また、この設定を混在させたいような複雑な処理は汎用化に向いていませんので、この処理を改造して作成してみてください

            zz配列要素数 = zz配列要素数 + 1

ここで要素数の再定義用のLong型変数を更新しています
配列データなので普通に1を加算しているだけです

    zz配列要素数 = 0
    zzzセル範囲1次元配列化 = zz動的配列

ループが終了したら、要素数再定義用変数は役割を終えているので初期値に戻しておきます
この後使うわけでもないので、無くてもいいです

そしてこの処理はFunctionプロシージャなので最後にプロシージャに作成した配列データを取得させます
これで、この関数の戻り値が確定したことになります

この後に使用した配列も初期化してもいいですね、しなくてもいいですけど
なお、Range型の変数はForループ終了時点で初期化されています
これはForループの仕様です

途中でも少し触れましたが、この関数はビッグデータを想定していません
せいぜい数千セル程度までを想定しています
1列全ての100万セルなら明らかに待ちます
さらに言うなら要素数の定義用変数がLong型なので21億程の範囲を超えるセル数ならエラー発生です

そんな意地悪なセル範囲は指定しないでね

ちなみに、セル範囲をこの関数を使用して1次元配列化したら
次に重複を除外する関数を使用することで、指定セル範囲の重複しないデータリストを作成することが出来ます
と、いうかその目的の為にこの記事の関数は作成しました
以下の記事でその続きの関数がありますので、利用ください

Itemプロパティ(Dictionary)

Dictionaryオブジェクトに作成したデータからitemを取得するプロパティです

’itemを取得する
zz辞書(1)

Dictionaryオブジェクトから取得したデータを取得するには、「Item」プロパティを使用して取得します
このプロパティは省略することが多いです

zz辞書.Item(1)

省略しない場合はこのような書き方をします、省略することが多いので書いても可読性は上がらないかもしれません

コードの様に、Dictionaryオブジェクトから直接「()」で指定することで取得できます
引数にはkeyを指定します

実際の使用例

zz辞書.Add 1, "あ"
zz辞書.Add 2, Range("A1:A3")
zz辞書.Add 3, Array("あ", "い")

このコードを使用して、Dictionaryオブジェクトにリストを作成している状態で解説を行います

zz辞書(1)
→→→ 

Dictionaryオブジェクトに直接「()」を結合して、その中にkeyを指定することでそのkeyにペアリングされたitemのデータが返されます

上記のコードで作成されたリストを確認してください
「あ」が返されることが分かるはずです

こうして、Dictionaryオブジェクトで作成したデータから取得を行います

プロパティ使用時の注意点

zz辞書(4)
→→→ 

このプロパティで取得する際の重要な注意点です
このコードを実行するとエラーは発生せず空白が返されます

本来このプロパティで指定しているkeyは存在しません
keyは上記で作成した物を利用しているので、「1,2,3」の3つだけです

存在しないkeyを指定しているので、本来はエラーが発生しても良いはずです
ですが、このプロパティはエラーを発生させません

別の記事で解説していますが、このプロパティは読み取りだけでなく書き込みも可能なプロパティであるため、存在しないkeyであっても書き込み処理は可能なためエラーが発生しません

と、いうわけでこのDictionaryオブジェクトではkeyの存在確認が非常に重要です
そこをしないと指定のkeyがあるかないか分からないという、すこし手間のかかるオブジェクトです

ですが、そここそがこのオブジェクトの利点でもあるので、このオブジェクトを利用する価値があるのです

Dictionaryオブジェクトからitemデータを取得する場合は、Existsメソッドを使用してkeyの存在確認を必ず行うようにしましょう

Existsメソッド(Dictionary)

Dictionaryオブジェクトから特定のkeyの存在確認をするメソッドです

'Keyの存在確認
Debug.Print zz辞書.Exists(1)

Dictionaryオブジェクトに作成したリストの中から、特定のものがあるかを調べるには「Exists」メソッドを使用します

このメソッドは、keyでの判定を行います
itemでの判定は行えません、itemは重複が可能なためです

このメソッドこそ、このDictionaryオブジェクトの真骨頂といっても過言では無いメソッドです
このメソッドの為に、このオブジェクトを使用する目的になるほどです

メソッドの書式

引数(太字は必須引数)
Exists (key)

「key」はDictionaryオブジェクトに登録したリストデータのkeyを指定します
引数はこれだけで、必須項目です
この引数に指定した内容と一致するデータが存在すれば「True」、存在しなければ「False」が返されます
存在に関わらず、itemが返されることはありませんので注意してください

実際の使用例

zz辞書.Add 1, "あ"
zz辞書.Add 2, Range("A1:A3")
zz辞書.Add 3, Array("あ", "い")

このコードによりリストが作成されます
3行のリストデータを作成しています、ここから特定のデータの存在確認を行ってみます

zz辞書.Exists(1)
→→→ True

ここでのkeyの検索は「1」を指定しています
上記のリスト作成でkeyには「1,2,3」の3つの整数値が作成されているため、ここでは指定したデータが見つかるため、「True」が返されます

zz辞書.Exists(4)
→→→ False

このコードを実行すると「False」が返されます
これは「4」という整数値がkeyに存在しないためです

こうして、指定したデータが存在するかどうかを判定することで参照したいデータがリストに登録されたデータであるかを調べることが出来ます
また、重複しないリストを作成する際に取得したいデータがリストにあるかどうかを、いちいち全ての要素を検証せずに判定が出来ます

ちなみに、このメソッドには型も重要になります

zz辞書.Exists(CStr(1))
→→→ False

このコードを実行すると、今度は「False」が返されます
指定の引数の数字は同じ「1」が指定されています

ですが、今回はCStr関数により整数値から文字列に変換されています
こうして、型に相違があると別の値と認識するため、存在しないと判断します

基本的にこのメソッドを使用する際に、こうした変換を行うことが無ければ問題はありません

しかし、Addメソッドによりリスト作成する際に注意する必要があります
それは、セルの値を取得する際などです
セルには数値や日付を文字列として入力することが可能です
逆に文字列と思っていたら、表示形式で実際は数値だったりもします

このセルを取得すると、数値ではなく文字列の数字を取得するので整数値をメソッドの引数に指定した場合、永遠にFalseのままです
見た目は全く同じ「1」であっても、存在しないことにしてしまいます

この型のズレを調整しておかないとリストに取得したはずで、見た目も全く同じデータなのになぜかExistsメソッドでFalseしか返ってこない、という現象に遭遇します

特にまだ変数に慣れきっていないような方は、型の理解が難しいかもしれません

ですが、このメソッドの利用価値が非常に高いため、この点はしっかり理解しておいてください
そうすることでより便利にこのExistsメソッドを利用できるようになります

Addメソッド(Dictionary)

Dictionaryオブジェクトにデータを新規追加するメソッドです

'データを新規追加する
zz辞書.Add 1, "い"

Dictionaryオブジェクトのリストにデータを追加するには、Addメソッドを使用します
このメソッドは追加になるので、指定のインデックス番号の箇所に挿入するような動作は出来ません

メソッドの書式

引数(太字は必須引数)
Add key, item

引数は2つで、どちらも必須項目になります
また、この2つがペアとしてオブジェクトにデータが登録されます

「key」は、データを検索や取得を行う際に目印となるデータです
ここのデータは重複したデータは登録できませんので、重複する場合は実行時エラーが発生します
また配列以外のデータで指定する必要があります、連番であったり管理コードなどでも構いませんが1つのデータにする必要がある訳です

「item」は、Keyにペアリングされるデータになります
ここは配列データやオブジェクトも代入させることができますので、比較的自由に指定できます
基本的に、このデータを取得することになります

実際の使用例

zz辞書.Add 1, "あ"

基本的なデータの追加方法です
keyとして「1」を、itemとして「”あ”」を追加します

これで、keyの「1」を指定した場合「”あ”」が取得されるデータが作成されます

1つ目のデータの取得と出力
データを追加した状態

画像はコードによりデータを追加作成した後の状態です
まず、右側のローカルウィンドウの辞書にitemが追加されて値が「1」になっていることが確認できます
なお、ローカルウィンドウではitemを確認することができませんのでkeyのみが表示された状態です

次に左下のイミディエイトを確認してください
追加したデータを出力しています、itemに登録した「あ」が出力されています

zz辞書.Add 2, Range("A1:A3")

2つ目はitemにオブジェクトを取得させてみます
Rangeオブジェクトを指定することで、そのセル範囲をkeyから取得することが出来ます

itemにRangeオブジェクトを代入させたとき
Rangeオブジェクトを操作した状態

イミディエイトを確認してください
2行出力されています

まず1行目は、取得させたRangeオブジェクトのAddressプロパティを出力しています
取得がA1~A3なので、そのセルアドレスが出力されています
このことからもitemにしっかりセル範囲が取得できていることが分かります

そのセルに値を代入してみます、その代入した値を再度イミディエイトに出力しています
「zz辞書(2)(2) = 100」の部分が、セルへの値入力の箇所になります
このうちzz辞書(2)はRange(“A1:A3”)と同義になるので、「Range(“A1:A3”)(2) = 100」と書き換えることが出来ます
このセル範囲の2番目のセルなので、A2が指定されることになります

なので、イミディエイト2行目の100はA2セルの入力値であることが分かります

インテリセンスも使用できず、分かりにくいことこの上ないので、あまりこういった使い方はしないかもしれませんが、オブジェクトも可能ということです
ユーザーフォームなどオブジェクトの多い時には役に立つかもしれませんね

zz辞書.Add 3, Array("あ", "い")

ここでは配列データをitemに取得させています
2次元配列の2次元目を追加しているようなイメージになりますね

itemに配列を代入した時
配列データを取得させる

1次元の配列データを取得させています
「”あ”,”い”」の2つの要素を含む配列データになります

その配列から特定のデータを取得するには、要素数のインデックス番号で指定することで可能です
配列のインデックス番号は0から始まりますので、「zz辞書(3)(1)」は2番目の要素を指定することになります

イミディエイトには2番目の要素である「い」が出力されています

こちらもオブジェクト同様、イメージしづらく
なによりローカルで確認できないので、処理作成はやりづらいです

要は、itemには数値や文字列だけでなく配列もオブジェクトも代入させることが出来るという点に理解を持っておいてもらえれば何かの役に立つときが来ます

zz辞書.Add Array("あ", "い"), 4

これはエラーになるコードです
keyに
配列データを指定しているためです

keyに配列を指定した場合のエラーメッセージ
実行時エラーのメッセージ

画像の様に実行時エラーが発生します
keyには配列データを指定することは出来ません

これをしたいなら、keyを「あ」と「い」の2つを登録して、itemは同じ値を取得させるようにします

なお、オブジェクトの取得は可能ですが、使う事は無いでしょう

必要なデータを分かり易くリスト化して扱うのが、このDictionaryオブジェクトです
処理速度も速いのでサクサク登録していきましょう