3

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?

Kieran Quinn
  • 1,085
  • 2
  • 22
  • 49

5 Answers5

6

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();
Arion
  • 31,011
  • 10
  • 70
  • 88
  • 2
    This would only work if each line starts with the same letter, right? I guess to accomodate for that, you'd want to first order by `l.SubString(0,1)` and `ThenBy(l => double.Parse [...] )` – germi Apr 16 '15 at 09:05
  • What about culture decimal separator ? – Ilia Maskov Apr 16 '15 at 09:05
  • @germi : I update the answer. To answer your question. Yes. But with an order by then by. It will solve it – Arion Apr 16 '15 at 09:11
  • 1
    @agent5566 : I have update the answer to include the decimal seperation – Arion Apr 16 '15 at 09:11
  • Somone posted this answer and then must have deleted it. It seems to work great though: `result = result.OrderBy(x => x.PadRight(10)).ToList();` – Kieran Quinn Apr 16 '15 at 09:15
  • 1
    @KieranQuinn this solution wrong for `"E13.9"` and `"E9.0"` – Grundy Apr 16 '15 at 09:23
  • @Grundy I think it should be ok though, there will always be 2 digits before the decimal – Kieran Quinn Apr 16 '15 at 09:30
2

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.

Community
  • 1
  • 1
germi
  • 4,628
  • 1
  • 21
  • 38
1

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

SWeko
  • 30,434
  • 10
  • 71
  • 106
1

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.

Community
  • 1
  • 1
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
0

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)
           );
});
Grundy
  • 13,356
  • 3
  • 35
  • 55