Reputation: 1115
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
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
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
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
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
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