Search code examples
c#linqsortingalphabeticalalphanumeric

How do I sort strings alphabetically while accounting for value when a string is numeric?


I'm trying to sort an array of numbers that are strings and I'd like them to sort numerically.

The catch is that I cannot convert the numbers into int.

Here is the code:

string[] things= new string[] { "105", "101", "102", "103", "90" };

foreach (var thing in things.OrderBy(x => x))
{
    Console.WriteLine(thing);
}

Output:

101, 102, 103, 105, 90

I'd like:

90, 101, 102, 103, 105

EDIT: The output can't be 090, 101, 102...

Updated the code sample to say "things" instead of "sizes". The array can be something like this:

string[] things= new string[] { "paul", "bob", "lauren", "007", "90" };

That means it needs to be sorted alphabetically and by number:

007, 90, bob, lauren, paul

Solution

  • Pass a custom comparer into OrderBy. Enumerable.OrderBy will let you specify any comparer you like.

    This is one way to do that:

    void Main()
    {
        string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "101"};
    
        foreach (var thing in things.OrderBy(x => x, new SemiNumericComparer()))
        {    
            Console.WriteLine(thing);
        }
    }
    
    
    public class SemiNumericComparer: IComparer<string>
    {
        /// <summary>
        /// Method to determine if a string is a number
        /// </summary>
        /// <param name="value">String to test</param>
        /// <returns>True if numeric</returns>
        public static bool IsNumeric(string value)
        {
            return int.TryParse(value, out _);
        }
    
        /// <inheritdoc />
        public int Compare(string s1, string s2)
        {
            const int S1GreaterThanS2 = 1;
            const int S2GreaterThanS1 = -1;
    
            var IsNumeric1 = IsNumeric(s1);
            var IsNumeric2 = IsNumeric(s2);
    
            if (IsNumeric1 && IsNumeric2)
            {
                var i1 = Convert.ToInt32(s1);
                var i2 = Convert.ToInt32(s2);
    
                if (i1 > i2)
                {
                    return S1GreaterThanS2;
                }
    
                if (i1 < i2)
                {
                    return S2GreaterThanS1;
                }
    
                return 0;
            }
    
            if (IsNumeric1)
            {
                return S2GreaterThanS1;
            }
    
            if (IsNumeric2)
            {
                return S1GreaterThanS2;
            }
    
            return string.Compare(s1, s2, true, CultureInfo.InvariantCulture);
        }
    }