Asim
Asim

Reputation: 90

Sort String List Numerically

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.

Upvotes: 1

Views: 239

Answers (6)

fixagon
fixagon

Reputation: 5566

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.

Upvotes: 1

Niyoko
Niyoko

Reputation: 7672

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;

Upvotes: 2

user1562155
user1562155

Reputation:

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).

Upvotes: 0

Enigmativity
Enigmativity

Reputation: 117174

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 

Upvotes: 1

kashi_rock
kashi_rock

Reputation: 557

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'));
}

Upvotes: 0

Leonard Klausmann
Leonard Klausmann

Reputation: 215

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;
}

}

Upvotes: -1

Related Questions