8

I've got various chapters with different depths.

so there are 14.1 and 14.4.2 and 14.7.8.8.2 and so on.

Alphanumerical sorted the 14.10 will appear before 14.2. That's bad. It should come after 14.9.

Is there an easy way to sort theese, without adding leading zeros? f.e. with linq?

Harry
  • 1,313
  • 3
  • 22
  • 37
  • 2
    I think the accepted answer [here](http://stackoverflow.com/questions/6248039/how-to-sort-list-of-ip-addresses-using-c-sharp) is valid for your case as well.. although you will be restricted to four "levels". – Shadow The GPT Wizard Jan 23 '12 at 12:25
  • looks quite nice, 4 levels - ok. Ideas for more still would be nice :) – Harry Jan 23 '12 at 12:32

6 Answers6

7
public class NumberedSectionComparer : IComparer<string>
{
  private int Compare(string[] x, string[]y)
  {
    if(x.Length > y.Length)
      return -Compare(y, x);//saves needing separate logic.
    for(int i = 0; i != x.Length; ++i)
    {
      int cmp = int.Parse(x[i]).CompareTo(int.Parse(y[i]));
      if(cmp != 0)
        return cmp;
    }
    return x.Length == y.Length ? 0 : -1;
  }
  public int Compare(string x, string y)
  {
    if(ReferenceEquals(x, y))//short-cut
      return 0;
    if(x == null)
      return -1;
    if(y == null)
      return 1;
    try
    {
      return Compare(x.Split('.'), y.Split('.'));
    }
    catch(FormatException)
    {
      throw new ArgumentException();
    }
  }
}
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
4

I did this right now, need some tests:

using System;
using System.Collections.Generic;
using System.Linq;

namespace TestesConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] vers = new[]
                              {
                                  "14.10",
                                  "14.9",
                                  "14.10.1",
                              };


            var ordered = vers.OrderBy(x => x, new VersionComparer()).ToList();

        }
    }

    public class VersionComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            string[] xs = x.Split('.');
            string[] ys = y.Split('.');

            int maxLoop = Math.Min(xs.Length, ys.Length);

            for (int i = 0; i < maxLoop; i++)
            {
                if(int.Parse(xs[i]) > int.Parse(ys[i]))
                {
                    return 1;
                }
                else if(int.Parse(xs[i]) < int.Parse(ys[i]))
                {
                    return -1;
                }
            }

            if(xs.Length > ys.Length)
            {
                return 1;
            }
            else if(xs.Length < ys.Length)
            {
                return -1;
            }

            return 0;
        }
    }
}
Felipe Pessoto
  • 6,855
  • 10
  • 42
  • 73
  • I tested this solution first, and for some reason its running in massive processing ending with max execution time... Thank you very much for your time, but for me, for some mysterious reason it doesn't work very well. :| – Harry Jan 23 '12 at 13:00
  • I didn´t load tests, just for curiosity, how many itens do you have in your collection? – Felipe Pessoto Jan 23 '12 at 13:02
  • There are about 1500 distinct items, 1700 or something in total :) @fujiy – Harry Jan 23 '12 at 13:07
1

As a small LINQ one-liner:

List<string> chapters= new List<string>()
{
    "14.1",
    "14.4.2",
    "14.7.8.8.2",
    "14.10",
    "14.2"
};

chapters.OrderBy(c => Regex.Replace(c, "[0-9]+", match => match.Value.PadLeft(10, '0')));

Independent of levels but surely not the best performance...

Credits are going to https://stackoverflow.com/a/5093939/226278

Community
  • 1
  • 1
sc911
  • 1,130
  • 10
  • 18
1
var headers = new List<string> {"14.1.2.3", "14.1", "14.9", "14.2.1", "14.4.2", "14.10.1.2.3.4", "14.7.8.8.2"};
    headers.Sort(new MySorter());



class MySorter : IComparer<string>
    {
    public int Compare(string x, string y)
    {
    IList<string> a = x.Split('.');
    IList<string> b = y.Split('.');
    int numToCompare = (a.Count < b.Count) ? a.Count : b.Count;
    for (int i = 0; i < numToCompare; i++)
    {
    if (a[i].Equals(b[i]))
    continue;
    int numa = Convert.ToInt32(a[i]);
    int numb = Convert.ToInt32(b[i]);
     return numa.CompareTo(numb);
    }
    return a.Count.CompareTo(b.Count);
    }

    }
chandmk
  • 3,496
  • 3
  • 21
  • 26
1

Using IComparer hast the big disadvantage of repeating the rather expensive calculation very often, so I thought precalculating an order criterium would be a good idea:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ChapterSort
{
    class Program
    {
        static void Main(string[] args)
        {
            String[] chapters=new String[] {"14.1","14.4.2","14.7.8.8.2","14.10","14.2","14.9","14.10.1.2.3.4","14.1.2.3" };  

            IEnumerable<String> newchapters=chapters.OrderBy(x => new ChapterNumerizer(x,256,8).NumericValue);

            foreach (String s in newchapters) Console.WriteLine(s);

        }
    }

    public class ChapterNumerizer
    {
        private long numval;

        public long NumericValue {get{return numval;}}

        public ChapterNumerizer (string chapter,int n, int m)
        {
            string[] c = chapter.Split('.');
            numval=0;
            int j=0;

           foreach (String cc in c)
           {
               numval=n*numval+int.Parse(cc);
               j++;
           }
           while (j<m)
           {
               numval*=n;
               j++;
           }
        }
    }
}
Eugen Rieck
  • 64,175
  • 10
  • 70
  • 92
1

This solution is more general.

public class SequenceComparer<T> : IComparer<IEnumerable<T>> where T : IComparable<T>
{
    public int Compare(IEnumerable<T> x, IEnumerable<T> y)
    {
        IEnumerator<T> enx = x.GetEnumerator();
        IEnumerator<T> eny = y.GetEnumerator();

        do
        {
            bool endx = enx.MoveNext();
            bool endy = eny.MoveNext();

            if (!endx && !endy)
                return 0;

            if (!endx)
                return -1;

            if (!endy)
                return 1;

            var comp = enx.Current.CompareTo(eny.Current);
            if(comp != 0)
                return comp;
        } while (true);
    }
}

Then use:

var sv = vers.Select(v => new { Key = v, Split = v.Split('.').Select(Int32.Parse) });
var ordered = sv.OrderBy(x => x.Split, new SequenceComparer<int>()).Select(x => x.Key);
Petr Behenský
  • 620
  • 1
  • 6
  • 17