views:

125

answers:

6

Let's say I have a list of objects:

var items = new {
    new { Order = 0 },
    new { Order = 1 },
    new { Order = -1 },
    new { Order = 3 },
    new { Order = 2 },
    new { Order = -1 }
};

I need to order it so that items with "Order > -1" be on top of the list ordered by Order ascending, and remaining items with "Order == -1" were following them.

Is there a more elegant way of doing this than using Conact() + Where() clauses:

var orderedItems = items.Where(x => x.Order > -1).OrderBy(x => x.Order)
                   .Conact(items.Where(x => x.Order == -1);

So that after sorting this list would look like:

var items = new {
    new { Order = 0 },
    new { Order = 1 },
    new { Order = 2 },
    new { Order = 3 },
    new { Order = -1 },
    new { Order = -1 }
};

Also "items" list in actual scenario is already a complex IQueryable object. That's why I am trying to find the most optimal way of doing such selective ordering.

+3  A: 

If you order by ascending, -1 should already be at the top of the list, because it is the smallest value.

However, more generally, if you still wanted to apply different sorting to subsets of the data, I don't think there would be a more elegant way, because that's exactly what you are doing and the union is logically accurate. You're trying to pull two seperate subset of the data out, sort them differently, and then merge them together, which is one of the things what I union is to be used for.

Mike Mooney
A: 

Use a custom comparer to define the order you want.

public class MyComparer : IComparer<int>
{
    public int Compare(int a, int b)
    {
        if ((a < 0) && (b >= 0))
        {
            return 1;
        }
        if ((a >= 0) && (b < 0))
        {
            return -1;
        }
        return int.Compare(a, b);
    }
}

Then you could do:

var orderedItems = items.OrderBy(x => x.Order, new MyComparer());
Craig Stuntz
Upon re-reading the question, it's unclear what to do with other negative numbers at all. This answer should give the general idea, though.
Craig Stuntz
+4  A: 

As Mike mentioned, in your example, it would work automatically, but say we wanted to get all -1 elements first and then sort remaining elements in a descending order. This can be done using a nice trick. You can use multiple keys when ordering elements. The first key can be a Boolean value which will be false for all -1 values (so they will be first) and true for all other values (so they won't be reordered). The second key can be whatever you want to order the remaining elements. For example:

var nums = new int[] { -1, 4, 2, 3, -1, 4, 7 };
var q = from n in nums
        orderby n != -1, n descending
        select n;

It will first yield all values for which n != -1 is false and then all elements ordered using n descending so you'll get:

-1, -1, 7, 4, 4, 3, 2

This works in general when you need to handle some elements especially in the ordering - you just need to provide the right ordering keys.

Tomas Petricek
Looks like exactly what I was looking for. Testing...
Koistya Navin
Very cool, but your explanation confuses me...if != -1 elements are yield-ed first, shouldn't -1 elements be coming at the end, or what exactly is happening here?
flq
This is because of the ascending ordering of Boolean values. `true.CompareTo(false)` gives 1, which indicates that `true > false`, so the ascending ordering is `false, true` (and so `false` elements come first).
Tomas Petricek
Wasn't aware that bool implements IComparable. Really neat one!
flq
A: 
OrderBy(x => x.Order < 0 ? int.MaxValue : x.Order)

or if you need to order the negative values in descending order

OrderBy(x => x.Order < 0 ? (long)int.MaxValue - x.Order : (long)x.Order)
leppie
This would return the negative items unordered. It's unclear from the question if that's what he wants.
Craig Stuntz
Well he did not ask for it :) I'll add simple adaption.
leppie
+3  A: 

You could try this - it produces the result you expect:

items.OrderBy(x.Order => x.Order == -1).ThenBy(x.Order => x.Order);
Leom Burke
+1  A: 

Also a custom comparer here if you want -1 to appear first, but the rest to be descending , but somehow I find it more elegant :) Note it's made for ints

class Comparer : IComparer<int>
{
  public int Compare(int x, int y)
  {
    if (x == -1 || y == -1) return x - y;
    return y - x;
  }
}
flq