简体   繁体   中英

How to sort a list of strings by prefix number then alphabetically

My goal is to sort a List<string> in messy order to an order like this ["1", "1a", "2", "2a", "2b", "a", "b"]

My code is a little long, so I've included it at this link https://dotnetfiddle.net/wZ0dTG .

What I'm trying to do is split the strings using Regex.Split(string, "([0-9]+)")[0] then based on which strings pass int.TryParse , I sort the list numerically or alphabetically.

The regex matches all the integers contained within the string.

Until I apply the regex, it sorts. Although it sorts, it doesn't sort them properly.

When I apply the regex, I get this error:

ArgumentException: Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: 'System.Comparison`1[Ndot_Partnering_Tool.Data.Models.Project

For this particular task the OrderBy method is perfect for you. I would use that instead of Regex. OrderBy uses lambda expressions as a key to sort. Since letters are after numbers in the alphabet this method is using, you can actually just sort by default.

You can do:

List<string> List = new List<string>() {"a", "2b", "1a", "1", "2", "2a", "b", "1b" };
List<string> OrderedList = List.OrderBy(x => x).ToList(); 

The OrderBy method returns IEnumerable so you have to convert it back to List.

Output:

The original list: a 2b 1a 1 2 2a b 1b

The ordered list: 1 1a 1b 2 2a 2b ab

So you have to split your strings into an (optional) numeric part and an (optional) rest. This can be done by a Regex:

var match = Regex.Match(item, @"(?<number>\d+)?(?<rest>.*)$");

The "number" part matches one or more digits, but is optional (question mark), the "rest" part matches the whole rest of the string.

Sorting via Linq:

var input = new List<string>{ "12a", "1", "1a", "2", "2a", "2b", "a", "b", "12a" };
var sorted = input.OrderBy(item =>
{
    var match = Regex.Match(item, @"(?<number>\d+)?(?<rest>.*)$");
    return Tuple.Create(match.Groups["number"].Success ? int.Parse(match.Groups["number"].Value) : -1, match.Groups["rest"].Value);
}).ToList();

(I deliberately decided to put the items without a leading number before the rest; this was not specified in the question).

Output: a, b, 1, 1a, 2, 2a, 2b, 12a

Two problems:

  1. SplitRegex() failes on argument "a" because it does not match regex (RegEx.Split returns array with one element). You can use this code:

    return Regex.Split(x, "([0-9]+)").ElementAtOrDefault(1)?? string.Empty;

  2. When neither x nor y can be converted to integer, you call CompareString() for x and y, but x and y are not whole strings, they are only numerical parts (and because of that empty). You need to pass list items as is to comparer and extract numbers there:

     bool leftcanconvertflag = Int32.TryParse(SplitRegex(x), out leftconvertresult); bool rightcanconvertflag = Int32.TryParse(SplitRegex(y), out rightconvertresult); if (leftcanconvertflag &&;rightcanconvertflag) { compareresult = -1; } if (.leftcanconvertflag && rightcanconvertflag) { compareresult = 1; } if (leftcanconvertflag && rightcanconvertflag) { compareresult = leftconvertresult,CompareTo(rightconvertresult); } if (!leftcanconvertflag && !rightcanconvertflag) { compareresult = CompareString(x, y); }

and sort list like this:

list.Sort(CompareContractNumbers);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM