7

I have a filenames array, I want to sort it by numeric style, please give to me a solution.

Example1:

Original array: [name99.txt, name98.txt, name100.txt]
Sorted array: [name98.txt, name99.txt, name100.txt]
(Using string sorting, result of sorting is [name100.txt, name98.txt, name99.txt])

Example2:

Original array: [a99.txt, b98.txt, b100.txt]
Sorted array: [a99.txt, b98.txt, b100.txt]
(Using string sorting, result of sorting is [a99.txt, b100.txt, b99.txt])

hungbm06
  • 1,549
  • 6
  • 24
  • 32
  • 4
    What have you tried? Obviously it will sort alphanumeric by default, but have you tried writing a custom comparer, for example? – Marc Gravell Jul 17 '11 at 11:22
  • Your question is confusing because you don't explain **what you get** and **what you want to get**. What is “sorted array”, what is “result of sorting using string sorting”? Communicate your thoughts clearly, and you'll get good answers. – Dan Abramov Jul 17 '11 at 11:50

4 Answers4

12
string[] ar = new string[] { "name99.txt", "name98.txt", "name100.txt" };
Array.Sort(ar, (a, b) => int.Parse(Regex.Replace(a, "[^0-9]", "")) - int.Parse(Regex.Replace(b, "[^0-9]", "")));

foreach (var a in ar)
    Console.WriteLine(a);

The above assumed that your files are allways called name###.txt. For the real numeric sorting use the following more complicated version:

public static void NumericalSort(string[] ar)
{
    Regex rgx = new Regex("([^0-9]*)([0-9]+)");
    Array.Sort(ar, (a, b) =>
    {
        var ma = rgx.Matches(a);
        var mb = rgx.Matches(b);
        for (int i = 0; i < ma.Count; ++i)
        {
            int ret = ma[i].Groups[1].Value.CompareTo(mb[i].Groups[1].Value);
            if (ret != 0)
                return ret;

            ret = int.Parse(ma[i].Groups[2].Value) - int.Parse(mb[i].Groups[2].Value);
            if (ret != 0)
                return ret;
        }

        return 0;
    });
}

static void Main(string[] args)
{
    string[] ar = new string[] { "a99.txt", "b98.txt", "b100.txt" };

    NumericalSort(ar);

    foreach (var a in ar)
        Console.WriteLine(a);
}
Petar Ivanov
  • 91,536
  • 11
  • 82
  • 95
  • Great Petar! But, what you think about my "Example 2" – hungbm06 Jul 17 '11 at 11:35
  • Just posted an implementation of the true numeric sorting - it will cover your second example. – Petar Ivanov Jul 17 '11 at 11:44
  • Just noting that in production code, I would extract this in a separate method (and class, probably). It is not immediately obvious what it does, at all. – Dan Abramov Jul 17 '11 at 11:55
  • @Dan There's just no pleasing some people. First you complain about your inability to look things up on MSDN. Now you are whining about not being able to understand a .net implementation. – David Heffernan Jul 18 '11 at 08:06
  • 2
    @David: Yeah, I'm a picky type. Sometimes this leads to answers being improved, which is the purpose of comments on StackExchange, I suppose. In my opinion, a perfect answer would contain an implementation of `IComparer` called `FilenameComparer` with this code inside. This would prove both easily maintainable and stable. – Dan Abramov Jul 18 '11 at 11:21
4

There may well be a managed way to do this, but I would probably just P/invoke to StrCmpLogicalW.

[DllImport("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
static extern int StrCmpLogicalW(String x, String y);    

If you use this function, rather than rolling your own comparison function, you'll get the same behaviour as Explorer and other system components that use logical comparison.

Note, however, that this will not work in environments where WinAPI is inaccessible (such as Windows Phone, Mono or Silverlight), might work differently on different systems and should be decorated with a comment so the future maintainer of your code knows why P/Invoke is used for sorting.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    Nice. didn't know that call existed. – Marc Gravell Jul 17 '11 at 11:31
  • 2
    I wouldn't use WinAPI for string comparison. Firstly, it ties code to a very particular platform (I'm not even talking Mono here—think Silverlight, Windows Phone). Secondly, **we are not even interoperating with Windows**. I understand calling WinAPI for handling complex corner cases with window control drawing in Windows Forms, or doing some real low-level stuff. But come on, *compare strings with WinAPI*? As a .NET developer, I have *no idea* what `StrCmpLogicalW` does and even if `shlwapi.dll` exists on all systems we want target. – Dan Abramov Jul 17 '11 at 11:45
  • What I'm basically saying is, by using this call you obligate future developers to spend 10 minutes of their time researching WinAPI documentation (btw, it's in C) and more time debugging it when something *does* go wrong (e.g. function result might depend on Regional Settings, the library may not exist on some systems, et cetera). – Dan Abramov Jul 17 '11 at 11:47
  • @David: sure. I just wanted to point out what I think is wrong with this approach. – Dan Abramov Jul 17 '11 at 11:48
  • why not add those points in an edit to the answer. That's just a bit more constructive than a downvote. – David Heffernan Jul 17 '11 at 11:53
  • @David: You're right I should've done precisely that instead of writing a comment. I also removed the downvote. – Dan Abramov Jul 18 '11 at 11:34
  • I'm looking for functionality similar to how Windows Explorer orders strings (well, filenames, actually), and so at first `StrCmpLogicalW` looks great, with the caveats @DanAbramov mentioned. But then I noticed that with this function, there's no way to pass in an arbitrary culture. And most importantly, the warning in the MSDN documentation: "**Note:**  Behavior of this function, and therefore the results it returns, can change from release to release. _It should not be used for canonical sorting applications_" (emphasis mine). That's a deal-breaker for me! – fourpastmidnight Jan 18 '18 at 05:52
1

One solution can be found here: Alphanumeric Sorting

c0deNinja
  • 3,956
  • 1
  • 29
  • 45
0

My approach is good when the length of the numeric chunks is no longer than 9 digits:

private string[] NumericalSort(IEnumerable<string> list)
{
    var ar = list.ToArray();

    Array.Sort(ar, (a, b) =>
    {
        var aa = Regex.Replace(a, @"\d+", m => m.Value.PadLeft(9, '0'));
        var bb = Regex.Replace(b, @"\d+", m => m.Value.PadLeft(9, '0'));
        
        return string.Compare(aa, bb);
    });

    return ar;
}
IDizor
  • 133
  • 2
  • 7