Custom sorting order in LINQ (ORDER BY WEIGHTING)

I have developed a C# LINQ extension method to allow very flexible ordering by assigning weightings to the sort keys of the elements to be ordered. I was inspired by the functionality offered by SQL Server’s ORDER BY CASE WHEN:

ORDER BY
CASE SEASON
WHEN 'WINTER' THEN 1
WHEN 'SPRING' THEN 2
WHEN 'SUMMER' THEN 3
WHEN 'AUTUMN' THEN 4
END

The extension method I have created lets you pass a lambda function which allows the use of logic to apply custom weightings to the sort keys.

var data = dt.Select(g => new
{
    Season = g.season,
    AverageTemp = g.temp
}).OrderByWeight(a => a.Season, x =>
{
    if (x == "WINTER") return 1;
    if (x == "SPRING") return 2;
    if (x == "SUMMER") return 3;
    if (x == "AUTUMN") return 4;
    return 99;
});

The above will return an IOrderedEnumerable sorted by Season in the order WINTER, SPRING, SUMMER, AUTUMN. However, the OrderByWeight extension method provides even more powerful functionality as the lambda allows us to perform string comparisons and more:

var data = dt.Select(g => new
{
    Year = g.year,
}).OrderByWeight(a => a.Year, x =>
{
    if (x.Contains("2008")) return 1;
    if (x.Contains("2009")) return 2;
    if (x.Contains("2010") return 3;
    return 99;
});

Using the above, a non-trivial list such as {‘London 2010′, ‘Leeds 2008′, ‘Cardiff 2009′, ‘Glasgow 2011′} can be easily sorted to {‘Leeds 2008′, ‘Cardiff 2009′, ‘London 2010′, ‘Glasgow 2011′}

The extension method used to implement this functionality is:

public static IOrderedEnumerable<TSource> OrderByWeight<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, int> weighting) where TKey : IComparable
{
    Dictionary<TSource, int> order = new Dictionary<TSource, int>();
    foreach (TSource item in source)
    {
        if (!order.ContainsKey(item)) order.Add(item, weighting(keySelector(item)));
    }
    return source.OrderBy(s => order[s]);
}

It has not been throughly unit tested yet but it should have the same behaviour as GroupBy. Whilst I have only provided the C# implementation I’m sure it can be converted to other languages using the CLR.

This entry was posted in General and tagged , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

6 Comments

  1. R.B
    Posted April 28, 2009 at 5:49 pm | Permalink

    Dude, you are cool!

    I just used this in my code, and it works really nice.
    Very nice.

  2. Posted April 28, 2009 at 6:06 pm | Permalink

    I’m glad it was useful. I have used it many times since initially writing it.

  3. Posted October 2, 2009 at 7:15 pm | Permalink

    This is brilliant. Thank you!

  4. Posted October 30, 2009 at 12:32 am | Permalink

    Nice code, it worked like needed

  5. orel zion
    Posted March 17, 2010 at 12:31 pm | Permalink

    Hi!
    Great code.
    Is it possible to use the function as a thenBy too?
    Like for example
    var data = dt.Select(g => new
    {
    Year = g.year,
    }).OrderBy(x=>x.Month).ThenByWeight(a => a.Year, x =>
    {
    if (x.Contains(“2008″)) return 1;
    if (x.Contains(“2009″)) return 2;
    if (x.Contains(“2010″) return 3;
    return 99;
    });

    • Posted April 5, 2010 at 9:12 am | Permalink

      Yes, this should work since OrderByWeight is returning an IOrderedEnumerable. You would need to implement the ThenByWeight extension method.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>