Tuesday, January 11, 2005

Customised sorting of an ArrayList (C#)


A handy feature that was included in ArrayList's, is the ability to sort the list according to your own comparison criteria. Unfortunately the documentation is a bit scarce on exactly how to implement this, of which an example wouldn't have gone awry.

The first thing you need to start with is the object to store in your ArrayList, for this example I have put together a simple object call Person (Person.cs). This object one contains two member variables, Name and Age.

using System;

namespace SortArrayListCS {

public class Person {
public string Name;
public int Age;

public Person() {
}

public Person(string name, int age) {
this.Name = name;
this.Age = age;
}
}
}

Now we need to implement the infamous IComparer interface to define how we want to sort this. Whilst I have made this a little more elaborate than it need be, I wanted to show how you can further define how you want to sort this collection.

Our comparer is called PersonComparer (PersonComparer.cs), and we also have a enum called SortDirection which allows us to define whether we want to sort Ascending or Descending.

using System;
using System.Collections;

namespace SortArrayListCS {

public enum SortDirection {
Ascending,
Descending
}

public class PersonComparer : IComparer {

private SortDirection m_direction = SortDirection.Ascending;

public PersonComparer() : base() { }

public PersonComparer(SortDirection direction) {
this.m_direction = direction;
}

int IComparer.Compare(object x, object y) {

Person personX = (Person) x;
Person personY = (Person) y;

if (personX == null && personY == null) {
return 0;
} else if (personX == null && personY != null) {
return (this.m_direction == SortDirection.Ascending) ? -1 : 1;
} else if (personX != null && personY == null) {
return (this.m_direction == SortDirection.Ascending) ? 1 : -1;
} else {
return (this.m_direction == SortDirection.Ascending) ?
personX.Age.CompareTo(personY.Age) :
personY.Age.CompareTo(personX.Age);
}
}
}
}

If you wanted to sort an ArrayList of mismatched objects which have a common field, you would design an interface that each of those objects implement, then change the comparer to cast these objects as their interface.

Finally we need to test this to make sure everything is working as it seems. I knocked this together as a Console Project (C#) just to be quick and simple, so our test file is SortArrayList.cs.

using System;
using System.Collections;

namespace SortArrayListCS {

class SortArrayList {

[STAThread]
static void Main(string[] args) {

ArrayList list = new ArrayList();
list.Add(new Person("John", 50));
list.Add(new Person("Mary", 28));
list.Add(new Person("William", 12));
list.Add(new Person("Marcia", 17));
list.Add(new Person("Rodrick", 36));

list.Sort(new PersonComparer());

Console.WriteLine("Sort Ascending");
foreach (Person person in list) {
Console.WriteLine("Name: " + person.Name + ", Age: " + person.Age.ToString());
}

list.Sort(new PersonComparer(SortDirection.Descending));

Console.WriteLine("\n\rSort Descending");
foreach (Person person in list) {
Console.WriteLine("Name: " + person.Name + ", Age: " + person.Age.ToString());
}

Console.ReadLine();
}

}
}

If all of this worked as planned you should see the following output:

Sort Ascending
Name: William, Age: 12
Name: Marcia, Age: 17
Name: Mary, Age: 28
Name: Rodrick, Age: 36
Name: John, Age: 50

Sort Descending
Name: John, Age: 50
Name: Rodrick, Age: 36
Name: Mary, Age: 28
Name: Marcia, Age: 17
Name: William, Age: 12




===============================================================================
Maybe it wouldn't a better choice to avoid overhead/exceptions from trying to cast null's be:

int IComparer.Compare(object x, object y) {

if (x == null && y == null) return 0;
if (x == null && y != null)
return (this.m_direction == SortDirection.Ascending) ? -1 : 1;
if (x != null && y == null)
return (this.m_direction == SortDirection.Ascending) ? 1 : -1;

Person personX = (Person) x;
Person personY = (Person) y;

return (this.m_direction == SortDirection.Ascending) ?
personX.Age.CompareTo(personY.Age) :
personY.Age.CompareTo(personX.Age);
}
}
}
}

1 comment:

Tomas Thalmann said...

if you dont need different sort criterias it might be easiear (and quicker to implement) to use ICompareable. Your Person Class should implement CompareTo(), then you'll be able to use Sort() without any parameters.

but IComparer is a powerful, customizable way to do sorting, but in some cases it would be more time efficient to use the easiest way ;-) - i'm a minimalist...

but greate resource for implementing IComparer

thx

Shared Cache - .Net Caching made easy

All information about Shared Cache is available here: http://www.sharedcache.com/. Its free and easy to use, we provide all sources at codeplex.

Facebook Badge