Ctrl_Alt_Defeat
Ctrl_Alt_Defeat

Reputation: 4009

c# ordering strings with different formats

I have Licence plate numbers which I return to UI and I want them ordered in asc order:

So let's say the input is as below:

1/12/13/2
1/12/11/3
1/12/12/2
1/12/12/1

My expected output is:

1/12/11/3
1/12/12/1
1/12/12/2
1/12/13/2

My current code which is working to do this is:

var orderedData = allLicenceNumbers
   .OrderBy(x => x.LicenceNumber.Length)
   .ThenBy(x => x.LicenceNumber)
   .ToList();

However for another input sample as below:

4/032/004/2
4/032/004/9
4/032/004/3/A
4/032/004/3/B
4/032/004/11

I am getting the data returned as:

4/032/004/2
4/032/004/9
4/032/004/11
4/032/004/3/A
4/032/004/3/B

when what I need is:

4/032/004/2
4/032/004/3/A
4/032/004/3/B
4/032/004/9
4/032/004/11

Is there a better way I can order this simply to give correct result in both sample inputs or will I need to write a custom sort?

EDIT

It wont always be the same element on the string.

This could be example input:

2/3/5/1/A
1/4/6/7
1/3/8/9/B
1/3/8/9/A
1/5/6/7

Expected output would be:

1/3/8/9/A
1/3/8/9/B
1/4/6/7
1/5/6/7
2/3/5/1/A

Upvotes: 3

Views: 181

Answers (3)

Caius Jard
Caius Jard

Reputation: 74605

You seem to be wanting to sort on the fourth element of the string (delimited by /) in numeric rather than string mode.. ?

You can make a lambda more involved/multi-statement by putting it like any other method code block, in { }

var orderedData = allLicenceNumbers
 .OrderBy(x => 
  { 
   var t = x.Split('/');
   if(t.Length<4)
     return -1;
   else{
     int o = -1;
     int.TryParse(t[3], out o);
     return o;
  }
 )
 .ToList();

If you're after sorting on more elements of the string, you might want to look at some alternative logic, perhaps if the first part of the string will always be in the form N/NNN/NNN/??/?, then do:

var orderedData = allLicenceNumbers
 .OrderBy(w => w.Remove(9)) //the first 9 are always in the form N/NNN/NNN
 .ThenBy(x =>               //then there's maybe a number that should be parsed
  {  
   var t = x.Split('/');
   if(t.Length<4)
     return -1;
   else{
     int o = -1;
     int.TryParse(t[3], out o);
     return o;
  }
 )
 .ThenBy(y => y.Substring(y.LastIndexOf('/'))) //then there's maybe A or B..
 .ToList();

Ultimately, it seems that more and more outliers will be thrown into the mix, so you're just going to have to keep inventing rules to sort with..

Either that or change your strings to standardize everything (int an NNN/NNN/NNN/NNN/NNA format for example), and then sort as strings..

var orderedData = allLicenceNumbers
 .OrderBy(x =>               
  {  
   var t = x.Split('/');
   for(int i = 0; i < t.Length; i++) //make all elements in the form NNN
   {
     t[i] = "000" + t[i];
     t[i] = t[i].Substring(t[i].Length - 3);
   }
   return string.Join(t, "/");
  }
 )
 .ToList();

Mmm.. nasty!

Upvotes: 0

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186698

If we can assume that

  1. Number plate constist of several (one or more) parts separated by '/', e.g. 4, 032, 004, 2
  2. Each part is not longer than some constant value (3 in the code below)
  3. Each part consist of either digits (e.g. 4, 032) or non-digits (e.g. A, B)

We can just PadLeft each number plate's digit part with 0 in order to compare not "3" and "11" (and get "3" > "11") but padded "003" < "011":

  var source = new string[] {
    "4/032/004/2",
    "4/032/004/9",
    "4/032/004/3/A",
    "4/032/004/3/B",
    "4/032/004/11",
  };

  var ordered = source
    .OrderBy(item => string.Concat(item
       .Split('/')                            // for each part
       .Select(part => part.All(char.IsDigit) // we either
           ? part.PadLeft(3, '0') // Pad digit parts e.g. 3 -> 003, 11 -> 011 
           : part)));             // ..or leave it as is

  Console.WriteLine(string.Join(Environment.NewLine, ordered));

Outcome:

4/032/004/2
4/032/004/3/A
4/032/004/3/B
4/032/004/9
4/032/004/11

Upvotes: 1

hansmaad
hansmaad

Reputation: 18905

You should split your numbers and compare each part with each other. Compare numbers by value and strings lexicographically.

var licenceNumbers = new[]
{
    "4/032/004/2",
    "4/032/004/9",
    "4/032/004/3",
    "4/032/004/3/A",
    "4/032/004/3/B",
    "4/032/004/11"
};

var ordered = licenceNumbers
    .Select(n => n.Split(new[] { '/' }))
    .OrderBy(t => t, new LicenceNumberComparer())
    .Select(t => String.Join("/", t));

Using the following comparer:

public class LicenceNumberComparer: IComparer<string[]>
{ 
    public int Compare(string[] a, string[] b)
    {
        var len = Math.Min(a.Length, b.Length);
        for(var i = 0; i < len; i++)
        {
            var aIsNum = int.TryParse(a[i], out int aNum);
            var bIsNum = int.TryParse(b[i], out int bNum);
            if (aIsNum && bIsNum)
            {
                if (aNum != bNum)
                {
                    return aNum - bNum;
                }
            }
            else
            {
                var strCompare = String.Compare(a[i], b[i]);
                if (strCompare != 0)
                {
                    return strCompare;
                }
            }
        }
        return a.Length - b.Length;
    }
}

Upvotes: 3

Related Questions