history | TOP |
2004/01/03:作成
overview | TOP |
タイトルのとおりですが、TStringListのカスタムソートについての使い方のメモを残します。
TStringList.Sort | TOP |
TStringListにはsortメソッドがあります。VCLリファレンスのTStringList.Sortの説明によると次のように動作します。
■ VCLリファレンス
TStringList.Sort
Sortメソッドは、リスト内の文字列を昇順で並べ替えます。procedure Sort; virtual;
説明
Sortメソッドを呼び出すと、SortedプロパティをFalseに設定したリストで文字列を並べ替えることができます。SortedプロパティをTrueに設定した文字列リストでは、自動的に並べ替えが実行されます。注意
Sortメソッドは、CaseSensitiveプロパティがTrueの場合にはAnsiCompareStrを使って、CaseSensitiveプロパティがFalseの場合にはAnsiCompareTextを使って文字列を並べ替えます。カスタマイズした比較演算子を使用する場合には、かわりにCustomSortメソッドを使用します。
最初は、う?となったのですが、つまるところ"普通の"文字列の比較(AnsiCompare***による)でソートするヨ。自動でもよいし、メソッド呼び出し時点でもよいヨ。ということです(もっと分かりにくいか?)
で、どんなソートかというと、クイックソートです。これはTListのソート方法がクイックソートとVCLリファレンスに書いてあって、TStringListがTListから派生していることからの類推ではなく、実際にソースを見たところそう書いてあります。
確認したのは、VCLのソースが付属していないDelphi6 Personalですが、きちんと最新のパッチまで適用していれば、TList/TStringList...が実装されているClasses.pasについてはソースをゲットできているはずです。Delphi 6 RTL アップデート #3に含まれています。
で、Classes.pasをエディタで開いて、"TStringList.Sort"で検索をかけるとすぐに、クイックソートを行っていることが分かります。さらに、このSortメソッド自体もCustomSortメソッドの一例として実装されていることが分かります。分かりにくいですが、「カスタマイズした比較演算子を使用する場合には、かわりにCustomSortメソッドを使用します」という説明がVCLリファレンスにありますが、Sortメソッド自体も内部ではCustomSortメソッドを使用しているということです。
TStringList.CustomSort | TOP |
で、CustomSortはどう説明されているかというと、
■ VCLリファレンスということで、何がなんだか分かりません(でした)。
TStringList.CustomSort
CustomSortメソッドは、リストの文字列をカスタマイズした順にソートします。type TStringListSortCompare = function(List: TStringList; Index1, Index2: Integer): Integer;
procedure CustomSort(Compare: TStringListSortCompare); virtual;
説明
CustomSortメソッドを使うと、Compareパラメータでそのソート順が定義されている場合に、リストの文字列をソートできます。
文字列リストの2つの文字列を比較するCompare関数に値を指定してください。Index1パラメータとIndex2パラメータは比較する文字列を表します。Listパラメータは文字列リストへのアクセスを提供します。Stringsプロパティ配列への添字としてIndex1パラメータとIndex2パラメータを使います。Compare関数が返す値は以下のとおりです。nilをパラメータの値として渡さないでください。
- Index1で表される文字列がIndex2によって表される文字列よりも前に来た場合、0未満の値
- 2つの文字列が同じ場合、0
- Index1によって表される文字列がIndex2によって表される文字列よりも後に来た場合、0を越える値
注意
CustomSortメソッドを明示的に呼び出さなくてはなりません。Sortedプロパティを設定しても、Sortメソッドで実装したANSI順を使用して文字列をソートするだけです。
TStringList.Sort in Classes.pas | TOP |
あまりに分かりにくいので、前述のBorlandが配布しているアップデータ内のClasses.pasからTStringList.Sortの実装部分を違法的に抜粋してみます。ただし、コメントをいくつか追加して、順序も並び替えています。
// CustomSortの呼び出し方 procedure TStringList.Sort; begin CustomSort(StringListCompareStrings); end; // 使用者側で準備する比較関数 function StringListCompareStrings(List: TStringList; Index1, Index2: Integer): Integer; begin Result := List.CompareStrings(List.FList^[Index1].FString, List.FList^[Index2].FString); end; // 使用者側で準備する比較関数の下請けだけども本体 function TStringList.CompareStrings(const S1, S2: string): Integer; begin if CaseSensitive then Result := AnsiCompareStr(S1, S2) else Result := AnsiCompareText(S1, S2); end;
で、これだと下請け関数が挟まっているのでちょっと統合して、比較関数と呼び出し側の2本にまとめたのが次です。
(* CustomSortの呼び出し方 *) procedure TStringList.Sort; begin CustomSort(StringListCompareStrings); end; (* 使用者側で準備する比較関数 *) function StringListCompareStrings(List: TStringList; Index1, Index2: Integer): Integer; var S1, S2: string; begin // 比較対象を取り出す - 1つ目 S1 := List.FList^[Index1].FString; // 比較対象を取り出す - 2つ目 S2 := List.FList^[Index2].FString; // 比較する if CaseSensitive then Result := AnsiCompareStr(S1, S2) else Result := AnsiCompareText(S1, S2); end;
ここまで来てなんですが、以上がTStringList内部から呼び出した場合の使用例になります。たとえば、おそらくprivateメンバと思われるFListとかFStringとかがバンバンと出てきてます。ですので、当然動作は確認していません。
TStringList.CustomSort usage | TOP |
というわけで、通常の使用方法は次のようになります(と思います)。
(* 通常のTStringList.Sortと同じ結果 *) function StringListCompareStrings(List: TStringList; Index1, Index2: Integer): Integer; var S1, S2: string; begin // 比較対象取り出し - 1つ目 S1 := List[Index1]; // 比較対象取り出し - 2つ目 S2 := List[Index2]; // 比較 if List.CaseSensitive then Result := AnsiCompareStr(S1, S2) else Result := AnsiCompareText(S1, S2); end; (* カスタムソート *) procedure TForm1.Button1Click(Sender: TObject); begin SL.CustomSort(StringListCompareStrings); end;
ここで「SL」はTForm1のメンバ変数として宣言しFormCreateで確保・FormDestroyで破棄しているTStringListです。また、カスタムソートの「カスタム」に当たる部分は上記コードの赤太で示した部分です。
さて、このコードには前述のTStringList.CustomSortのリファレンスに登場する「0未満の値」「0」「0を超える値」に関する処理がありません。その理由は「AnsiCompareStr / AnsiCompareText がそういう値を返すような関数だから」です。少しだけAnsiCompareStrのヘルプの記述を抜いてみると
■ VCLリファレンス
AnsiCompareStr 関数
function AnsiCompareStr(const S1, S2: string): Integer;
説明
AnsiCompareStr関数は、大文字と小文字を区別してS1とS2を比較します。比較演算は現在のロケールによって制御されます。戻り値は以下のとおりです。
条件 戻り値 S1 > S2 > 0 S1 = S2 0 S1 < S2 < 0 (略)
これってモロに、CustomSortのカスタム比較関数が求める戻り値そのものです。結局これさえ守っていれば嫌になるほど自由なソートが可能になるということです。
文字列比較に限っても
等など。さらに、恐るべきはTStringListですが、Objectsプロパティに任意のTObjectを保持することができます。で、文字列で比較する必要すらなくて、Objectsプロパティを比較関数内で覗いて、そこに保持されている任意のクラスの保持する情報で並び替えることすら可能です。
ここまでくると、特に問題がなければTListやTObjectListなどでもかまわないような気がしますが...。というわけで、次回は、いくつかのカスタムソートの実例を作成してみたいと思います。(ホントか?)
EOF | TOP |