1

I have a following string list:

List<string> a = new List<string>();

a.Add("2015");
a.Add("2015 /M1");
a.Add("2015 /M2");
a.Add("2015 /M9");
a.Add("2015 /M10");
a.Add("2015 /M11");
a.Add("2015 /M12");
a.Add("2015 /M3");
a.Add("2015 /M4");
a.Add("2015 /M5");
a.Add("2015 /M6");
a.Add("2015 /M7");
a.Add("2015 /M8");

When I call the sort function a.sort() it sorts like this:

2015
2015 /M1
2015 /M11
2015 /M12
2015 /M2
2015 /M3
2015 /M4
2015 /M5
2015 /M6
2015 /M7
2015 /M8
2015 /M9

But how can I modify to make it look like below.

2015
2015 /M1
2015 /M2
2015 /M3
2015 /M4
2015 /M5
2015 /M6
2015 /M7
2015 /M8
2015 /M9
2015 /M10
2015 /M11
2015 /M12

I have same pattern in other list items as well like 2015 Q/12, 2015 Q/11 etc.

Peter B
  • 22,460
  • 5
  • 32
  • 69
Asim
  • 90
  • 9
  • 2
    Implement IComparer interface and perform your custom sorting algorithm and pass that to the Sort method. MSDN is your friend. – Thangadurai Oct 26 '16 at 05:09
  • is your input format always "yyyy X/MM", where yyyy is year, X is any random char, MM is month? The short answer to your problem is sort by int, not by string. – kurakura88 Oct 26 '16 at 05:11
  • I need it to be done in string list as I have 2015 Q/1, 2015 Q/2 as well and these values are created dynamically. – Asim Oct 26 '16 at 05:14

6 Answers6

2

You need to extract the number from string, it can be done by regex. Then convert it to integer and sort by it.

var e = from s in a
        let g = Regex.Match(s, @"^\d+(?: \/[MQ](\d+))?$")
        let n = g.Groups[1].Value != "" ? int.Parse(g.Groups[1].Value) : (int?)null
        orderby n
        select s;

a = e.ToList();

Edit

To sort by year first, then use following code

var e = from s in a
        let g = Regex.Match(s, @"^[A-Za-z]*(\d+)(?: \/[MQ](\d+))?$")
        let y = g.Groups[1].Value != "" ? int.Parse(g.Groups[1].Value) : 0
        let m = g.Groups[2].Value != "" ? int.Parse(g.Groups[2].Value) : 0                    
        orderby y, m      
        select s;
Niyoko
  • 7,512
  • 4
  • 32
  • 59
  • What if want to sort 2015 Q/1 is same string along with 2015 M/1? – Asim Oct 26 '16 at 05:36
  • @Asim Do you want /Q1 to sort after /M1 or after /M3? – Niyoko Oct 26 '16 at 05:39
  • @NiyokoYuliawan I have tested your method and it solves the the months issue, show can I modify it to make it work for Q/1 as well. – Asim Oct 26 '16 at 06:39
  • @NiyokoYuliawan thanks, how can I modify it to add CY or FY at start like CY2015 /M1. It wil be either CY or FY in whole string. Thanks – Asim Oct 26 '16 at 06:56
  • @Asim Change your regex pattern to `^[A-Za-z]*\d+(?: \/[MQ](\d+))?$`. – Niyoko Oct 26 '16 at 07:08
  • I have tried this, solves the M/12 M/2 issue but it did sort on base of years. e.g here was my list 2009 2010 2011 2009/M1 2009/M10 2009/M2 it should be sorted like 2009 2009/M1 2009/M2 2009/M10 but it is giving results like this 2009 2010 2011 2009/M1 2009/M2 2009/M10 does removing space between 2009 and M/1 makes any difference? and should I use it with sort like a.sort and then apply your function ? – Asim Oct 26 '16 at 07:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/126694/discussion-between-niyoko-yuliawan-and-asim). – Niyoko Oct 26 '16 at 07:18
1

I did this:

var results =
    a
        .OrderBy(x => new string(x.Take(7).ToArray()))
        .ThenBy(x => int.Parse(new string(x.Skip(7).DefaultIfEmpty('0').ToArray())));

...and got this:

2015 
2015 /M1 
2015 /M2 
2015 /M3 
2015 /M4 
2015 /M5 
2015 /M6 
2015 /M7 
2015 /M8 
2015 /M9 
2015 /M10 
2015 /M11 
2015 /M12 
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
1

You are looking for natural sort order. In the linked question you can find a pure LINQ implementation which can be reused for any natural sort order problem.

Community
  • 1
  • 1
fixagon
  • 5,506
  • 22
  • 26
0

This post was similar to yours. Edited as per your requirement:

var sortedList = a.OrderBy(x => PadNumbers(!x.Contains("M")? "" : x.Substring(x.IndexOf('M'), (x.Length - x.IndexOf('M'))))).ToList();

public static string PadNumbers(string input)
{
    return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0'));
}
Community
  • 1
  • 1
kashi_rock
  • 539
  • 1
  • 5
  • 19
0

I have elaborated on Niyoko Yuliawans solution and the result is:

  class StringComparer : IComparer<string>
  {
    const string pattern = @"^\D*(?<year>\d{4})( \/[MQ](?<index>\d+))?$";

    public int Compare(string x, string y)
    {
      var mx = Regex.Match(x, pattern);
      var my = Regex.Match(y, pattern);

      int ix;
      if (int.TryParse(mx.Groups["index"].Value, out ix))
      {
        int iy;
        if (int.TryParse(my.Groups["index"].Value, out iy))
        {
          return ix.CompareTo(iy);
        }
      }

      return mx.Groups["year"].Value.CompareTo(my.Groups["year"].Value);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {

      List<string> a = new List<string>();

      a.Add("2015");
      a.Add("2015 /Q1");
      a.Add("CY2015 /Q2");
      a.Add("2015 /Q9");
      a.Add("2015 /Q10");
      a.Add("2015 /Q11");
      a.Add("2015 /Q12");
      a.Add("2015 /Q3");
      a.Add("2014");
      a.Add("2015 /Q4");
      a.Add("2015 /Q5");
      a.Add("2015 /Q6");
      a.Add("2015 /Q7");
      a.Add("2015 /Q8");

      a.Sort(new StringComparer());

      foreach (var x in a)
      {
        Console.WriteLine(x);
      }

      Console.WriteLine("END");
      Console.ReadLine();

    }
  }

It avoids the temporary sequence (e).

-1

you can also write easily your own Extension for this sort function:

    public static class Extension
{
    public static List<string> sortItMyWay(this List<string> mylist)
    {
        string temp = string.Empty;

        for (int write = 0; write < mylist.Count; write++)
        {
            for (int sort = 0; sort < mylist.Count - 1; sort++)
            {
                if (mylist[sort].Weight() > mylist[sort + 1].Weight())
                {
                    temp = mylist[sort + 1];
                    mylist[sort + 1] = mylist[sort];
                    mylist[sort] = temp;
                }
            }
        }
        return mylist;
    }


public static int Weight (this string input)
{
    var value = 0;
    for (int i = input.Length - 1; i >= 0 ; i--)
    {
        value += input[i] * (int)Math.Pow(10,i);
    }
    return value;
}

}

  • How does this answer the question? – Enigmativity Oct 26 '16 at 05:46
  • This is effectively sorting by string length? It fails if the string is too long (due to the `Math.Pow). It's not at all an obvious solution. Can you provide an explanation? – Enigmativity Oct 26 '16 at 06:27
  • This weight function is giving a int value to each string. It is not sorting by lenght ! You are right concerning the restriction in Length, but it's ok for the question. This extension is maybe no best practice, but the questioner can change the Weight function the way he likes, for this question and for each other way of sorting he likes. In my opinion this has a much higher learning effect then just copying the regular expression, without getting how it works. The sortItMyWay function is just a bubble sort, using the weight function. – Leonard Klausmann Oct 26 '16 at 06:53