Results 1 to 1 of 1

Thread: Sorting AlphaNumeric Strings Logically as Windows File Explorer Does

  1. #1

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,274

    Sorting AlphaNumeric Strings Logically as Windows File Explorer Does

    VB version here.

    People often ask how to sort alphanumeric Strings logically. It is usually, although not exclusively, in the context of sorting file names. That's because Windows File Explorer does just that. As an example, Windows File Explorer would sort the following file names in the following order:
    Code:
    File1Test.txt
    File2Test.txt
    File3Test.txt
    File4Test.txt
    File5Test.txt
    File10Test.txt
    File20Test.txt
    File30Test.txt
    File40Test.txt
    File50Test.txt
    whereas a standard String sort would yield the following order:
    Code:
    File1Test.txt
    File10Test.txt
    File2Test.txt
    File20Test.txt
    File3Test.txt
    File30Test.txt
    File4Test.txt
    File40Test.txt
    File5Test.txt
    File50Test.txt
    Windows File Explorer achieves this by using the StrCmpLogicalW API function and you can do the same. The first step is to use Platform Invoke to declare this function in your .NET code so that you can invoke the unmanaged function:
    csharp Code:
    1. using System.Runtime.InteropServices;
    2.  
    3. namespace Demo
    4. {
    5.     public static class NativeMethods
    6.     {
    7.         [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    8.         public static extern int StrCmpLogicalW(string x, string y);
    9.     }
    10. }
    Sorting is done in various specific ways but they all come down to comparing pairs of values and swapping them if required. You can provide the code to perform those comparisons yourself and use the API function above:
    csharp Code:
    1. using System.Collections;
    2. using System.Collections.Generic;
    3.  
    4. namespace Demo
    5. {
    6.     public class LogicalStringComparer : IComparer, IComparer<string>
    7.     {
    8.         int IComparer.Compare(object x, object y)
    9.         {
    10.             return Compare((string) x, (string) y);
    11.         }
    12.  
    13.         public int Compare(string x, string y)
    14.         {
    15.             return NativeMethods.StrCmpLogicalW(x, y);
    16.         }
    17.     }
    18. }
    That class follows the convention of implementing both when there is a generic and non-generic version of an interface. As is done above, you should have the non-generic method explicitly implement the interface method and have it call the generic method. That way, those who use the class directly will only have access to the generic method but code that uses a reference of the non-generic interface type will still have access to the non-generic method. More on that later. Note that you may prefer to declare the API function inside that class if that's the only place you plan to use it:
    csharp Code:
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Runtime.InteropServices;
    4.  
    5. namespace Demo
    6. {
    7.     public class LogicalStringComparer : IComparer, IComparer<string>
    8.     {
    9.         [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    10.         private static extern int StrCmpLogicalW(string x, string y);
    11.  
    12.         int IComparer.Compare(object x, object y)
    13.         {
    14.             return Compare((string) x, (string) y);
    15.         }
    16.  
    17.         public int Compare(string x, string y)
    18.         {
    19.             return StrCmpLogicalW(x, y);
    20.         }
    21.     }
    22. }
    You can then use that class to perform the comparisons wherever an IComparer or IComparer<string> is expected. Here is some example code that uses it to sort a string array, an ArrayList, a List<string> and a LINQ query producing an IEnumerable<string>:
    csharp Code:
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. namespace Demo
    7. {
    8.     class Program
    9.     {
    10.         static void Main()
    11.         {
    12.             var fileNames = new[]
    13.                             {
    14.                                 "File1Test.txt",
    15.                                 "File2Test.txt",
    16.                                 "File3Test.txt",
    17.                                 "File4Test.txt",
    18.                                 "File5Test.txt",
    19.                                 "File10Test.txt",
    20.                                 "File20Test.txt",
    21.                                 "File30Test.txt",
    22.                                 "File40Test.txt",
    23.                                 "File50Test.txt"
    24.                             };
    25.             var rng = new Random();
    26.  
    27.             SortArray(fileNames.OrderBy(s => rng.NextDouble()).ToArray());
    28.             SortArrayList(new ArrayList(fileNames.OrderBy(s => rng.NextDouble()).ToArray()));
    29.             SortList(new List<string>(fileNames.OrderBy(s => rng.NextDouble())));
    30.             SortQuery(fileNames.OrderBy(s => rng.NextDouble()).ToArray());
    31.  
    32.             Console.ReadLine();
    33.         }
    34.  
    35.         private static void SortArray(string[] arr)
    36.         {
    37.             Console.WriteLine("Array before:");
    38.  
    39.             foreach (var s in arr)
    40.             {
    41.                 Console.WriteLine(s);
    42.             }
    43.  
    44.             Console.WriteLine();
    45.  
    46.             Array.Sort(arr, new LogicalStringComparer());
    47.  
    48.             Console.WriteLine("Array after:");
    49.  
    50.             foreach (var s in arr)
    51.             {
    52.                 Console.WriteLine(s);
    53.             }
    54.  
    55.             Console.WriteLine();
    56.         }
    57.  
    58.         private static void SortArrayList(ArrayList al)
    59.         {
    60.             Console.WriteLine("ArrayList before:");
    61.  
    62.             foreach (var s in al)
    63.             {
    64.                 Console.WriteLine(s);
    65.             }
    66.  
    67.             Console.WriteLine();
    68.  
    69.             al.Sort(new LogicalStringComparer());
    70.  
    71.             Console.WriteLine("ArrayList after:");
    72.  
    73.             foreach (var s in al)
    74.             {
    75.                 Console.WriteLine(s);
    76.             }
    77.  
    78.             Console.WriteLine();
    79.         }
    80.  
    81.         private static void SortList(List<string> lst)
    82.         {
    83.             Console.WriteLine("List before:");
    84.  
    85.             foreach (var s in lst)
    86.             {
    87.                 Console.WriteLine(s);
    88.             }
    89.  
    90.             Console.WriteLine();
    91.  
    92.             lst.Sort(new LogicalStringComparer());
    93.  
    94.             Console.WriteLine("List after:");
    95.  
    96.             foreach (var s in lst)
    97.             {
    98.                 Console.WriteLine(s);
    99.             }
    100.  
    101.             Console.WriteLine();
    102.         }
    103.  
    104.         private static void SortQuery(string[] arr)
    105.         {
    106.             Console.WriteLine("Query before:");
    107.  
    108.             foreach (var s in arr)
    109.             {
    110.                 Console.WriteLine(s);
    111.             }
    112.  
    113.             Console.WriteLine();
    114.  
    115.             var qry = arr.OrderBy(s => s, new LogicalStringComparer());
    116.  
    117.             Console.WriteLine("Query after:");
    118.  
    119.             foreach (var s in qry)
    120.             {
    121.                 Console.WriteLine(s);
    122.             }
    123.  
    124.             Console.WriteLine();
    125.         }
    126.     }
    127. }
    I've included the ArrayList because it demonstrates that, even though the method that implements IComparer.Compare is implemented explicitly, it is still accessible where a reference of type IComparer is used. The parameter of the ArrayList.Sort method is type IComparer so, inside that method, it is actually that explicitly implemented overload of LogicalStringComparer.Compare that is being invoked.

    Note that the LINQ OrderBy method that accepts an IComparer<T> as an argument also requires you to specify a selector for the sort key of type T. In the example above, we're sorting strings so the key is the item itself. In other cases, the sort key might be a property of the item, e.g. the Name property of a FileInfo object:
    csharp Code:
    1. var folder = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
    2. var files = folder.EnumerateFiles("*.*", SearchOption.AllDirectories)
    3.                   .OrderBy(fi => fi.Name,
    4.                            new LogicalStringComparer())
    5.                   .ToArray();

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width