0

I have an array that stores the order that I want to sort a list by.

SortOrderArray: "Color", "Volume", "Weight"

So I want to order my list by Color, Volume, then Weight

MyList.OrderBy(a=>a.Color).ThenBy(a=>a.Volume).ThenBy(a=>a.Weight).ToList();

So that's pretty good. Now, I want to be able to write a function that does this sorting based on the sortOrder array I send in:

public List<row> GetSortedList(List<row> list, string[] sortOrder){
    ???
}

I can't figure out how to do this without writing a linq query for every combination of sortOrders (27 different queries just seems like the worst way to accomplish this, and has a fairly high possibility of me making a tiny mistake). I would like to be able to just write 3 linq queries that reorders the list according to each of the 3 sorting methods, something like this:

switch(sortOrder[0]){
    Sort by the first sort method
}  
switch(sortOrder[1]){
    Sort by the second sort method
}
switch(sortOrder[2]){
    Sort by the third sort method
}

But if I try doing the above code, it just resorts it each time, instead of doing sub-sorts after the one above it. Hope that is clear, any help would be appreciated.

sooprise
  • 22,657
  • 67
  • 188
  • 276

5 Answers5

2

Two things. You need a sort method that performs a "stable sort" - it keeps the existing order of items with identical keys. And then you need to call it in reverse order of your sort criteria , so that the primary sort is the last one you do.

AShelly
  • 34,686
  • 15
  • 91
  • 152
  • This is a clever solution, do you know if Linq's order by is "Stable"? Also, what is the correct term for a "Stable Sort" (or is that just it) – sooprise Aug 18 '11 at 15:07
  • 1
    It appears that 'OrderBy' is stable: see [this question](http://stackoverflow.com/questions/148074/is-the-sorting-algorithm-used-by-nets-array-sort-method-a-stable-algorithm). And ["Stable Sort"](http://en.wikipedia.org/wiki/Sorting_algorithm#Stability) is the common term. – AShelly Aug 18 '11 at 19:56
0

I guess, you are not using the return value of the order clauses.

public List<row> GetSortedList(List<row> list, string[] sortOrder)
{
    IOrderedEnumerable<row> result = null;

    bool first = true;

    foreach(sortClause in sortOrder)
    {
        switch sortClause
        {
            case "Color":
                if(first)
                    result = list.OrderBy(x => x.Color);
                else
                    result = result.ThenBy(x => x.Color);
                break;
            // the other cases 
        }
        first = false;
    }    

    return result.ToList();
}

Something like that.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • Looks like I can only put a .ThenBy after a .OrderBy, this code wasn't working for me unfortunately... – sooprise Aug 18 '11 at 15:00
  • @sooprise: Then you introduced an error somewhere. This works. The whole point of `first` is the fact, that you first need an OrderBy and only then can use ThenBy. Please update your question with the exact code you used. – Daniel Hilgarth Aug 18 '11 at 15:06
  • I have the exact same code as you, but I'm using a for instead of foreach and checking if i==0 instead of if first==true. – sooprise Aug 18 '11 at 15:14
  • @sooprise: I fixed the code, because it didn't even compile. Now it should work correctly. Please note: `OrderBy` needs to be called on `list` and all `ThenBy`s need to be called on `result`. I tested it and it works as expected. You can paste [this](http://pastebin.com/YMBB8UeD) code into LINQPad to verify it. – Daniel Hilgarth Aug 19 '11 at 07:58
0

If you have a limited number of possible fields to sort by, a switch might be the best solution. If you're looking for something that scales, you'll have to generate a lambda expression on the fly, and use reflection to call the appropriately-typed .OrderBy and .ThenBy methods.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
0

Using Dynamic LINQ, you can do something like:

public List<row> GetSortedList(List<row> list, string[] sortOrder)
{
    // argument-validation, including testing that 
    // sort-order has at least 1 item.

    return sortOrder.Skip(1)
                    .Aggregate(list.AsQueryable().OrderBy(sortOrder.First()),
                               (query, nextSortTerm) => query.ThenBy(nextSortTerm))
                    .ToList();     
}

Essentially: OrderBy the first sort-term, ThenBy the remaining.

EDIT: Added an AsQueryable call to make Dynamic LINQ work on IEnumerable<T>

Ani
  • 111,048
  • 26
  • 262
  • 307
0

Have a look at this post from Scott Guthrie: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Strillo
  • 2,952
  • 13
  • 15