和風スパゲティのレシピ

日本語でコーディングするExcelVBA

36本目:列の並べ替え

Excel&VBA解説サイト「エクセルの神髄」様出題の問題集、
VBA100本ノック」に対する私の回答と解説のページです。

100本ノックの出題リストはこちらから
excel-ubara.com

出題:列の並べ替え

#VBA100本ノック 36本目
1行目の見出しの後ろには半角括弧()の中に数値が入っています。
この()括弧内の数値の昇順で列を並べ替えてください。
・全ての列に数値の入った()が正しく最後についています。
・数値は1~3桁の正の整数です。
※非表示列はありません。
※シートは任意

データサンプル

◇ 出題ページはこちら
https://excel-ubara.com/vba100/VBA100_0●●.html

ソースコード

メインモジュール

Option Explicit

' 100本ノック036:列の並べ替え
Sub 見出しのカッコ内番号をキーに列を並び換える()
    With WSデータ
    
        ' 見出しの上部に行を挿入して()内番号を抽出
        .Rows(1).Insert
        Dim C As Long
        For C = 1 To Get最終列(WSデータ)
            .Cells(1, C) = Mid文字列から文字列(.Cells(2, C), "(", ")")
        Next
        
        ' 第1行をキーとして列を並び替え
        Dim ソートエリア As Range
        Set ソートエリア = .Range("A1").CurrentRegion
        ソートエリア.Sort .Range("A1"), Orientation:=xlSortRows
        
        ' 作業行を削除
        .Rows(1).Delete
    
    End With
    
End Sub

汎用関数モジュール

Option Explicit

' 文字列の切り出し
Function Mid文字列から文字列(ByVal 元テキスト As String, ByVal 前検索値 As String, ByVal 後検索値 As String _
    , Optional is前検索値を結果に含む As Boolean = False, Optional is後検索値を結果に含む As Boolean = False) As String

    Dim instr前値 As Long: instr前値 = InStr(元テキスト, 前検索値)
    If instr前値 = 0 Then Exit Function

    Dim instr後値 As Long: instr後値 = InStr(instr前値 + Len(前検索値) + 1, 元テキスト, 後検索値)
    If instr後値 = 0 Then Exit Function

    Mid文字列から文字列 = _
        IIf(is前検索値を結果に含む, 前検索値, "") & _
        Mid(元テキスト, instr前値 + Len(前検索値), instr後値 - instr前値 - Len(前検索値)) & _
        IIf(is後検索値を結果に含む, 後検索値, "")

End Function

' 最終列の取得
' 参考:https://www.limecode.jp/entry/library/get-lastrow-lastcolumn
Function Get最終列(指定オブジェクト As Variant, Optional ByVal R As Long = -1) As Long

    ' 渡されたオブジェクトからセル範囲を取得
    Dim 対象セル範囲 As Range
    Select Case TypeName(指定オブジェクト)
    Case "Range"
        If 指定オブジェクト.Cells.CountLarge = 1 Then ' 単独セルにはCurrentRegionを取る
            Set 対象セル範囲 = 指定オブジェクト.CurrentRegion
        Else
            Set 対象セル範囲 = 指定オブジェクト
        End If
    Case "Worksheet"
        Set 対象セル範囲 = 指定オブジェクト.UsedRange
    Case "AutoFilter", "ListObject"
        Set 対象セル範囲 = 指定オブジェクト.Range
    Case Else
        Err.Raise 1000, , "対象外のオブジェクト「" & TypeName(指定オブジェクト) & "」が指定されました。"
    End Select

    ' エリアの最終列を取得
    Get最終列 = 対象セル範囲.Columns.Count + 対象セル範囲.Column - 1

    ' 列が指定されていればその列の入力最終行を取得
    If R <> -1 Then
        Do While 対象セル範囲.Worksheet.Cells(R, Get最終列) = ""
            Get最終列 = Get最終列 - 1
            If Get最終列 < 対象セル範囲.Column Then
                Get最終列 = 0
                Exit Function
            End If
        Loop
    End If

End Function

解説

列見出しをキーとして並び換えを行う問題でした。

見出しそのままがキーではなくカッコ内の数値を取る必要があるため、
一旦作業行を挿入してキー行を作成しています。


カッコ内を取り出す処理は汎用関数「Mid文字列から文字列」を用いました。

Excel365であればTEXTAFTER/TEXTBEFOREが使えますが、
そうでない場合は作っておくと便利な関数です。


列のソートはSortメソッドにそのままその機能があり、
「Orientation:=xlSortRows」を指定するだけでOKです。




余談ですが、この「Orientation:=xlSortRows」にとんでもない誤植があります。


この「xlSortRows」は行を並び替えるという意味ではなく、
行をキーとして列を並び替えるという意味になります。

よって普段(省略時)は「xlSortColumns」になっており、
これをRowsと指定することで列の並び替えに挙動を変えることが出来ます。


しかし、この「行をキーとしたのか」「行を並び替えるのか」、
どちらを指して「Rows」と言っているのかが非常にわかりづらいですよね。


そしてこの分かりづらさはMicrosoftの開発陣も混乱させたようで、

▼ Microsoft公式「Sortメソッド」
Sort

▼ Microsoft公式「XlSortOrientation 列挙」
XlSortOrientation

▼ Sortメソッドの引数ヒント
Sort引数ヒント

なんと3つすべてで「省略時はxlSortRows」と大ウソをこいています。


よく嘘が書いてあるMicrosoft公式ページはまあわかるんですが、
引数ヒントに誤植があるのはあまり記憶にないくらい珍しいですね。


正しくは「省略時はxlSortColumns」であり、
これは「列をキーとして行を並び替える」処理になっています。

混乱しない方が無理だと思いますが、混乱しないように気を付けていきましょう。