13

For natural sorting in my application I currently P/Invoke a function called StrCmpLogicalW in shlwapi.dll. I was thinking about trying to run my application under Mono, but then of course I can't have this P/Invoke stuff (as far as I know anyways).

Is it possible to see the implementation of that method somewhere, or is there a good, clean and efficient C# snippet which does the same thing?

My code currently looks like this:

[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    public static extern int StrCmpLogicalW(string psz1, string psz2);
}

public class NaturalStringComparer : IComparer<string>
{
    private readonly int modifier = 1;

    public NaturalStringComparer() : this(false) {}
    public NaturalStringComparer(bool descending)
    {
        if (descending) modifier = -1;
    }

    public int Compare(string a, string b)
    {
        return SafeNativeMethods.StrCmpLogicalW(a ?? "", b ?? "") * modifier;
    }
}

So, what I'm looking for is an alternative to the above class which doesn't use an extern function.

Svish
  • 152,914
  • 173
  • 462
  • 620
  • 2
    I don't think there is a native .NET way. – Oded Jan 09 '12 at 19:18
  • There is no .net framework equivalent to StrCmpLogicalW. Either [write your own](http://www.codeproject.com/KB/dotnet/SortingStringsForHumans.aspx?msg=2360693) or pinvoke. – David Heffernan Jan 09 '12 at 19:21
  • I just found this [blog post on natural sorting](http://www.interact-sw.co.uk/iangblog/2007/12/13/natural-sorting) in C#. Is this of any use? In response to your comment - I didn't analyse it in great detail myself, it just looked promising. There must be other c# implementations of natural sorting out there, perhaps you just need to find one and profile it? – ChrisF Oct 21 '09 at 16:05
  • Just finished reading it actually :P It seemed to do what I think it should, but it also seems quite inefficient... I don't really know though... hehe. – Svish Oct 21 '09 at 16:07

4 Answers4

12

I just implemented natural string comparison in C#, perhaps someone might find it useful:

public class NaturalComparer : IComparer<string>
{
   public int Compare(string x, string y)
   {
      if (x == null && y == null) return 0;
      if (x == null) return -1;
      if (y == null) return 1;

      int lx = x.Length, ly = y.Length;

      for (int mx = 0, my = 0; mx < lx && my < ly; mx++, my++)
      {
         if (char.IsDigit(x[mx]) && char.IsDigit(y[my]))
         {
            long vx = 0, vy = 0;

            for (; mx < lx && char.IsDigit(x[mx]); mx++)
               vx = vx * 10 + x[mx] - '0';

            for (; my < ly && char.IsDigit(y[my]); my++)
               vy = vy * 10 + y[my] - '0';

            if (vx != vy)
               return vx > vy ? 1 : -1;
         }

         if (mx < lx && my < ly && x[mx] != y[my])
            return x[mx] > y[my] ? 1 : -1;
      }

      return lx - ly;
   }
}
Andrew Skalkin
  • 287
  • 3
  • 10
  • its not complete, if you have ex1, ex-3, ex2 then the ex-3 will become last. the pinvoke windows function natural sorting algorithm does it explorer like. – Herman Van Der Blom Apr 04 '17 at 10:14
  • Last line should be `return lx - mx - (ly - my)`. Also `char.IsDigit` may return `true` for other Unicode digits outside '0'..'9' – Wizou Mar 11 '19 at 08:48
  • Has problems with overflow also – David Heffernan May 07 '19 at 09:42
  • @HermanVanDerBlom And the reason why `StrCmpLogicalW` works as in the Windows shell file explorer is because it's a function from `shlwapi`, the Windows Shell API. Probably because it's from the Shell that it doesn't exist in other APIs, although the Windows Shell was ported to ARM, it is not included in their newer Windows Store App thing. – Trinidad Apr 01 '21 at 13:11
5

http://www.interact-sw.co.uk/iangblog/2007/12/13/natural-sorting seems to be what you're looking for.

(and no, there is no managed equivalent to StrCmpLogicalW built into .NET)

Joachim Isaksson
  • 176,943
  • 25
  • 281
  • 294
0

I used regular expression to remove special characters. then casting to int. then i compared integers.

input :

List input = new List{ "6.04","6.01","6.03","6#04" };
Expected Output:
6.01
6.03
6.04
6#04

 var output = input.OrderBy(s => s, new NaturalStringComparer());
            foreach (var sort in output)
            {
                Console.WriteLine(sort);
            }


public struct NaturalStringComparer : IComparer
    {
        public int Compare(string x, string y)
        {
            if (x == null && y == null) return 0;
            if (x == null) return -1;
            if (y == null) return 1;

            int lx = x.Length, ly = y.Length;

            int a = int.Parse(System.Text.RegularExpressions.Regex.Replace(x, @"\D+", ""));
            int b = int.Parse(System.Text.RegularExpressions.Regex.Replace(y, @"\D+", ""));

            return a.CompareTo(b);
        }
    }
0

If you're running on Windows XP or newer, you can PInvoke to the shell function StrCmpLogicalW:

public static int StrCmpLogical(String s1, String s2)
{
    if (String.IsNullOrEmpty(s1) && !String.IsNullOrEmpty(s2))
        return 1; //empty s1 comes after s2
    else if (String.IsNullOrEmpty(s2) && !String.IsNullOrEmpty(s1))
        return -1; //non-empty string comes before empty

    return SafeNativeMethods.StrCmpLogicalW(s1, s2);
}

And then the internal unsafe class:

/// <summary>
/// This class suppresses stack walks for unmanaged code permission. 
/// (System.Security.SuppressUnmanagedCodeSecurityAttribute is applied to this class.) 
/// This class is for methods that are safe for anyone to call. 
/// Callers of these methods are not required to perform a full security review to make sure that the 
/// usage is secure because the methods are harmless for any caller.
/// </summary>
[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    internal static extern Int32 StrCmpLogicalW(string psz1, string psz2);
}
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 1
    Note StrCmpLogicalW is not supported on ARM devices. So if you're deploying through the Windows Store to ARM, this isn't going to fly. – Zodman Nov 24 '20 at 02:59
  • Nor is it supported in .NET core running on non-Windows OS's – Ian Boyd Jun 05 '23 at 12:14