Kieran Quinn
Kieran Quinn

Reputation: 1115

Sort list by Alphanumeric with decimal

I have a List<string> that I need to sort by alphanumeric but it also has a decimal. The sample looks like this:

E11.9
E13.9
E10.9
E11.65
E10.65
E11.69
E13.10
E10.10

The output I need should look like this:
E10.10
E10.65
E10.9
E11.69
E11.9
etc..

I've tried this code:

result.Sort((s1, s2) =>
{
    string pattern = "([A-Za-z])([0-9]+)";
    string h1 = Regex.Match(s1, pattern).Groups[1].Value;
    string h2 = Regex.Match(s2, pattern).Groups[1].Value;
    if (h1 != h2)
        return h1.CompareTo(h2);
    string t1 = Regex.Match(s1, pattern).Groups[2].Value;
    string t2 = Regex.Match(s2, pattern).Groups[2].Value;
    return int.Parse(t1).CompareTo(int.Parse(t2));
});

But it only seems to sort by the Letter first, then by the digits prior to the decimal place. So this is what I get:
E10.9
E10.65
E10.10
E11.9
E11.69
etc..

Am I missing something in the regex? Or is there a better way to accomplish this?

Upvotes: 3

Views: 1335

Answers (5)

Grundy
Grundy

Reputation: 13381

Yet another variant with updated regex

ls.Sort((a, b) =>
{
    var pattern = new Regex(@"(?<letter>[A-Za-z])(?<number>[0-9]+\.?[0-9]*)$");
    var matchA = pattern.Match(a);
    var matchB = pattern.Match(b);
    var compareLetter = matchA.Groups["letter"].Value.CompareTo(matchB.Groups["letter"].Value);
    if (compareLetter != 0) return compareLetter;

    return double.Parse(matchA.Groups["number"].Value, CultureInfo.InvariantCulture).CompareTo(
            double.Parse(matchB.Groups["number"].Value, CultureInfo.InvariantCulture)
           );
});

Upvotes: 0

Arion
Arion

Reputation: 31239

Can you just do this?:

Test data:

var ls=new List<string>
    {
    "E11.9",
    "E13.9",
    "E10.9",
    "E11.65",
    "E10.65",
    "E11.69",
    "E13.10",
    "E10.10",
    };

Linq:

var result= ls
             .OrderBy (l =>l.Substring(0,1))
             .ThenBy(l =>double.Parse(l.Substring(1), CultureInfo.InvariantCulture))
             .ToList();

Upvotes: 6

Tim Schmelter
Tim Schmelter

Reputation: 460108

Here's a LINQ solution without regex:

var ordered = from str in result
              let firstChar = string.IsNullOrEmpty(str) ? "" : str.Substring(0, 1)
              let decimalPart = string.IsNullOrEmpty(str) ? "" : str.Substring(1)
              let numOrNull = decimalPart.TryGetDecimal(NumberFormatInfo.InvariantInfo)
              orderby firstChar, numOrNull ?? decimal.MaxValue ascending, str ascending
              select str;
result = ordered.ToList();

Used this extension to parse the sub-string to decimal:

public static decimal? TryGetDecimal(this string item, IFormatProvider formatProvider = null)
{
    if (formatProvider == null) formatProvider = NumberFormatInfo.CurrentInfo;
    decimal d = 0m;
    bool success = decimal.TryParse(item, NumberStyles.Any, formatProvider, out d);
    if (success) 
        return d;
    else
        return null;
}

You should use this extension instead of a local variable in LINQ queries as learned here.

Upvotes: 1

germi
germi

Reputation: 4658

Building on Arion's answer:

var result = list.OrderBy(l => l[0]).ThenBy(l => double.Parse(l.Substring(1));

So first you'd sort by the letter and after that by the number after the letter. In case you need to handle different culture settings in double.Parse, you'd have to supply that there. See MSDN on Double.Parse.

Upvotes: 2

SWeko
SWeko

Reputation: 30892

If the format is [single-letter][decimal-number] you can do:

var result= list.
              .OrderBy(s => s.Substring(0,1))
              .ThenBy(s => double.Parse(s.Substring(1), CultureInfo.InvariantCulture));

with no need to involve regexes into the story

Upvotes: 1

Related Questions