配列ソート | Excel作業をVBAで効率化 https://vbabeginner.net いつものExcel作業はVBAを使えば数秒で終わるかもしれませんよ Sun, 10 Nov 2024 15:09:17 +0000 ja hourly 1 https://wordpress.org/?v=6.6.2 https://vbabeginner.net/wp-content/uploads/2019/02/favicon-150x150.png 配列ソート | Excel作業をVBAで効率化 https://vbabeginner.net 32 32 VBAの配列を偏りなく高速にランダムに並べ替える https://vbabeginner.net/randomly-sort-arrays/ Wed, 04 Mar 2020 15:48:52 +0000 https://vbabeginner.net/?p=4694 ランダムな配列の必要性

通常、配列はなんらかの規則性を持って並んでいることが多いのですが、並び順が不規則であることが必要な場合があります。データの最大や最小や昇順や降順などを無視した状態でのテストを行うような場合で、データがいくつあるか不明、最大値も不明、最小値も不明、でもそこから結果がどうなるのかを検証しなければならない、というようなことがあります。

そのような場合には規則性を持ったデータ列はむしろ邪魔で、めちゃくちゃな並び順であることの方が必要になります。手で入れ替えてもいいのですが、数が多くなれば当然大変です。

以下で配列の内容を値に関係なく偏りなくランダムに並べ替えるコードを紹介します。

処理速度はO(n)と高速に動作するフィッシャー-イェーツのシャッフルアルゴリズムを採用しています。当該アルゴリズムについての詳細は各自で。

コード

以下は配列の要素を高速にランダムに入れ替える関数です。引数には配列を渡します。

Sub SwapArray(ar)
    Dim iCount      As Long     '// 配列要素数
    Dim i           As Long     '// ループカウンタ
    Dim iRnd        As Long     '// 乱数値
    Dim s                       '// 一時保持バッファ
    Dim bObject     As Boolean  '// 引数データ型判定
    
    '// 配列要素数を取得
    iCount = UBound(ar)
    
    '// 入れ替え不要の場合は処理を抜ける
    If iCount < 1 Then
        Exit Sub
    End If
    
    '// 引数がオブジェクト型か判定
    bObject = IsObject(ar(0))
    
    '// 終端から先頭1に向かってループ
    For i = iCount To 1 Step -1
        '// 乱数値を取得(0?ループごとの終端値の範囲で取得)
        iRnd = Int((i + 1) * Rnd)
        
        '// オブジェクト配列の場合
        If bObject = True Then
            '// 配列の乱数値を終端と入れ替える
            Set s = ar(iRnd)
            Set ar(iRnd) = ar(i)
            Set ar(i) = s
        '// オブジェクト配列ではない場合
        Else
            '// 配列の乱数値を終端と入れ替える
            s = ar(iRnd)
            ar(iRnd) = ar(i)
            ar(i) = s
        End If
    Next
End Sub

処理の開始時に引数配列の要素数を取得し、要素が1つしかない場合は入れ替える必要がないため関数を抜けます。

引数の配列のデータ型はなんでもOKにしたいので、先にデータ型を取得しています。なお、IsObject(ar)と書いてしまうと要素ではなく配列自体の型をとってVariant型と判定されて常にFalse扱いになるため、IsObject(ar(0))として配列の要素でデータ型の判定を行っています。

メインのループ部分ですが、一般的な配列ループは先頭から終端に向かいますが、逆に終端から先頭に向かってループしています。これは2つの理由があり、一つはアルゴリズムのルールに従っていることと、乱数値を取得する際の終端値をループごとに1ずつ減らす必要があるのに都合が良かったためです。

同じことを先頭から終端に向かって行うループ(逆順でのループ)でもできますが、(考えるのが面倒だったので)やりませんでした。

ループ内ではループごとに0から配列インデックスの終端値の範囲でRnd関数を使って整数値を取得し、配列のインデックスに利用します。

Rnd関数や乱数については「VBAで乱数を発生させる(Rnd、Randomize)」をご参照ください。

あとは、取得した乱数値と配列の終端インデックスの要素を入れ替えます。

使い方

上の関数の使い方です。配列作って呼び出すだけです。

String型の配列とRange型の配列を用意してそれぞれで実行します。

Sub SwapArrayTest()
    Dim arString()  As String   '// 文字列配列
    Dim arRange()   As Range    '// セル配列
    Dim i           As Long     '// ループカウンタ
    
    '// テスト用の初期値を設定
    ReDim arString(7)
    ReDim arRange(7)
    
    arString(0) = "a"
    arString(1) = "b"
    arString(2) = "c"
    arString(3) = "d"
    arString(4) = "e"
    arString(5) = "f"
    arString(6) = "g"
    arString(7) = "h"
    
    Set arRange(0) = Range("A1")
    Set arRange(1) = Range("A2")
    Set arRange(2) = Range("A3")
    Set arRange(3) = Range("A4")
    Set arRange(4) = Range("A5")
    Set arRange(5) = Range("A6")
    Set arRange(6) = Range("A7")
    Set arRange(7) = Range("A8")
    
    '// 配列要素入れ替え
    Call SwapArray(arString)
    Call SwapArray(arRange)
    
    '// 文字列配列の入れ替え結果を出力
    For i = 0 To UBound(arString)
        Debug.Print CStr(i) & " - " & arString(i)
    Next
    
    '// セル配列の入れ替え結果を出力
    For i = 0 To UBound(arRange)
        Debug.Print CStr(i) & " - " & arRange(i).Address(False, False)
    Next
End Sub

実行結果(実行するたびに並び順は変わります)
0 – a
1 – c
2 – d
3 – e
4 – b
5 – g
6 – h
7 – f
0 – A1
1 – A6
2 – A8
3 – A7
4 – A5
5 – A4
6 – A3
7 – A2

]]>
VBAの配列を.NETのArrayListのSortで並べ替え https://vbabeginner.net/sort-in-net-arraylist/ Thu, 31 May 2018 15:35:21 +0000 https://vbabeginner.net/?p=3068 配列のソート

配列のソート方法にはいろいろあります。

アルゴリズムとしてのソートにはクイックソートなどがあります。当サイトでも以下を紹介しています。

ただ、アルゴリズムとかの実装は面倒で、単にソートすればOKという場合もあります。

そういう場合の方法の1つに.NET FrameworkのArrayListクラスのSortメソッドがあります。

以下でSortメソッドの使い方を紹介します。Sortメソッドは昇順ソートのためReverseメソッドを使って降順にする方法も紹介しています。

ArrayListについては以下のMicrosoftのヘルプに各プロパティやメソッドが記載されています。

https://msdn.microsoft.com/ja-jp/library/system.collections.arraylist(v=vs.110).aspx

事前設定

.NET FrameworkのArrayListクラスを利用するために場合によっては参照設定が必要な場合があります。

VBA画面のツールメニュー→参照設定で以下の選択もしくは参照してください。
C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb(dllではありません)
Microsoft Common Language Runtime Class Library

参照してもチェックが付かない場合がありますが、その場合は参照設定が不要ですので気にせず未設定で構いません。

ArrayListのSortメソッドによるソート関数

引数の配列をArrayListクラスのSortメソッドで昇順に並べ替えて、再度ToArrayメソッドで配列に戻す関数です。

条件として引数の配列は呼び出し元もVariant型の配列であることです。

型変換が必要な場合はソート後に呼び出し元で行うようにしてください。

Sub ArrayListSort(ary() As Variant)
    Dim aryList As Object   '// ArrayList
    Dim s                   '// 文字列
    
    '// .NET FrameworkのArrayListクラスを利用する
    Set aryList = CreateObject("System.Collections.ArrayList")

    '// 配列をArrayListにコピー
    For Each s In ary
        Call aryList.Add(s)
    Next
    
    '// 並べ替え
    Call aryList.Sort
    
    '// 配列に再格納
    ary = aryList.ToArray
End Sub

 

ArrayListのReverseメソッドによる反転関数

Sortメソッドは昇順です。

昇順ではなく降順にしたい場合は一度Sortメソッドで昇順に並べ替えたあとに、Reverse関数で反転させることで可能になります。

以下はその反転を行う関数です。SortだったのがReverseになっているだけです。

引数はSort時と同じでVariant型の配列になります。

Sub ArrayListReverse(ary() As Variant)
    Dim aryList As Object   '// ArrayList
    Dim s                   '// 文字列
    
    '// .NET FrameworkのArrayListクラスを利用する
    Set aryList = CreateObject("System.Collections.ArrayList")

    '// 配列をArrayListにコピー
    For Each s In ary
        Call aryList.Add(s)
    Next
    
    '// 並べ替え
    Call aryList.Reverse
    
    '// 配列に再格納
    ary = aryList.ToArray
End Sub

 

使い方

上記のArrayListSort関数の使い方です。

Variant型の配列を用意し、各要素にデータを格納します。

あとはArrayListSort関数に渡すだけです。

Sub ArrayListSortTest()
    Dim ary()   As Variant   '// 文字列配列
    Dim i
    Dim s
    
    ReDim ary(9)
    
    ary(0) = "444"
    ary(1) = "333"
    ary(2) = "000"
    ary(3) = "111"
    ary(4) = "555"
    ary(5) = "222"
    ary(6) = "666"
    ary(7) = "999"
    ary(8) = "888"
    ary(9) = "777"
    
    '// 昇順ソート
    Call ArrayListSort(ary)
    '// 反転
    Call ArrayListReverse(ary)
End Sub

実行結果

20行目のSort直後の配列の並び

“000”
“111”
“222”
“333”
“444”
“555”
“666”
“777”
“888”
“999”

22行目のReverse直後の配列の並び

“999”
“888”
“777”
“666”
“555”
“444”
“333”
“222”
“111”
“000”

高速化

上記のSort用とReverse用の関数はそれぞれでCreateObject関数を実行しているため、何度も呼び出される場合にはそれが処理速度の低下になります。

何度も呼び出す場合はCreateObject関数を呼び出し元で1度だけ行うようにして、aryList変数を引数で渡すようにすると処理速度は向上します。

その際に注意点があります。

aryList変数を使いまわす場合に前回利用時の値をクリアする必要があります。

そのため、利用する直前に「Call aryList.Clear」と書いてデータクリアを行うようにしてください。

以下はCreateObject関数を呼び出し元で行うようにしたコードです。

処理は上のコードと同じですがCreateObject関数の呼び出しが1回になったこととArrayListクラスのaryList変数のクリアがSortとReverseを呼び出す度に行うようになっています。

Sub ArrayListSortTestEx()
    Dim ary()   As Variant  '// 文字列配列
    Dim aryList As Object   '// ArrayList
    Dim i
    Dim s
    
    ReDim ary(9)
    
    ary(0) = "444"
    ary(1) = "333"
    ary(2) = "000"
    ary(3) = "111"
    ary(4) = "555"
    ary(5) = "222"
    ary(6) = "666"
    ary(7) = "999"
    ary(8) = "888"
    ary(9) = "777"
    
    '// .NET FrameworkのArrayListクラスを利用する
    Set aryList = CreateObject("System.Collections.ArrayList")
    
    '// 昇順ソート
    Call ArrayListSortEx(aryList, ary)
    '// 反転
    Call ArrayListReverseEx(aryList, ary)
End Sub

Sub ArrayListSortEx(aryList As Object, ary() As Variant)
    Dim s                   '// 文字列
    
    '// ArrayListをクリア
    Call aryList.Clear
    
    '// 配列をArrayListにコピー
    For Each s In ary
        Call aryList.Add(s)
    Next
    
    '// 並べ替え
    Call aryList.Sort
    
    '// 配列に再格納
    ary = aryList.ToArray
End Sub

Sub ArrayListReverseEx(aryList As Object, ary() As Variant)
    Dim s                   '// 文字列
    
    '// ArrayListをクリア
    Call aryList.Clear
    
    '// 配列をArrayListにコピー
    For Each s In ary
        Call aryList.Add(s)
    Next
    
    '// 並べ替え
    Call aryList.Reverse
    
    '// 配列に再格納
    ary = aryList.ToArray
End Sub

]]>
VBAの配列を逆順に並べ替え https://vbabeginner.net/sort-vba-array-in-reverse-order/ Wed, 09 Aug 2017 17:27:03 +0000 http://vbabeginner.net/?p=719 配列を逆から参照すると可読性が落ちる

配列を利用する際に、配列の逆から参照したい場合があります。

以下は逆順から参照するコード例です。

Sub RevLoop(ar())
    Dim i
    Dim iLen
    
    iLen = UBound(ar)
    
    '// For文で逆順に書いた場合
    For i = iLen To 0 Step -1
        Debug.Print ar(i)
    Next
    
    '// Do文で逆順に書いた場合
    i = iLen
    Do
        If i < 0 Then
            Exit Do
        End If
        
        Debug.Print ar(i)
        i = i - 1
    Loop
End Sub

このように、逆順に参照した場合はループカウンタの初期値に最大値を設定して、カウンタが-1減るコードになるため、ソースコードの可読性がどうしても落ちてしまいます。

また、For Each文は逆順では使えません。

過去に、通常ループと逆順ループが混在しているソースを見たことがありますが、フラグやインデックス変数が乱立してもうわけわかりませんでした。

こういう場合は通常の順番のループに統一した方がよいでしょうね。

配列を逆順に並べ替え

以下のコードは配列の順番を反転させて逆順になるようにします。

一応Option Base 1の場合の対応としてLBound関数を利用していますが、そうでない場合は12行目をiStart = 0としても問題ありません。

Sub Reverse(ar())
    Dim iStart
    Dim iEnd
    Dim s
    
    '// 配列でない場合は処理をしない
    If IsArray(ar) = False Then
        Exit Sub
    End If
    
    '// 配列の添え字の最小と最大を取得
    iStart = LBound(ar)
    iEnd = UBound(ar)
    
    '// 配列の入れ替えが終わるまでループ
    Do
        '// 添え字の大小が逆転もしくは一致した場合はループ終了
        If iStart >= iEnd Then
            Exit Do
        End If
        
        '// 配列要素の入れ替え
        s = ar(iStart)
        ar(iStart) = ar(iEnd)
        ar(iEnd) = s
        
        '// 添え字の移動
        iStart = iStart + 1
        iEnd = iEnd - 1
    Loop
End Sub

以下がテストコードです。
配列が0から4まで順に設定されていますが、Reverse関数で反転します。

Sub ReverseTest()
    Dim ar()
    Dim s
    
    ReDim ar(4)
    ar(0) = 0
    ar(1) = 1
    ar(2) = 2
    ar(3) = 3
    ar(4) = 4
    
    Call Reverse(ar)
    
    For Each s In ar
        Debug.Print s
    Next
End Sub

実行結果
4
3
2
1
0

]]>
VBAの配列を挿入ソートで並べ替え https://vbabeginner.net/sort-vba-array-with-insertion-sort/ Wed, 01 Mar 2017 17:37:44 +0000 http://vbabeginner.net/?p=106 挿入ソート

VBAで配列の並び順を昇順や降順に変えたいことがあります。

ここでは配列を挿入ソートで並べなおすコードを紹介します。挿入ソートは単純挿入法という言い方もされます。同じ値の順序がソート前と変わらない安定ソートです。

ソート処理の実行時間はO(n^2)と速いとは言えないのですが、ある程度整列している状態であれば高速に処理できます。その性質を利用して、クイックソートとの組み合わせを行うことがあります。

クイックソートは一般的にはソートの中で一番高速ですが、ある程度整列している状態であれば挿入ソートの方が高速なため、クイックソートである程度ソートしたあとに挿入ソートで整列する方法が採られることもあります。

なお、以下の挿入ソートのコードを使って指定フォルダ配下の一覧を取得する方法を「VBAで指定フォルダ配下の一覧をソートして取得」で紹介しています。

他のソートについては以下をご参照ください。
VBAの配列をクイックソートで並べ替え
VBAの配列をバブルソートで並べ替え
VBAの配列を.NETのArrayListのSortで並べ替え
VBAの配列を逆順に並べ替え

挿入ソートのコード

以下の挿入ソートは、引数で渡された配列の内容を昇順(小さい順)に並べなおします。

Sub insertsort(a_Ar())
    Dim iNow                    '// ループカウンタ(現要素用)
    Dim iBefore                 '// ループカウンタ(前要素用)
    Dim temp                    '// 一時格納領域
    Dim iArrayCount             '// 配列要素数
    
    '// 配列の要素数を取得
    iArrayCount = UBound(a_Ar)
    
    '// 配列を2番目の要素から最後までループ
    For iNow = 1 To iArrayCount
        '// 配列の要素を一時格納
        temp = a_Ar(iNow)
        
        '// 前要素を取得
        iBefore = iNow - 1
        
        '// 前要素から先頭に向かってループ
        Do
            '// 配列要素外の場合
            If (iBefore < 0) Then
                Exit Do
            End If
            
            '// 入れ替える必要がない場合
            If (a_Ar(iBefore) <= temp) Then
                Exit Do
            End If
            
            '// 配列の前後を入れ替える
            a_Ar(iBefore + 1) = a_Ar(iBefore)
            
            '// 前要素を先頭側に移動する
            iBefore = iBefore - 1
        Loop
        
        '// 挿入個所に事前に取得した値を格納
        a_Ar(iBefore + 1) = temp
    Next
End Sub

 

使い方

利用方法はこのような感じになります。

Sub sorttest()
    Dim ar()
    
    ReDim ar(10)
    ar(0) = "9"
    ar(1) = "8"
    ar(2) = "7"
    ar(3) = "6"
    ar(4) = "5"
    ar(5) = "4"
    ar(6) = "3"
    ar(7) = "2"
    ar(8) = "1"
    ar(9) = "0"
    ar(10) = "a"
    
    Call insertsort(ar)
End Sub

ソート後は、以下の順になります。

ar(0) = "0"
    ar(1) = "1"
    ar(2) = "2"
    ar(3) = "3"
    ar(4) = "4"
    ar(5) = "5"
    ar(6) = "6"
    ar(7) = "7"
    ar(8) = "8"
    ar(9) = "9"
    ar(10) = "a"

是非、活用してください。

]]>
VBAの配列をクイックソートで並べ替え https://vbabeginner.net/sort-array-with-quicksort/ Tue, 21 Feb 2017 17:27:03 +0000 http://vbabeginner.net/?p=58 VBAでのクイックソート

今、このページを見ているということは、よほど高速に処理したいということではないかと思います。

高速な並べ替えと言えば「クイックソート」です。一般的にはいろんなソート処理の中でも最速と言われています。ただ、「最速」といっても一般的にであって、ほとんど整列済みのデータの場合は挿入ソートの方が速いなど、一概に最速とは言えない点もあります。また、高速ではあるものの仕組みが複雑なため、当然、実装も複雑になる欠点があります。

そして、こんなページを書いておきながらなんですが、「どうにかこうにか高速に並べ替え処理したい」という場合であれば、VBAでクイックソートを実装するのではなくVBAから離れる(ソート部分を別の仕組みにしたり、C言語等のライブラリを使うなどする)のも一つの方法です。とはいえ、そういう話になるともうExcel効率化というよりもシステム開発の感じにはなりますね。

他のソートについては以下をご参照ください。
VBAの配列をバブルソートで並べ替え
VBAの配列を挿入ソートで並べ替え
VBAの配列を.NETのArrayListのSortで並べ替え
VBAの配列を逆順に並べ替え

クイックソートの仕組み

以下のクイックソート関数の仕組みを簡単に説明します。

  1. 配列の中の中央の要素値を取得する。(中央要素値とします)
  2. 配列を先頭から見ていき、中央要素値以上の値を探す。
  3. 配列を最後から見ていき、中央要素値以下の値を探す。
  4. それぞれ見つかった位置が先頭から探した方が、最後から探した方より右にある場合は中央要素値以上、以下の値の探索を終了する。
  5. 上記の条件に合致しない場合は、見つかった大小の値を入れ替える。
  6. 先頭を1つ右に、最後を1つ左にずらして、再度2.に戻る。
  7. 中央値の左側を再帰でクイックソートする。
  8. 中央値の右側を再帰でクイックソートする。

 

コード

以下の関数は引数で渡された一次元配列の並び順をクイックソートで昇順に並べなおします。

引数は配列、ソート開始位置、ソート終了位置の3つがあります。

2番目と3番目の引数は配列の一部だけをソートしたい場合に設定します。通常は省略して構いません。

Sub quicksort(a_Ar(), Optional iFirst As Integer = 0, Optional iLast As Integer = -1)
    Dim iLeft                   As Integer      '// 左ループカウンタ
    Dim iRight                  As Integer      '// 右ループカウンタ
    Dim sMedian                                 '// 中央値
    Dim tmp                                     '// 配列移動用バッファ
    
    '// ソート終了位置省略時は配列要素数を設定
    If (iLast = -1) Then
        iLast = UBound(a_Ar)
    End If
    
    '// 中央値を取得
    sMedian = a_Ar(Int((iFirst + iLast) / 2))
    
    iLeft = iFirst
    iRight = iLast
    
    Do
        '// 中央値の左側をループ
        Do
            '// 配列の左側から中央値より大きい値を探す
            If (a_Ar(iLeft) >= sMedian) Then
                Exit Do
            End If
            
            '// 左側を1つ右にずらす
            iLeft = iLeft + 1
        Loop
        
        '// 中央値の右側をループ
        Do
            '// 配列の右側から中央値より大きい値を探す
            If (sMedian >= a_Ar(iRight)) Then
                Exit Do
            End If
            
            '// 右側を1つ左にずらす
            iRight = iRight - 1
        Loop
        
        '// 左側の方が大きければここで処理終了
        If (iLeft >= iRight) Then
            Exit Do
        End If
        
        '// 右側の方が大きい場合は、左右を入れ替える
        tmp = a_Ar(iLeft)
        a_Ar(iLeft) = a_Ar(iRight)
        a_Ar(iRight) = tmp
        
        '// 左側を1つ右にずらす
        iLeft = iLeft + 1
        '// 右側を1つ左にずらす
        iRight = iRight - 1
    Loop
    
    '// 中央値の左側を再帰でクイックソート
    If (iFirst < iLeft - 1) Then
        Call quicksort(a_Ar, iFirst, iLeft - 1)
    End If
    
    '// 中央値の右側を再帰でクイックソート
    If (iRight + 1 < iLast) Then
        Call quicksort(a_Ar, iRight + 1, iLast)
    End If
    
End Sub

昇順ではなく降順にしたい場合は、大小比較や入れ替え部分を反対にすれば可能になります。

または別ページ「VBAの配列を逆順に並べ替え」のようなコードで昇順と降順を入れ替えるのもありです。

利用方法

利用方法はこのような感じになります。

Sub sorttest()
    Dim ar()
    
    ReDim ar(10)
    ar(0) = "9"
    ar(1) = "8"
    ar(2) = "7"
    ar(3) = "6"
    ar(4) = "5"
    ar(5) = "4"
    ar(6) = "3"
    ar(7) = "2"
    ar(8) = "1"
    ar(9) = "0"
    ar(10) = "a"
    
    'Call quicksort(ar, 0, UBound(ar))
    Call quicksort(ar) '// こちらのように引数は省略しても構いません
End Sub

ソート後は、以下の順になります。

ar(0) = "0"
    ar(1) = "1"
    ar(2) = "2"
    ar(3) = "3"
    ar(4) = "4"
    ar(5) = "5"
    ar(6) = "6"
    ar(7) = "7"
    ar(8) = "8"
    ar(9) = "9"
    ar(10) = "a"

]]>
VBAの配列をバブルソートで並べ替え https://vbabeginner.net/sort-array-with-bubble-sort/ Mon, 20 Feb 2017 16:59:50 +0000 http://vbabeginner.net/?p=56 VBAでのバブルソート

VBAでは以下のように括弧を付けることで配列を使うことができます。

Dim sArray()

このときに、格納済みの配列の並び順を昇順や降順に変えたいことがあります。しかし残念ながらVBAの標準機能では配列の並び順を変えることはできません。解決するにはいくつか方法ありますが、ここではバブルソートの関数を作成する方法を紹介します。

なお、他のソートについては以下をご参照ください。
VBAの配列をクイックソートで並べ替え
VBAの配列を挿入ソートで並べ替え
VBAの配列を.NETのArrayListのSortで並べ替え
VBAの配列を逆順に並べ替え

バブルソートとは、隣同士の値の大小を比較し、必要に応じて入れ替えていき、それを先頭から最後まで繰り返す方法です。先頭から最後までの並べ替えを配列の要素数繰り返すことで並べ替えが完了します。

利点として仕組みが単純なため実装しやすい点がありますが、欠点としてソート処理としては高速ではない点があります。クイックソートなどのもっと高速なアルゴリズムはありますが、バブルソートと比べるとどうしても複雑な仕組みになるため、そこまで高速性を求めない場合であれば十分に利用できます。

ちなみに、遅いとは言ってもコンピュータの中での話ですので、「100万件のデータを繰り返し並べ替える」とかの性能要求がなく、「100件程度の並べ替え」ぐらいであれば1秒も変わらないためバブルソートで十分です。

バブルソートの例

以下は昇順(小さい順)のバブルソートの考え方の例です。黄色部分が値の入れ替えを行っている箇所で、緑部分は入れ替えていない箇所です。

  • 0回目:4 – 3 – 2 – 1(並べ替え前)
  • 1回目:3 – 2 – 1 – 4
    (4と3を比較し入れ替え(3 – 4 – 2 – 1)、4と2を比較し入れ替え(3 – 2 – 4 – 1)、4と1を比較し入れ替え(3 – 2 – 1 – 4))
  • 2回目:2 – 1 – 3 – 4
    (3と2を比較し入れ替え(2 – 3 – 1 – 4)、3と1を比較し入れ替え(2 – 1 – 3 – 4)、3と4を比較し入れ替えなし(2 – 1 – 3 – 4))
  • 3回目:1 – 2 – 3 – 4(この時点で並べ替え完了)
    (2と1を比較し入れ替え(1 – 2 – 3 – 4)、2と3を比較し入れ替えなし(1 – 2 – 3 – 4)、3と4を比較し入れ替えなし(1 – 2 – 3 – 4))
  • 4回目:配列要素数の4に達したため、ここでは並べ替えを行わずバブルソートを終了する。

このように、単純な並べ替えを行った場合、1回目の並べ替えで終端(右端)には必ず一番大きな値(上の例では4)がきます。2回目には2番目に大きな値(上の例では3)が終端の直前にきます。

これはバブルソートの特性のため、言い換えれば、1回目に確定した一番大きな値は2回目以降は並べ替えをする必要がなくなるため、その部分は処理を省略することで高速化を望めます。

上の例はわかりやすく書いていますが、下の実装では不要になった並べ替え部分は処理を行わないように高速化を考慮しています。

バブルソートのコード

以下の関数は、バブルソートの仕組みの通り、隣り合う要素の大小を比較して、降順(小さい順)に並べなおします。

要素の先頭から最後までの入れ替えを1回行うと一番最後の要素にそのループでの最大値が設定されます。よって、次のループでは一番最後の要素まで比較せずに済むため、その分を減らしています。(iArCnt2 = iArCnt2 – 1の部分です)

Sub bubbleSort(a_Ar)
    Dim i, ii               '// ループカウンタ
    Dim iArCnt              '// 配列の要素数
    Dim iArCnt2             '// 配列の要素数
    Dim v                   '// 一時保存用
    
    '// 配列の要素数を取得
    iArCnt = UBound(a_Ar)
    iArCnt2 = iArCnt
    
    i = 0
    
    '// 配列要素数ループ(全要素)
    Do While (i < iArCnt)
        ii = 0
        '// 配列要素数ループ(大小比較)
        Do While (ii < iArCnt2)
            '// 現ループの内容が次ループ時の内容より大きい場合
            If (a_Ar(ii) > a_Ar(ii + 1)) Then
                '// 小さい順に並べなおす
                v = a_Ar(ii)                '// 現ループの大きい値を一時保存
                a_Ar(ii) = a_Ar(ii + 1)     '// 次ループの小さい値を現ループ値にセット
                a_Ar(ii + 1) = v            '// 次ループに現ループの値をセット
            End If
            
            ii = ii + 1
        Loop
        
        '// 最大値が確定したため未確定部分のみループするようにカウンタを減らす
        iArCnt2 = iArCnt2 - 1
        
        i = i + 1
    Loop
End Sub

以下は降順用のバブルソート関数です。関数名のDescは「descending」の略です。大小比較の部分の > を < に変更し、大小の入れ替え処理を反対にしているだけです。

Sub bubbleSortDesc(a_Ar)
    Dim i, ii               '// ループカウンタ
    Dim iArCnt              '// 配列の要素数
    Dim iArCnt2             '// 配列の要素数
    Dim v                   '// 一時保存用
    
    '// 配列の要素数を取得
    iArCnt = UBound(a_Ar)
    iArCnt2 = iArCnt
    
    i = 0
    
    '// 配列要素数ループ(全要素)
    Do While (i < iArCnt)
        ii = 0
        '// 配列要素数ループ(大小比較)
        Do While (ii < iArCnt2)
            '// 現ループの内容が次ループ時の内容より小さい場合
            If (a_Ar(ii) < a_Ar(ii + 1)) Then
                '// 大きい順に並べなおす
                v = a_Ar(ii)                '// 現ループの小さい値を一時保存
                a_Ar(ii) = a_Ar(ii + 1)     '// 次ループの大きい値を現ループ値にセット
                a_Ar(ii + 1) = v            '// 次ループに現ループの値をセット
            End If
            
            ii = ii + 1
        Loop
        
        '// 最小値が確定したため未確定部分のみループするようにカウンタを減らす
        iArCnt2 = iArCnt2 - 1
        
        i = i + 1
    Loop
End Sub

 

使い方

利用方法はこのような感じになります。

要素数を11の配列のarを用意し、各要素に文字列をセットします。そしてバブルソート関数を実行します。コメントアウトしているのは降順用の関数です。

Sub sorttest()
    Dim ar()
    
    ReDim ar(10)
    ar(0) = "9"
    ar(1) = "8"
    ar(2) = "7"
    ar(3) = "6"
    ar(4) = "5"
    ar(5) = "4"
    ar(6) = "3"
    ar(7) = "2"
    ar(8) = "1"
    ar(9) = "0"
    ar(10) = "a"
    
    Call bubbleSort(ar)
'    Call bubbleSortDesc(ar)
End Sub

ソート後は、以下の順になります。

ar(0) = "0"
    ar(1) = "1"
    ar(2) = "2"
    ar(3) = "3"
    ar(4) = "4"
    ar(5) = "5"
    ar(6) = "6"
    ar(7) = "7"
    ar(8) = "8"
    ar(9) = "9"
    ar(10) = "a"

]]>