Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting chapter stuff like 14.1.2.3 and 14.10.1.2.3.4

Tags:

c#

sorting

linq

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?

like image 329
Harry Avatar asked Jan 23 '12 12:01

Harry


5 Answers

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();
    }
  }
}
like image 94
Jon Hanna Avatar answered Nov 15 '22 06:11

Jon Hanna


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;
        }
    }
}
like image 45
Felipe Pessoto Avatar answered Nov 15 '22 07:11

Felipe Pessoto


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

    }
like image 1
chandmk Avatar answered Nov 15 '22 07:11

chandmk


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++;
           }
        }
    }
}
like image 1
Eugen Rieck Avatar answered Nov 15 '22 08:11

Eugen Rieck


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);
like image 1
Petr Behenský Avatar answered Nov 15 '22 06:11

Petr Behenský