やっとまとまった時間が取れたのでよくよく見返してみると・・・間違ってますね。
qSortの複数列入れ替えの対応も間違ってますし、
実験してみたところend-uさんの言う通り、複数列をkeyにするとクイックソートの不安定な特性から正しく並べ替えできないことになりました。
不安定なソートとは
13 A
15 A
17 B
15 B
という並びに大して1列目をキーにしてソートすると
13 A
15 B
15 A
17 B
という並びになってしまうことがある。
つまり、語弊があるかもしれないけどキー以外は考慮できないソートになります。
安定版のクイックソートアルゴリズムもあるようなのですが、ちょっと面倒な感じです。
あたかも問題なくできるような書き方をして申し訳ありませんでした。m(_ _)m
並び順の変わらない安定なソートとして有名なものにマージソートというものがあります。マージソートを作ってみましたので、グローバルにリストを持たせるサンプルを書いておきます。
複数列を同時に並び変えたかったのでちょっとイレギュラーですがJAG配列ってのを使ってみました。
リストを作る上ではend-uさんの使っているDictionaryコレクションの方が楽かもしれませんが、こんな方法もあるよってことでよろしくお願いします。
Dictionaryコレクションについて-重複しないリストを作る
http://officetanaka.net/excel/vba/tips/tips80.htm
'半角空白2個を全角空白1個に置き換えてインデントを表現しています。
Option Explicit
Const keyCol As Long = 3 'キーのカラム位置
Public Arrs() As Variant
Sub test3() '起動時に読み込むにはSub Auto_Open()とするのがよい
Dim Temp As Variant
Dim i As Long
Dim a As Variant
Erase Arrs
For i = 2 To Range("A65536").End(xlUp).Row
'1行分のデータを取得する
Temp = Range("A" & i & ":B" & i)
ReDim Preserve Arrs(i - 1)
'配列に配列を入れる (JAG配列)
Arrs(i - 1) = Temp
Next i
Call mySort(Arrs, 2, 1)
' 'テスト
' '配列領域の確保
' a = Range("I" & 2 & ":J" & Range("A65536").End(xlUp).Row)
'
' For i = 1 To UBound(Arrs)
' 'JAG配列からデータを取り出す
' a(i, 1) = Arrs(i)(1, 1)
' a(i, 2) = Arrs(i)(1, 2)
' Next i
' 'シートに貼り付ける
' Range("I" & 2 & ":J" & Range("A65536").End(xlUp).Row) = a
End Sub
'第3優先まで対応のソート関数 key2とkey3はデフォルトの引数で0を指定しておく。
'最低限、第1優先は必要
Private Sub mySort(ByRef Arr, key1 As Integer, Optional key2 As Integer = 0, Optional key3 As Integer = 0)
Dim iMax As Long
Dim iMin As Long
iMin = LBound(Arr)
iMax = UBound(Arr)
'優先度の低い項目からソートしていく
If key3 <> 0 Then
Call mergeSort(Arr, iMin, iMax, key3)
End If
If key2 <> 0 Then
Call mergeSort(Arr, iMin, iMax, key2)
End If
Call mergeSort(Arr, iMin, iMax, key1)
End Sub
' マージソート
' 既にソート済みの2つの配列を併合して新しい配列を
'生成することで、データのソートを行います。
Private Sub mergeSort(ByRef Arr As Variant, ByVal iMin As Long, iMax As Long, key As Integer)
Dim iCent1 As Long
Dim iCent2 As Long
Dim Arr1() As Variant
Dim Arr2() As Variant
Dim i As Long
If iMax - iMin <= 1 Then
Exit Sub
End If
'Arrを半分に分割したArr1, Arr2を作成する
iCent1 = (iMax - iMin) / 2
iCent2 = (iMax - iMin) - iCent1
ReDim Arr1(iCent1)
ReDim Arr2(iCent2)
For i = 1 To iCent1
Arr1(i) = Arr(i)
Next i
For i = 1 To iCent2
Arr2(i) = Arr(iCent1 + i)
Next i
'再帰的に呼んでどんどん細かくしていく
Call mergeSort(Arr1, LBound(Arr1), UBound(Arr1), key)
Call mergeSort(Arr2, LBound(Arr2), UBound(Arr2), key)
'再帰の帰り道でソートしながら結合していく
Call merge(Arr1, Arr2, Arr, key)
Erase Arr1
Erase Arr2
End Sub
'マージ
'2つの配列Arr1とArr2を併合してArryを作ります
' Arr1 Arr2 Arr
' 15 17 から 15
' 18 17 を作るイメージ
' 18
'JAG配列を使っているので、1行数列分の配列を丸ごと格納している
Sub merge(ByRef Arr1 As Variant, ByRef Arr2 As Variant, ByRef Arr As Variant, key As Integer)
Dim i As Long
Dim j As Long
i = 1
j = 1
While i <= UBound(Arr1) Or j <= UBound(Arr2)
'Arr2の添え字がArr2のサイズを超えているときはArrにArr1を入れる
' Arr1 Arr2 Arr
' 15
' 15 17 17
' 18 --------->18
'
If j > UBound(Arr2) Then
Arr(i + j - 1) = Arr1(i)
i = i + 1
GoTo NEXT_Arr
End If
'上記の逆パターン
If i > UBound(Arr1) Then
Arr(i + j - 1) = Arr2(j)
j = j + 1
GoTo NEXT_Arr
End If
'比較して小さい方をArrに入れる (JAG配列にアクセスしてkeyで比較する)
' Arr1 Arr2
' A ア
' A イ <-> B ア key2で比較する場合、Arr2を小さいと見る
'
If Arr1(i)(1, key) <= Arr2(j)(1, key) Then
Arr(i + j - 1) = Arr1(i)
i = i + 1
Else
Arr(i + j - 1) = Arr2(j)
j = j + 1
End If
NEXT_Arr:
Wend
End Sub
お礼
kenpon24さん、本当にありがとうございました。 そうなんです、クイックソートは安定ソートじゃないのでキーが2個以上の場合は使わない方がいいんですね。 今回はオンメモリ+Dictionaryコレクション+キーはクイックソートでとりあえずの完成版となりました。 上のend_uさんへのお礼をご笑覧ください。 まだまだ汚いと思いますが、短くはなっています。 金曜日の午後と今朝と、通産8時間ぐらいの作業ですが、今回だけでなくすごくいろいろ教わってよかったです。 本当にみなさんありがとうございました。