4

I am looking for a natural sort technique like Windows explorer does.

For example, if I have the Alpha-numeric Array -

var array = new[]{"B01 002", "B01 0010", "01", "B01 001", "B10 001", "B01 01", "1", "B1 001", "B02 001", "A1"};

I am expecting this to be sorted in the below Order-
[01, 1, A1, B01 001, B01 01, B01 002, B01 0010, B1 001, B02 001, B10 001]

This is precisely the way that Windows Explorer does it-

enter image description here

I have tried the solutions in the below thread-

No other constraint on the approach taken. Either it is LINQ, Regex, Extention, or Interface approach that can be used to replicate the sort order done by "shlwapi.dll"

Erçin Dedeoğlu
  • 4,950
  • 4
  • 49
  • 69
theLearner
  • 363
  • 4
  • 16
  • It's in Japanese, but maybe you can use this article and the source code. [C# 自然順で文字列をソートする](https://qiita.com/tomochan154/items/1a3048f2cd9755233b4f), [toy-box/NaturalComparer.cs](https://github.com/ryosuke-ninomiya/toy-box/blob/master/NaturalComparer.cs) – kunif Sep 09 '22 at 07:36
  • Does this solution not work? https://stackoverflow.com/a/66354540/14868997 – Charlieface Sep 09 '22 at 09:12
  • See https://dotnetfiddle.net/q9aWDI it looks exactly the same as you requested – Charlieface Sep 09 '22 at 09:17
  • Does this answer your question? [Natural Sort Order in C#](https://stackoverflow.com/questions/248603/natural-sort-order-in-c-sharp) – Charlieface Sep 11 '22 at 01:58

1 Answers1

3

I suggest splitting string to chunks where each chunk is either all digits or no digits: "B01 001" -> {"B", "01", " ", "001"}. Then compare these chunks (comparing two all digits chunks being a special case).

Code: (Fiddle)

public sealed class NaturalComparer : IComparer<string> {

  private static int CompareChunks(string x, string y) {
    if (x[0] >= '0' && x[0] <= '9' && y[0] >= '0' && y[0] <= '9') {
      string tx = x.TrimStart('0');
      string ty = y.TrimStart('0');

      int result = tx.Length.CompareTo(ty.Length);

      if (result != 0)
        return result;

      result = tx.CompareTo(ty);

      if (result != 0)
        return result;
    }
    
    return string.Compare(x, y);
  }

  public int Compare(string? x, string? y) {
    if (ReferenceEquals(x, y))
      return 0;
    if (x is null)
      return -1;
    if (y is null)
      return +1;

    var itemsX = Regex
      .Split(x, "([0-9]+)")
      .Where(item => !string.IsNullOrEmpty(item))
      .ToList();

    var itemsY = Regex
      .Split(y, "([0-9]+)")
      .Where(item => !string.IsNullOrEmpty(item))
      .ToList();

    for (int i = 0; i < Math.Min(itemsX.Count, itemsY.Count); ++i) {
      int result = CompareChunks(itemsX[i], itemsY[i]);

      if (result != 0)
        return result;
    }

    return itemsX.Count.CompareTo(itemsY.Count);
  }
}

Demo:

string[] demo = new string[] {
    "B01 002", 
    "B01 0010", 
    "01", 
    "B01 001", 
    "B10 001", 
    "B01 01", 
    "1", 
    "B1 001", 
    "B02 001", 
    "A1"
};

Array.Sort(demo, new NaturalComparer());

Console.WriteLine(string.Join(Environment.NewLine, demo));

Output:

01
1
A1
B01 001
B01 01
B01 002
B01 0010
B1 001
B02 001
B10 001
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215