Wednesday, April 29, 2009

IComparer Proxy to Sort by Multiple Conditions

IComparerI thought of another cool use for implementing IComparer.

If you've ever wanted to sort a list of objects using more than one comparer (i.e., you want to sort a list of people by last name and then by first name), then you probably had to compose your own IComparer object to do so. If you wanted to be able to dynamically change sort orders, then your task was more difficult.

To address this problem, I wrote a class called ComparerProxy which implements IComparer and proxies comparisons through a list of IComparers. First, I'll show you the class and then I'll explain a few nuances.
public class ComparerProxy<T> : IComparer<T>
{
private readonly IComparer<T>[] _comparers;

public ComparerProxy(params IComparer<T>[] comparers)
{
_comparers = comparers;
}

public int Compare(T x, T y)
{
int retVal = 0, i = 0;

while (retVal == 0 && i < _comparers.Length)
retVal = _comparers[i++].Compare(x, y);

return retVal;
}
}
So, what's happening here is that when ComparerProxy.Compare() is called, it loops through the collection of comparers until it either runs out of comparers and deems the two objects equal or it identifies a difference and returns the value of that difference.

That sounds confusing even to me and I wrote the class so I'll break it down. Basically, imagine you have a Person object (and soon we will). The person has a FirstName property and a LastName property. If you want to sort by LastName and then by FirstName, it would go something like this:
  1. Sort the two objects by LastName
  2. If the two objects have the same LastName, then sort by FirstName
  3. If the two objects have the same FirstName, then the two objects are equal
If that doesn't make sense, please comment and I'll elaborate.

So, shall we see what it does? I created a Person class (very similar do the one described above) just to do some testing. I overrode the ToString method to print "Last, First" and I added two IComparers. Here's what that class looks like:
private class Person
{
public string First, Last;

public class FirstNameComparer : IComparer<Person>
{
public int Compare(Person x, Person y)
{
return x.First.CompareTo(y.First);
}
}

public class LastNameComparer : IComparer<Person>
{
public int Compare(Person x, Person y)
{
return x.Last.CompareTo(y.Last);
}
}

public override string ToString()
{
return string.Format("{0}, {1}", Last, First);
}
}
I created a test list of Persons and a set of IComparers to test with including two complex IComparers using the ComparerProxy:
List<Person> _folks = new List<Person>();
_folks.Add(new Person { First = "Jennifer", Last = "Caldwell" });
_folks.Add(new Person { First = "Cory", Last = "Caldwell" });
_folks.Add(new Person { First = "Lauren", Last = "Woody" });
_folks.Add(new Person { First = "Colin", Last = "Caldwell" });
_folks.Add(new Person { First = "Jamus", Last = "Brechtel" });
_folks.Add(new Person { First = "Pete", Last = "Woody" });
_folks.Add(new Person { First = "Royce Ann", Last = "Woody" });
_folks.Add(new Person { First = "Ashley", Last = "Brechtel" });

IComparer<Person> first = new Person.FirstNameComparer();
IComparer<Person> last = new Person.LastNameComparer();

IComparer<Person> firstLast = new ComparerProxy<Person>(first, last);
IComparer<Person> lastFirst = new ComparerProxy<Person>(last, first);
I executed the following lines and, lo and behold, achieved the expected results:
_folks.Sort(last);
PrintList(_folks);

// Brechtel, Jamus
// Brechtel, Ashley
// Caldwell, Jennifer
// Caldwell, Colin
// Caldwell, Cory
// Woody, Lauren
// Woody, Royce Ann
// Woody, Pete


_folks.Sort(first);
PrintList(_folks);

// Brechtel, Ashley
// Caldwell, Colin
// Caldwell, Cory
// Brechtel, Jamus
// Caldwell, Jennifer
// Woody, Lauren
// Woody, Pete
// Woody, Royce Ann


_folks.Sort(firstLast);
PrintList(_folks);

// this one is the same as just sorting by first name
// in retrospect, I should've added some folks with the same first name
// c'est la vie
//
// Brechtel, Ashley
// Caldwell, Colin
// Caldwell, Cory
// Brechtel, Jamus
// Caldwell, Jennifer
// Woody, Lauren
// Woody, Pete
// Woody, Royce Ann


_folks.Sort(lastFirst);
PrintList(_folks);

// Brechtel, Ashley
// Brechtel, Jamus
// Caldwell, Colin
// Caldwell, Cory
// Caldwell, Jennifer
// Woody, Lauren
// Woody, Pete
// Woody, Royce Ann

No comments:

Post a Comment