7

I have a list of objects that have a property Rank. This is an integer.

I want to sort by rank on my view but when i do this:

  myObjects = myObjects.Orderby(r=>r.Rank);

i get all of the zeros (meaning these haven't been set at the top)

I want to order by 1 --> n but have the zeros be at the bottom of the list.

I would like it to be as efficient a sort as possible as the list is quite long

svick
  • 236,525
  • 50
  • 385
  • 514
leora
  • 188,729
  • 360
  • 878
  • 1,366

3 Answers3

20

LINQ:

myObjects = myObjects
    .OrderBy(r => r.Rank == 0) //false before true
    .ThenBy(r => r.Rank);

This won't actually do two full sorts. It will combine the two lambdas into a single dictionary sort across the two keys.

If you're not comfortable with the not-so-obvious false-before-true rule, you can replace the first lambda with r => r.Rank == 0 ? 1 : 0 - but, knowing the false-before-true rule makes this seem really redundant.

Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • is there any better way without having to do two full sorts? – leora May 22 '13 at 22:00
  • @leora: See http://msmvps.com/blogs/jon_skeet/archive/2011/01/04/reimplementing-linq-to-objects-part-26a-iorderedenumerable.aspx (and the subsequent posts) for some more details – Jon Skeet May 22 '13 at 22:03
  • 2
    @leora: This doesn't do two full sorts. "OrderBy/ThenBy" is pretty smart. Remember, the result of a query expression is *a query*, not *the computation of the results of the query*. When the query is executed, the query knows whether there's a ThenBy after the OrderBy. – Eric Lippert May 22 '13 at 22:04
  • 1
    _"knowing the false-before-true rule"_ you could also use `OrderByDescending(r => r.Rank != 0)` instead. – Tim Schmelter May 22 '13 at 22:09
  • @TimSchmelter How would that eliminate the need to *either* (A) "know the `false`-before-`true` rule" *or* (B) hide it by using something like a ternary operator (as in my example)? That lambda maps from `int` to `bool`, so it's still using the `false`-before-`true` rule. – Timothy Shields May 22 '13 at 22:12
  • @TimothyShields: It does not _eliminate_ the need to know it but it allows to keep your logic. So if you are interested in ranks that are different than zero first, you can use `OrderByDescending(r => r.Rank != 0)` instead of reversing your logic. Just a matter of taste. – Tim Schmelter May 22 '13 at 22:15
  • @TimSchmelter Oooh - I'm sorry, I misread your comment. You were just offering an alternative that you thought was more intuitive. Gotcha. :) – Timothy Shields May 22 '13 at 22:16
6

You can create a custom comparer (implementing IComparer) and have it sort zeroes to the bottom. The pseudo code would be:

public class ZeroComparer : IComparer {
    public int Compare(Object intA, Object intB) {
        if(intA == 0 && intB != 0)
            return -1;
        if(intA != 0 && intB == 0)
            return 1;
        return int.Compare(intA, intB);
    }
}

Then use it like:

var comparer = new ZeroComparer();
myObjects = myObjects.Orderby(r=>r.Rank, comparer);

A quick example of how to use custom comparers:

Use own IComparer<T> with Linq OrderBy

Community
  • 1
  • 1
ean5533
  • 8,884
  • 3
  • 40
  • 64
1
myObjects = myObjects.Orderby(r => r.Rank == 0 ? int.MaxValue : r.Rank);

to deal with the case Rank == int.MaxValue :

myObjects = myObjects.Orderby(r => r.Rank == 0 ? int.MaxValue : r.Rank - 1);
polkduran
  • 2,533
  • 24
  • 34