10

I have been using the following sort:

var query = _cityRepository.GetAll(
               .OrderBy(item => item.RowKey.Substring(0, 4))
               .ThenBy(item => item.ShortTitle)

However I am having a problem because my ShortTitle looks like this:

Liverpool - 1
Liverpool - 2
...
Liverpool - 9
Liverpool - 10
Liverpool - 11
West Kirby - 1
West Kirby - 8
West Kirby - 12

When I sort this using LINQ it comes in the order

Liverpool - 1
Liverpool - 11
Liverpool - 12
Liverpool - 2
West Kirby - 1
West Kirby - 12
West Kirby - 8

The ShortTitle is always a string of words followed by a single hyphen and then a number.

Is there a way I can get this to sort correctly?

Alan2
  • 23,493
  • 79
  • 256
  • 450

5 Answers5

22

Try this

List<string> list = new List<string>() { 
    "Liverpool - 1",
    "Liverpool - 11",
    "Liverpool - 12",
    "Liverpool - 2",
    "West Kirby - 1",
    "West Kirby - 12",
    "West Kirby - 8" };
var sortedList = list.CustomSort().ToArray();    


public static class MyExtensions
{
    public static IEnumerable<string> CustomSort(this IEnumerable<string> list)
    {
        int maxLen = list.Select(s => s.Length).Max();

        return list.Select(s => new
        {
            OrgStr = s,
            SortStr = Regex.Replace(s, @"(\d+)|(\D+)", m => m.Value.PadLeft(maxLen, char.IsDigit(m.Value[0]) ? ' ' : '\xffff'))
        })
        .OrderBy(x => x.SortStr)
        .Select(x => x.OrgStr);
    }

}
L.B
  • 114,136
  • 19
  • 178
  • 224
  • @LB - This looks good. Can you explain how I could fit this into my code in the question? Is there a way I could make it so I can add your sort example just like I added .OrderBy and .ThenBy ? – Alan2 Jun 15 '12 at 14:12
  • 1
    This is a pretty convoluted solution for something with an elegant linq solution. – NominSim Jun 15 '12 at 14:14
  • I should note that I didn't downvote this, as it seems a perfectly reasonable approach... – NominSim Jun 15 '12 at 14:21
  • 1
    @NominSim this is a general solution. It works also for `list = new List() { "100.1", "10.20","10.5", "2", "5.9" };` – L.B Jun 15 '12 at 14:25
  • 1
    @Gemma I think you can easily convert it to an extension method to use like `list.CustomSort()` – L.B Jun 15 '12 at 14:26
  • +1 for the idea to make an extension method. But I would replace the regex algorithm with linq ;) – IAbstract Jun 15 '12 at 21:00
  • @IAbstract, The idea is: find all numbers in a string and replace them with a fixed length string like `text File(2)=>text File(0002)` , `text File(10)=>text File(0010)`. How would you do it with Linq? – L.B Jun 15 '12 at 21:42
  • I'm simply not fond of `regex` - unless a Linq query will not meet requirements. I like the Linq implementation that @NominSim presented. I wanted to give you credit for an equally productive answer (in spite of my dislike for regex) and the extension method! – IAbstract Jun 16 '12 at 01:25
11

It's because you are sorting them as strings, and for strings 11 comes before 2. You'll need to parse the ShortTitle to give you the (I'm assuming) int value at the end, and sort by that.

Your LINQ query can be changed to this for it to work:

var query = _cityRepository.GetAll(
           .OrderBy(item => item.RowKey.Substring(0, 4))
           .ThenBy(item => item.ShortTitle.Split('-').First())
           .ThenBy(item => Convert.ToInt16(item.ShortTitle.Split().Last()));
NominSim
  • 8,447
  • 3
  • 28
  • 38
  • The problem here is that I have a lot more cities and combination of words than just Liverpool. For example "North West - 1", "North West - 2". I can just use a substring – Alan2 Jun 15 '12 at 14:11
  • @NominSim - Hello Nomin, Don't I need another sort in there to sort by the city. So the sort would be first RowKey then city (up to the hyphen) then numeric sort after the hyphen? – Alan2 Jun 15 '12 at 14:27
3

quick and dirty:

class Program
{
    static void Main(string[] args)
    {
        var list = new[] {"Liverpool - 1", 
                          "Liverpool - 11",
                          "Liverpool - 123",
                          "Liverpool - 342",
                          "Liverpool - 2"};

        foreach (var x in list.OrderBy(s => Int32.Parse(Regex.Match(s, @"- (\d*)").Groups[1].Value)))
            Console.WriteLine(x);
    }
}
sloth
  • 99,095
  • 21
  • 171
  • 219
1

Late to the party, but I won't let that stop me!

Piggybacking on @NominSim's nice solution:

var query = _cityRepository.GetAll()
           .Select(item => 
             {
               var fields = item.ShortTitle.Split('-');
               return new
               {
                 Key = item.RowKey.Substring(0,4),
                 Title = fields[0].Trim(),
                 Index = Convert.ToInt16(fields[1])
               }
             })
           .OrderBy(item => item.Key)
           .ThenBy(item => item.Title)
           .ThenBy(item => item.Index);

Really, the main difference is capturing the result of the split so that we don't have to do the split again to get the index and then I added a Trim() to the Title.

wageoghe
  • 27,390
  • 13
  • 88
  • 116
0

You can create your own Comparer and pass it to OrderBy or ThenBy to have them sort string in the way you want.

For example, this very primitive comparer, will pad the last number with 0s before converting the strings:

class MyComparer:Comparer<string>
{
    public override int Compare(string x, string y)
    {
        var xL = xparts[xparts.Length - 1];
        long xN;
        if (long.TryParse(xL,out xN))
        {
            xparts[xparts.Length - 1] = xN.ToString().PadLeft(20,'0');
        }

        var yL = yparts[yparts.Length - 1];
        long yN;
        if (long.TryParse(yL, out yN))
        {
            yparts[yparts.Length - 1] = yN.ToString().PadLeft(20, '0');
        }

        var x2=String.Join(" ", xparts);
        var y2 = String.Join(" ", yparts);

        return x2.CompareTo(y2);
    }
}

and you can call it with minimal changes:

var query = _cityRepository.GetAll(
           .OrderBy(item => item.RowKey.Substring(0, 4))
           .ThenBy(item => item.ShortTitle, new MyComparer());
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236