tags:

views:

203

answers:

4

How can I perform a sort on two different criteria?

For example, I have person objects like:

Person with properties FirstName (string), LastName, and Rank (int).

Example data like so:

Xavier    Smith 1
Alexander Smith 2
Alexander Smith 1
Bob       Hawke 2

It should sort on FirstName alphabetically, then on rank, e.g. resulting:

Alexander Smith 1
Alexander Smith 2
Bob       Hawke 2
Xavier    Smith 1

So far, I have tried the following, but it isn't working properly:

peopleList is List<Person>

peopleList.Sort(new Comparison<Person>((x,y) => x.Rank.CompareTo(y.Rank)));
peopleList.Sort(new Comparison<Person>((x, y) => string.Compare(x.Name, y.Name)));

Thanks

edit: to avoid changing my code too much, I really want to keep list, if I change the above lines to:

peopleList.OrderBy(person => person.FirstName).ThenBy(person => person.Rank).ToList();

Would give the exact same list just sorted properly, correct?

+7  A: 

LINQ Approach

With LINQ you can use OrderBy and ThenBy:

var result = peopleList.OrderBy(p => p.FirstName).ThenBy(p => p.Rank);

This will return an IEnumerable<T>. If you really need a List<T> add a .ToList() at the end.

If you want to use the Sort method then you'll need to write a custom comparer.

EDIT: using ToList() returns a new list. If you want to sort your existing list then you should use the Sort method which does not return a list but rather operates on the current list (it is a void method).

Sort / Comparer Approach

Use: list.Sort(new PersonComparer());

Here's the comparer code. It was adapted from the MSDN example so I recommend reading the comments they used to understand why it is structured this way.

public class PersonComparer : IComparer<Person>
{
    public int Compare(Person x, Person y)
    {
        if (x == null)
        {
            if (y == null)
            {
                return 0;
            }
            else
            {
                return -1;
            }
        }
        else
        {
            if (y == null)
            {
                return 1;
            }
            else
            {
                int retval = x.FirstName.CompareTo(y.FirstName);

                if (retval != 0)
                {
                    return retval;
                }
                else
                {
                    return x.Rank.CompareTo(y.Rank);
                }
            }
        }
    }
}
Ahmad Mageed
I really must read much more on LINQ, it seems to be making my life so much easier!
baron
@baron see my edit, I commented on your question in your recent edit.
Ahmad Mageed
@baron I added a comparer that can be used with `Sort()`.
Ahmad Mageed
Thank you for your thorough answer Ahmad
baron
@baron you're welcome :) There are some other good answers here. The idea of CompareTo is to see whether the items are equal, greater or smaller then sort based on the integer return value. The comparer can certainly be reduced in size with some ternary operators used in return statements or lambdas as shown by others. If you intend to use this type of comparison in many places it helps to have a class, otherwise rare comparisons can be done in place without a new class.
Ahmad Mageed
+1  A: 

I like the LINQ answer. If that's not an option, you could always use

(x,y) => 2*string.Compare(x.Name,y.Name) + x.Rank.CompareTo(y.Rank)

so that the string compare always dominates unless it equals 0

Jimmy
+3  A: 

You were actually really close with the in-place sorting lambda syntax. You're just missing the fact that lambdas can be enclosed in their own scope:

peopleList.Sort(new Comparison<Person>((x,y) =>
{
    int result = x.FirstName.CompareTo(y.FirstName);
    return (result != 0) ? result : x.Rank.CompareTo(y.Rank);
}));

It's a little less effort than writing your own IComparer<Person>!

Aaronaught
ahhhh.... Thank you! Knowing that will help with a lot of my existing code : )
baron
@Aaronaught very nice, but you need to flip the comparison. Compare FirstName first, then in the 2nd part of the ternary compare Rank.
Ahmad Mageed
@Ahmad: Whoops, helps to read the question eh. Fixed that.
Aaronaught
+4  A: 

The other answers seem to be more elegant than this and they make me feel like more of a noob, however if you understand how to sort like this, you can sort any kind of list in any way without knowing much of anything. And there's no need to write a whole new class (though writing a comparer class can be useful if you sort other similar lists in other parts of your code).

peopleList.Sort((x, y) =>
    {
        int compare = x.FirstName.CompareTo(y.FirstName);
        if (compare != 0)
            return compare;

        compare = x.Rank.CompareTo(y.Rank);
        if (compare != 0)
            return compare;

        return x.LastName.CompareTo(y.LastName);
    });
Phil