Advanced IComparer // Sorting on Multiple Values

I’ve been writing IComparer’s for a while, and could never figure out a "clean" way to write one which handled mutliple values to compare against.
One time I tried stringing together the multi values, but that was deficient fairly quickly, when you went to datatypes outside of strings.
( If you sort the numbers 1-15 with a string, you get : 1,10,11,12,13,14,15,2,3,4,5,6,7,8,9, which is not what you want).  Throw some DateTime’s in the mix, and you see that can get ugly very quickly.
Well, no longer.  The following sample shows how to have your cake and eat it too.
The EmployeeComparer can handle strings, times, ints (or others).  It can handle 1:N number of sort parameters, where N is probably the overall number of properties you have on your object.
And it only does the work needed, aka, it doesn’t run 5 comparing operations when 1 will suffice.
The little trick is to nest some comparisons, in case there is a "tie" ( compare value of 0 ).
The other good news is that this solution is not limited to 1, 2 or 3 levels of comparing values.
The limit is your imagination.
However, the solution does not do unnecessary compares either.
I think the solution is pretty elegant.
The example can sort ASC or DESC.  However, it sorts ASC and DESC for the entire set of SortColumns.
I’m working on my own solution for this.  (Just a wrapper object to hold the SortColumn and the direction).
But I will leave that as an exercise for the reader.

Here is the downloadable example.  (Right Click and "Save Target As" is your best bet).  This is 1.1 code, fyi.

This entry was posted in Software Development. Bookmark the permalink.

5 Responses to Advanced IComparer // Sorting on Multiple Values

  1. Simon says:

    Here\’s a generic class I wrote which does the same sort of thing. You might find it useful in your 2.0 projects:

    public class AnythingComparer<T> : System.Collections.Generic.IComparer<T>{
    /// <summary>/// Sorts/Compares any type of object by any number of Properties/// </summary>/// <param name="property">Property name followed by sort direction. e.g. \’MyProperty DESC\'</param>
    public AnythingComparer(string property){   _properties = new string[] { property };}
    /// <summary>/// Sorts/Compares any type of object by any number of Properties/// </summary>/// <param name="properties">An array of property names followed and sort direction. e.g. {\’MyProperty DESC\’, \’Property2 ASC\’}</param>
    public AnythingComparer(string[] properties){  _properties = properties;}
    private string[] _properties;
    /// <summary>/// An array of property names followed and sort direction. e.g. {\’MyProperty DESC\’, \’Property2 ASC\’}/// </summary>public string[] Properties{  get { return _properties; }  set { _properties = value; }}
    public int Compare(T x, T y){  if (x.GetType() != y.GetType())    throw new ArgumentException("Can\’t compare " + x.GetType() + " with " + y.GetType());
      int res = 0;  for (int i = 0; i < _properties.Length; i++)  {      string[] args = _properties[i].Split(\’ \’);      string prop = args[0];      bool desc = args.Length > 0 && args[1].Equals("DESC", StringComparison.OrdinalIgnoreCase);      object o1 = x.GetType().GetProperty(prop).GetValue(x, null);      object o2 = y.GetType().GetProperty(prop).GetValue(y, null);      if (o1 == null)        throw new NullReferenceException();      if (o2 == null)        throw new NullReferenceException();
          IComparable comp1 = o1 as IComparable;      IComparable comp2 = o2 as IComparable;
          if (comp1 == null)        throw new ArgumentException("Property \’" + prop + "\’ of type " + o1.GetType() + " is not IComparable");      if (comp2 == null)        throw new ArgumentException("Property \’" + prop + "\’ of type " + o2.GetType() + " is not IComparable");      res = comp1.CompareTo(o2) * (desc ? -1 : 1);      if (res != 0)        return res;    }  return res;  }}
    … usage would be something like:
    List<SomeClass> ar = new List<SomeClass>();… populate the array …AnythingComparer<SomeClass> comp = new AnythingComparer<SomeClass>(new string[] { "SomeProp ASC", "SomeBool DESC", "SomeDate ASC", "SomeEnum ASC" });ar.Sort(comp);

  2. dootndo2 says:

    I wanted to let you know that the AnythingComparer is awesome and works very well.  We came across a small issue with property names in different cases (camel case vs upper case).  So I added a check for this and though that i\’d post it back.
     
    Imports SystemImports System.Collections.GenericImports System.Text
    <Serializable()> Public Class AnythingComparer(Of T) Implements IComparer(Of T)
     \’\’\’ <summary>  \’\’\’ Sorts/Compares any type of object by any number of Properties  \’\’\’ </summary>  \’\’\’ <param name="property">Property name followed by sort direction. e.g. \’MyProperty DESC\'</param>
     Public Sub New(ByVal [property] As String)  _properties = New String() {[property]} End Sub
     \’\’\’ <summary>  \’\’\’ Sorts/Compares any type of object by any number of Properties  \’\’\’ </summary>  \’\’\’ <param name="properties">An array of property names followed and sort direction. e.g. {\’MyProperty DESC\’, \’Property2 ASC\’}</param>
     Public Sub New(ByVal properties As String())  _properties = properties End Sub
     Private _properties As String()
     \’\’\’ <summary>  \’\’\’ An array of property names followed and sort direction. e.g. {\’MyProperty DESC\’, \’Property2 ASC\’}  \’\’\’ </summary>  Public Property Properties() As String()  Get   Return _properties  End Get  Set(ByVal value As String())   _properties = value  End Set End Property
     Public Function Compare(ByVal x As T, ByVal y As T) As Integer Implements System.Collections.Generic.IComparer(Of T).Compare  If Not x.GetType() Is y.GetType() Then   Throw New ArgumentException("Cannot compare " & x.GetType().ToString() & " with " & y.GetType().ToString())  End If
      Dim res As Integer = 0  For i As Integer = 0 To _properties.Length – 1   Dim args As String() = _properties(i).Split(" "c)   Dim prop As String = args(0)   Dim desc As Boolean = args.Length > 0 AndAlso args(1).Equals("DESC", StringComparison.OrdinalIgnoreCase)
       \’ Updated here
       \’ get all properties for x/y (should be the same for same object type)   Dim props As System.Reflection.PropertyInfo() = x.GetType().GetProperties()
       Dim j As Integer   For j = 0 To props.Length    If props(j).Name.ToLower() = args(0).ToLower() Then     prop = props(j).Name     Exit For    End If   Next
       \’ End Update
       Dim o1 As Object = x.[GetType]().GetProperty(prop).GetValue(x, Nothing)   Dim o2 As Object = y.[GetType]().GetProperty(prop).GetValue(y, Nothing)
       If o1 Is Nothing Then    Throw New NullReferenceException()   End If   If o2 Is Nothing Then    Throw New NullReferenceException()   End If
       Dim comp1 As IComparable = TryCast(o1, IComparable)   Dim comp2 As IComparable = TryCast(o2, IComparable)
       If comp1 Is Nothing Then    Throw New ArgumentException("Property \’" & prop & "\’ of type " & o1.GetType().ToString() & " is not IComparable")   End If   If comp2 Is Nothing Then    Throw New ArgumentException("Property \’" & prop & "\’ of type " & o2.GetType().ToString() & " is not IComparable")   End If
       res = comp1.CompareTo(o2) * (IIf(desc, -1, 1))
       If res <> 0 Then    Return res
       End If  Next  Return res End Function
    End Class
     
    It would be used the same way as noted above.
     
    Thanks again!

  3. Unknown says:

    wow gold wow gold wow gold wow power leveling wow power leveling wow power leveling wow power leveling World of Warcraft Gold wow gold wow power leveling wow gold wow gold wow gold wow power leveling wow power leveling Rolex Replica rolex Rolex Replica rolex Rolex changyongkuivip

  4. David says:

    Excellent, thank you!!

  5. guy says:

    Thank you very much

Leave a comment