18
Mar

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:

[codesyntax lang=”sql”]

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

[/codesyntax]

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.

[codesyntax lang="csharp"]
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;
});
[/codesyntax]

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:

[codesyntax lang="csharp"]
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;
});
[/codesyntax]

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:

[codesyntax lang="csharp"]
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]);
}
[/codesyntax]

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.

  • R.B

    Dude, you are cool!

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

  • http://www.destiny-denied.co.uk/ Daniel Skinner

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

  • http://codery.blogspot.com/ Dave Gruska

    This is brilliant. Thank you!

  • http://www.supersum.com Supersum

    Nice code, it worked like needed

  • orel zion

    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;
    });

    • http://www.destiny-denied.co.uk/ Daniel Skinner

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

  • http://matchartists.com/index.php?do=/profile-9667 pod zastaw

    Excellent article. I’m going through a few of these issues as well..