Implementing custom IComparer<> (with example)

maxp picture maxp · Feb 5, 2013 · Viewed 13.1k times · Source

Ive just written the following code, which will order strings by their native string.Compare() but allow a collection of exceptions (in this case customPriority) that will place priority over the default string.Compare() function.

It all seems a bit long winded, I was wondering if there was something built into .NET to allow this?

    var unorderered = new[] { "a", "b", "c", "x", "y", "z" };
    var ordered = unorderered.OrderBy(a => a, new CustomStringComparer());
    //expected order y,x,a,b,c,z

class CustomStringComparer : IComparer<string>
{
    int IComparer<string>.Compare(string x, string y)
    {
        if (x == y)
            return 0;
        else
        {
            //----------------------------
            //beginning of custom ordering
            var customPriority = new[] { "y", "x" };
            if (customPriority.Any(a => a == x) && customPriority.Any(a => a == y)) //both in custom ordered array
            {
                if (Array.IndexOf(customPriority, x) < Array.IndexOf(customPriority, y))
                    return -1;                   
                return 1;
            }
            else if (customPriority.Any(a => a == x)) //only one item in custom ordered array (and its x)                    
                return -1;
            else if (customPriority.Any(a => a == y)) //only one item in custom ordered array (and its y)                    
                return 1;
            //---------------------------
            //degrade to default ordering
            else
                return string.Compare(x, y);

        }
    }
}

Answer

svick picture svick · Feb 5, 2013

First, I think it's useful to restate the problem: You want to sort by:

  1. the index in the given array; if the item is not in the array, the index is infinity
  2. the string itself

That means you can achieve your sort order by using OrderBy() for the first condition followed by ThenBy() for the second one:

private static uint NegativeToMaxValue(int i)
{
    if (i < 0)
        return uint.MaxValue;
    return (uint)i;
}

…

var ordered = unorderered
    .OrderBy(a => NegativeToMaxValue(Array.IndexOf(new[] { "y", "x" }, a)))
    .ThenBy(a => a);

NegativeToMaxValue() is necessary, because items not in the array should be last, but they would be first normally, because the index is -1. (A hackish and unreadable way to do the same would be to directly cast the result of IndexOf() to uint.)

If you wanted to reuse this sorting by creating an IComparer, I believe there is nothing in .Net to help you with that. But you could use ComparerExtensions instead:

IComparer<string> comparer = KeyComparer<string>
    .OrderBy(a => NegativeToMaxValue(Array.IndexOf(new[] { "y", "x" }, a)))
    .ThenBy(a => a);