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?
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();
}
}
}
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;
}
}
}
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);
}
}
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++;
}
}
}
}
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With