A big part of the solution is found in other answers like this one but more is needed to sort the results.
Here's a class that will take a list of strings and return them sorted by the numeric values they represent:
public delegate long ConvertStringToNumberDelegate(string number);
public class NumbersAsTextSorter
{
private readonly ConvertStringToNumberDelegate _convertStringToNumberFunction;
public NumbersAsTextSorter(ConvertStringToNumberDelegate convertStringToNumberFunction)
{
_convertStringToNumberFunction = convertStringToNumberFunction;
}
public IEnumerable<string> ReverseSort(IEnumerable<string> input)
{
var textWithNumbers = input.Select(i => new {input = i, value = _convertStringToNumberFunction(i)});
return textWithNumbers.OrderByDescending(t => t.value).Select(s => s.input);
}
}
This will create a collection of text inputs matched with numeric values. It sorts the collection by the numeric value and then returns the text inputs in that sorted order.
As you can see, a critical detail is missing. How do you convert "one" to 1, "two" to 2, and so forth?
For that you need an implementation of the delegate - ConvertStringToNumberDelegate
. You can get that by using the class in the the answer I referenced above.
For convenience I've copied that static method (for which I am explicitly not taking credit!) into a static class:
public static class TestToNumbers
{
private static Dictionary<string, long> numberTable =
new Dictionary<string, long>
{{"zero",0},{"one",1},{"two",2},{"three",3},{"four",4},
{"five",5},{"six",6},{"seven",7},{"eight",8},{"nine",9},
{"ten",10},{"eleven",11},{"twelve",12},{"thirteen",13},
{"fourteen",14},{"fifteen",15},{"sixteen",16},
{"seventeen",17},{"eighteen",18},{"nineteen",19},{"twenty",20},
{"thirty",30},{"forty",40},{"fifty",50},{"sixty",60},
{"seventy",70},{"eighty",80},{"ninety",90},{"hundred",100},
{"thousand",1000},{"million",1000000},{"billion",1000000000},
{"trillion",1000000000000},{"quadrillion",1000000000000000},
{"quintillion",1000000000000000000}};
public static long ToLong(string numberString)
{
var numbers = Regex.Matches(numberString, @"\w+").Cast<Match>()
.Select(m => m.Value.ToLowerInvariant())
.Where(v => numberTable.ContainsKey(v))
.Select(v => numberTable[v]);
long acc = 0, total = 0L;
foreach (var n in numbers)
{
if (n >= 1000)
{
total += (acc * n);
acc = 0;
}
else if (n >= 100)
{
acc *= n;
}
else acc += n;
}
return (total + acc) * (numberString.StartsWith("minus",
StringComparison.InvariantCultureIgnoreCase) ? -1 : 1);
}
}
Now you can create an instance of NumbersAsTextSorter
which uses that static method to convert text to numbers. I've done that in this unit test which passes:
[TestMethod]
public void TestSorting()
{
var sorter = new NumbersAsTextSorter(TestToNumbers.ToLong);
var output = sorter.ReverseSort(new string[] { "one", "three hundred", "zero" });
Assert.IsTrue(output.SequenceEqual(new string[] { "three hundred", "one", "zero" }));
}
For clarity, this is passing in "one", "three hundred", and "zero" and asserting that after it's sorted, the sequence is "three hundred", "one", "zero".