5

I have following array

[0] = GB_22_T0001.jpg
[1] = GB_22_T0002.jpg
[2] = GB_22_T0003.jpg
[3] = GB_22_T0006.jpg
[4] = GB_22_T0007.jpg
[5] = GB_22_T0008.jpg
[6] = GB_22_T0009.jpg
[7] = GB_22_T00010.jpg
[8] = GB_22_T00011.jpg
[9] = GB_22_T00012.jpg
[10] = GB_22_T00013.jpg

I have put this items in a listbox and noticed that 'GB_22_T00010' comes straight after 'GB_22_T0001' instead of 'GB_22_T0002'

Seems to be a common issue with c# but cannot find a common answer to the problem.

I tried sorting the array with Array.sort(data) and also tried LinQ's OrderBy method but none of them helps.

Anyone with a solution?

Sahil
  • 1,959
  • 6
  • 24
  • 44
  • What are the actual items you are storing? If each one is an object already, just add a variable to it that stores the last numeric part, and then sort it on that using LINQ. – Sean Cogan May 24 '13 at 16:06
  • This is expected because you are sorting as strings. If you want a mixed comparison, then the #2 "Related" question offered by SO has the answer: [Sorting an array of folder names like Windows Explorer (Numerically and Alphabetically)](http://stackoverflow.com/questions/3099581/sorting-an-array-of-folder-names-like-windows-explorer-numerically-and-alphabet) (Surprised you said you couldn't find a common answer since it's right there.) – Raymond Chen May 24 '13 at 16:08
  • 2
    It looks like whoever made the file names made a silly mistake. The numbers following T all start with 000 which makes me think they were aware of this, but they don't use that padding when the number grows to more digits! I would expect to see ... 0008 0009 0010 0011 0012 ... – Timothy Shields May 24 '13 at 16:55

3 Answers3

5

This is my code to sort a string having both alpha and numeric characters.

First, this extension method :

public static IEnumerable<string> AlphanumericSort(this IEnumerable<string> me)
{
    return me.OrderBy(x => Regex.Replace(x, @"\d+", m => m.Value.PadLeft(50, '0')));
}

Then, simply use it anywhere in your code like this :

List<string> test = new List<string>() { "The 1st", "The 12th", "The 2nd" };
test = test.AlphanumericSort();

How does it works ? By replaceing with zeros :

  Original  | Regex Replace |      The      |   Returned
    List    | Apply PadLeft |    Sorting    |     List
            |               |               |
 "The 1st"  |  "The 001st"  |  "The 001st"  |  "The 1st"
 "The 12th" |  "The 012th"  |  "The 002nd"  |  "The 2nd"
 "The 2nd"  |  "The 002nd"  |  "The 012th"  |  "The 12th"

Works with multiples numbers :

 Alphabetical Sorting | Alphanumeric Sorting
                      |
 "Page 21, Line 42"   | "Page 3, Line 7"
 "Page 21, Line 5"    | "Page 3, Line 32"
 "Page 3, Line 32"    | "Page 21, Line 5"
 "Page 3, Line 7"     | "Page 21, Line 42"

Hope that's will help.

Picsonald
  • 301
  • 3
  • 4
2

GB_22_T0001 is a string not a number. So it's sorted lexicographically instead of numerically. So you need to parse a part of the string to an int.

var ordered = array.Select(Str => new { Str, Parts=Str.Split('_') })
                   .OrderBy(x => int.Parse(x.Parts.Last().Substring(1))) 
                   .Select(x => x.Str);

Split('_') splits the string into substrings on a delimiter _. The last substring contains your numeric value. Then i use String.Substring to take only the numeric part(remove the starting T) for int.Parse. This integer is used for Enumerable.OrderBy. The last step is to select just the string instead of the anonymous type.

Edit: Here is the version that supports Paths:

var ordered = array.Select(str => { 
    string fileName = Path.GetFileNameWithoutExtension(str);
    string[] parts =  fileName.Split('_');
    int number = int.Parse(parts.Last().Substring(1));
    return new{ str, fileName, parts, number };
 })
.OrderBy(x => x.number)
.Select(x => x.str);
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • Sorry I forgot to add .jpg at the end. Will this code work with .jpg at the end of the string? – Sahil May 24 '13 at 16:14
2

Windows has a built-in comparison function that you can use to compare strings like this (mix of strings and numbers): StrCmpLogicalW

You can use it as the guts of a IComparer to do your sorting.

This blog entry has many details about this: http://gregbeech.com/blog/natural-sort-order-of-strings-and-files

It works really well.

Edit: The implementation I used based on the above blog:

public sealed class NaturalStringComparer : IComparer<string>
{
  public static readonly NaturalStringComparer Default = new NaturalStringComparer();

  public int Compare(string x, string y)
  {
    return SafeNativeMethods.StrCmpLogicalW(x, y);
  }
}

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

Then to be used using LINQ:

var sortedItems = items.OrderBy(i => i, new NaturalStringComparer());
Matt Houser
  • 33,983
  • 6
  • 70
  • 88