[Delphi]Windows XPの規則(自然順ソート・natural sort・natural ordering)ででファイル名をソートする

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関数を使わない方法を追加しました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください