5

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() and 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<T> object. That's why I am trying to find the most optimal way of doing such selective ordering.

double-beep
  • 5,031
  • 17
  • 33
  • 41
Konstantin Tarkus
  • 37,618
  • 14
  • 135
  • 121

6 Answers6

7

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

items.OrderBy(x.Order => x.Order == -1).ThenBy(x.Order => x.Order);
Leom Burke
  • 8,143
  • 1
  • 35
  • 30
6

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
  • 240,744
  • 19
  • 378
  • 553
  • 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 Feb 17 '10 at 15:46
  • 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 Feb 17 '10 at 16:25
  • Wasn't aware that bool implements IComparable. Really neat one! – flq Feb 17 '10 at 18:16
3

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
  • 11,729
  • 3
  • 36
  • 42
1
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
  • 115,091
  • 17
  • 196
  • 297
1

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
  • 22,247
  • 8
  • 55
  • 77
0

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());
Community
  • 1
  • 1
Craig Stuntz
  • 125,891
  • 12
  • 252
  • 273
  • 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 Feb 17 '10 at 15:44