views:

91

answers:

3

Suppose I have a list of items (e.g., Posts) and I want to find the first item according to some non-trivial ordering (e.g., PublishDate and then CommentsCount as a tie-breaker). The natural way to do this with LINQ is like this:

posts.OrderBy(post => post.PublishDate).ThenBy(post => post.CommentsCount).First()

However, the micro-optimizer in me is worried that calling OrderBy actually costs me O(n*lgn) for sorting the entire list, when all I really need is an O(n) find-minimum operation.

So, is LINQ smart enough to return something from OrderBy() that knows how to optimize subsequent First() calls? If not, what's a better way to do this out-of-the-box? (I can always write my own FindMinimumItem implementation but that seems like overkill).

A: 

Usually that is a max or min (I don't know the how is it called in LinQ), given a specific key; sorting and getting the first or last seems overkill in any language or framework.

fortran
The methods which take keys in LINQ unfortunately return the maximum/minimum key itself rather than the object containing that key.
Jon Skeet
cannot you make a composed key then? In python it's very typical to arrange a temporary tuple that contains the key and the whole record, since they are compared lexicographically.
fortran
@fortran: Although .NET 4.0 has a Tuple type, .NET 3.5 doesn't. It's possible that this approach would work in .NET 4.0, but I'm not sure it's very idiomatic.
Jon Skeet
Well theoretically I could use anonymous objects as tuples, but then we're back at square one since I can only compare the key property and get the Min or Max of that.
Avish
Also, I doubt tuples in .NET 4 compare lexicographically (member-wise); that seems pretty pythonic ("let's do the reasonable thing by default") but not very C#-ish ("let's not do anything by default and force the code to be explicit and somewhat more readable").
Avish
+2  A: 

Is this in SQL or LINQ to Objects? If it's the latter, you probably want MinBy from MoreLINQ; your statement as written will indeed sort and then take the first item.

And yes, it's a shame that it doesn't include this (and similar things like DistinctBy) out of the box.

EDIT: I see your question has now changed; MoreLINQ doesn't support a compound comparison like that. In MiscUtil I have code to create a compound IComparer<T> - you could pass that into MinBy using the identity function as the key selector. Feel free to add a feature request for a MinBy which takes a source and an IComparer<T> without a key selector :)

Jon Skeet
Thanks, I'll take a look at these. It's really a shame that there's no out-of-the-box way to convert between sets of key expressions (e.g. as used with OrderBy...ThenBy) and an IComparer.
Avish
An alternative to `ThenBy` could be returning a tuple: `Tuple.Create(post.PublishData, post.CommentsCount)`.
Tomas Petricek
+2  A: 

The sorting is smart in the way that it will only do the ThenBy on the first group from the OrderBy, but the OrderBy still has to sort all items before it can return the first group.

You can use the Aggregate method to get the first post according to a custom comparison:

Post lowest =
  posts.Aggregate((Post)null,
    (x, y) =>
      x == null
      || y.PublishDate < x.PublishDate
      || (y.PublishDate == x.PublishDate && y.CommentsCount < x.CommentsCount)
      ? y : x
  );

(Assuming that you are using LINQ to Objects of course.)

Guffa
Thanks, that's a good solution albeit a little verbose.
Avish