1

We have some columns in a grid that contain numbers, strings, or strings that start with a number and the users expect them to sort in a sensible way. (The format depends on the customer so we don’t know the formats the strings will be in)

Is there a pre-canned” implementation of IComparable that does something like this?

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
  • look here http://stackoverflow.com/a/8362864/920384 – punker76 Dec 07 '11 at 14:49
  • 1
    Could you be a little more specific about what you're looking for? What it sounds like you want is an `IComparable` that sorts according to every customer's different but undefined wishes? – Edward Thomson Dec 07 '11 at 14:50
  • I removed the wpf, winforms and datagrid tags, and added an algorithm tag, since the question is really only about comparing strings. – Olivier Jacot-Descombes Dec 07 '11 at 14:53
  • @EdwardThomson, yes that is what I am looking for, but in real life the values are numbers, or strings that start or end in numbers – Ian Ringrose Dec 07 '11 at 14:54
  • Oh - I understand - the string format will differ depending on the customer - the way I read this, the sort order should differ depending on the customer. – Edward Thomson Dec 07 '11 at 14:55
  • @OlivierJacot-Descombes, the tagging is hard, as this is a problem with sorting rows in a grid as part of a UI, so someone is lickly to be looking for solutions in those tag. – Ian Ringrose Dec 07 '11 at 15:10
  • @IanRingrose: It does not make a difference, whether your IComparable is used in a grid or somewhere else. I would work as well for sorting arrays or whatever. – Olivier Jacot-Descombes Dec 07 '11 at 15:30
  • see also http://stackoverflow.com/questions/104599/sort-on-a-string-that-may-contain-a-number – Ian Ringrose Dec 08 '11 at 12:56

2 Answers2

2

here is a fast alphanumeric sort (can be used for other sorts with numerics too).

C# Alphanumeric Sorting http://www.dotnetperls.com/alphanumeric-sorting

var unordered = new[] { "100F", "50F", "SR100", "SR9" };
var ordered = unordered.OrderBy(s => s, new AlphanumComparatorFast());

and here is a nice article about the problem:

Sorting for Humans : Natural Sort Order http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html

Here is the AlphanumComparatorFast class in case the above link stops working The end of the page states "It can be used in any program with no restrictions" and the comment at the start of the code also indicates it is free for use:

// NOTE: This code is free to use in any program.
// ... It was developed by Dot Net Perls.

public class AlphanumComparatorFast : IComparer
{
    public int Compare(object x, object y)
    {
        string s1 = x as string;
        if (s1 == null)
        {
            return 0;
        }
        string s2 = y as string;
        if (s2 == null)
        {
            return 0;
        }

        int len1 = s1.Length;
        int len2 = s2.Length;
        int marker1 = 0;
        int marker2 = 0;

        // Walk through two the strings with two markers.
        while (marker1 < len1 && marker2 < len2)
        {
            char ch1 = s1[marker1];
            char ch2 = s2[marker2];

            // Some buffers we can build up characters in for each chunk.
            char[] space1 = new char[len1];
            int loc1 = 0;
            char[] space2 = new char[len2];
            int loc2 = 0;

            // Walk through all following characters that are digits or
            // characters in BOTH strings starting at the appropriate marker.
            // Collect char arrays.
            do
            {
                space1[loc1++] = ch1;
                marker1++;

                if (marker1 < len1)
                {
                    ch1 = s1[marker1];
                }
                else
                {
                    break;
                }
            } while (char.IsDigit(ch1) == char.IsDigit(space1[0]));

            do
            {
                space2[loc2++] = ch2;
                marker2++;

                if (marker2 < len2)
                {
                    ch2 = s2[marker2];
                }
                else
                {
                    break;
                }
            } while (char.IsDigit(ch2) == char.IsDigit(space2[0]));

            // If we have collected numbers, compare them numerically.
            // Otherwise, if we have strings, compare them alphabetically.
            string str1 = new string(space1);
            string str2 = new string(space2);

            int result;

            if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
            {
                int thisNumericChunk = int.Parse(str1);
                int thatNumericChunk = int.Parse(str2);
                result = thisNumericChunk.CompareTo(thatNumericChunk);
            }
            else
            {
                result = str1.CompareTo(str2);
            }

            if (result != 0)
            {
                return result;
            }
        }
        return len1 - len2;
    }
}
John Cummings
  • 1,949
  • 3
  • 22
  • 38
punker76
  • 14,326
  • 5
  • 58
  • 96
1

Regarding the code taken from http://www.dotnetperls.com/alphanumeric-sorting: Parse will throw an Exception when the number is out of the 'int' range, this lead to exception in our third party grid tool "Unable to sort because the IComparer.Compare() method returns inconsistent results."

We changed the code to use decimal instead of int, and TryParse instead of Parse.

Original part:

int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);

Our version of it:

bool canParseStr1 = Decimal.TryParse(str1, out decimal thisNumericChunk);
bool canParseStr2 = Decimal.TryParse(str2, out decimal thatNumericChunk);
if (canParseStr1 && canParseStr2)
{
   result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
   // just return something, be deterministic
   result = str1.CompareTo(str2);
}
novalus
  • 11
  • 1