Windows XPのファイル名のソート(ファイル名中の数字を数値と見なすソート)(自然順ソート・natural sort・natural ordering)を実装する方法を紹介します。
Windows XPからファイル名のソート方法が変更されました。
Windows 2000まではアルファベット順のソートでしたが、Windows XPからはファイル名に含まれる数字を数値をに見なしてソートしています。
※参考:名前に数字が含まれるファイルやフォルダの並べ替え順序が Windows XP と Windows 2000 で異なる
Windows XPのソート
Ie4_01
Ie4_128
Ie5
Ie6
Ie401sp2
Ie501sp2
Windows 2000のソート
Ie4_01
Ie4_128
Ie401sp2
Ie5
Ie501sp2
Ie6
StrCmpLogicalW関数を使った方法
自作のソフトウェアでWindows XPのソートを実装する場合は、shlwapi.dllのStrCmpLogicalW関数を使用します。
function StrCmpLogicalW(psz1, psz2: PWideChar): Integer; stdcall; external 'shlwapi.dll';
次のサンプルプログラムではTStringListの文字列を2種類の方法でソートします。
/// <summary>
/// TStringList.CustomSortでWindows XPのソートをするための比較関数
/// </summary>
function CompareLogical(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := StrCmpLogicalW(PWideChar(List[Index1]), PWideChar(List[Index2]));
end;
var
SL: TStringList;
S: string;
begin
// テストデータ
SL := TStringList.Create;
SL.Add('Ie5');
SL.Add('Ie6');
SL.Add('Ie401sp2');
SL.Add('Ie4_128');
SL.Add('Ie501sp2');
SL.Add('Ie4_01');
// アルファベット順のソート。Windows2000と同じ結果
SL.Sort;
for S in SL do
Writeln(S);
// WindowsXP以降の規則でソート
SL.CustomSort(CompareLogical);
for S in SL do
Writeln(S);
StrCmpLogicalW関数を使わない方法
StrCmpLogicalW関数はWindowsでしか使えません。
他のOSで使えるようにDelphiでコードを書きました。
次のページを参考にしました。
DaveKoelle.com | The Alphanum Algorithm
/// <summary>
/// 自然順ソードの比較
/// </summary>
function NaturalSortCompare(const S1, S2: string): Integer;
const
RE_CHUNK: string = '([\D]+|[\d]+)';
RE_LETTERS: string = '\D+';
RE_NUMBERS: string = '\d+';
var
L, R: string;
LChunk, RChunk: string;
ReChunk, ReLetter, ReNumber: TRegEx;
/// <summary>
/// 文字列の先頭から、連続する数字または文字列を取得する
/// </summary>
function GetChunk(var S: string): string;
var
Match: TMatch;
begin
Match := ReChunk.Match(S);
if Match.Success then
begin
Result := Match.Groups[0].Value;
S := S.Substring(Result.Length);
end
else
begin
Result := '';
end;
end;
/// <summary>
/// 文字列を数値として比較して、S1が小さいときは-1、S2が小さいときは1、等しいときは0を返す
/// </summary>
function CompareNum(const S1, S2: string): Integer;
var
Num1, Num2: Integer;
begin
Num1 := StrToInt(S1);
Num2 := StrToInt(S2);
if Num1 < Num2 then
Result := -1
else if Num1 > Num2 then
Result := 1
else
Result := 0;
end;
begin
Result := 0;
L := S1;
R := S2;
ReChunk := TRegEx.Create(RE_CHUNK);
ReLetter := TRegEx.Create(RE_LETTERS);
ReNumber := TRegEx.Create(RE_NUMBERS);
while Result = 0 do
begin
if L.IsEmpty and R.IsEmpty then
Exit(CompareStr(S1, S2));
LChunk := GetChunk(L);
RChunk := GetChunk(R);
if ReLetter.Match(LChunk).Success and ReLetter.Match(RChunk).Success then
begin
Result := CompareStr(LChunk, RChunk);
end
else
begin
if ReNumber.Match(LChunk).Success and ReNumber.Match(RChunk).Success then
begin
Result := CompareNum(LChunk, RChunk);
end
else
begin
Result := CompareStr(LChunk, RChunk);
if Result = 0 then
Result := 1;
end;
end;
end;
end;
function NaturalSort(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := NaturalSortCompare(List[Index1], List[Index2]);
end;
var
SL: TStringList;
S: string;
begin
// テストデータ
SL := TStringList.Create;
SL.Add('Ie5');
SL.Add('Ie6');
SL.Add('Ie401sp2');
SL.Add('Ie4_128');
SL.Add('Ie501sp2');
SL.Add('Ie4_01');
// WindowsXP以降の規則でソート
SL.CustomSort(NaturalSort);
for S in SL do
Writeln(S);
SL.Free;
end.
更新履歴
- 2013/11/04 StrCmpLogicalW関数を使わない方法を追加しました。