Inclusively Take Elements using LINQ and Custom Extensions

| 2 Comments | No TrackBacks

I ran into an interesting LINQ problem recently, one which required me to extend LINQ in my own fashion. The problem was fairly simple. I’m trying to build a list of every day a course meets over the course of a term. I start by querying our Academic Calendar for every event in the calendar, then I skip all the events prior to the first day of class, taking all the events until the end of classes for the term. I couldn’t just take those two events, because different events in the middle of the term affect if courses are offered. My first draft at this problem was simple:

IEnumerable<AcademicEvent> _events = GetAcademicCalendarEvents
                            (data.Year, data.Term, data.Campus)
                .SkipWhile(c => c.EventType != "TermBegins")
                .TakeWhile(c => c.EventKey != "TermEnds");

This code is broken. But the error is subtle. The TakeWhile method is not inclusive. The resulting IEnumerable will not include the event with the Key “TermEnds”, resulting in my later code inadvertently not showing some days at the end of the term. Given that the event that follows the TermEnds event may not always be the same, I didn’t really want to try to key off of the next possible event. What I really wanted to do was take items on the list until I reached the TermEnds event. Sounds like an easy enough extension method!

public static IEnumerable<T> TakeUntil<T>
    (this IEnumerable<T> data, Func<T, bool> predicate)
{
    return data.TakeWhile(c => !predicate(c));
}

And since the messing around with the IEnumerable has already been done for me, I might as well take advantage of that work, and just reverse the predicate as I send it into the existing TakeWhile. Of course, this doesn’t work either. Because TakeWhile (and by extension, TakeUntil) will not return the item which triggered the predicate, which I want. So, another extension method is required, TakeUntilInclusive.

public static IEnumerable<T> TakeUntilInclusive
        (this IEnumerable<T> data, Func<T, bool> predicate)
{
    int index = 0;
    var enumerator = data.GetEnumerator();
    while (enumerator.MoveNext())
    {
        index++;
        if (predicate(enumerator.Current)) { break; }
    }
    enumerator.Dispose();
    return data.Take(index);
}

And while I’m at it…

public static IEnumerable<T> TakeWhileInclusive<T>
    (this IEnumerable<T> data, Func<T, bool> predicate)
{
    return data.TakeUntilInclusive(p => !predicate(p));
}

I’ve metioned extension methods before, but I can honestly say that they are the single best reason for using C# 3, and .NET 3/3.5. They’re that good. Essentially, extension methods bring limited monkey-patching to the static compilation world, but do so in a really selective way. All the code above would be wrapped in a static class in a Namespace that would allow me to select when those methods were available or not. The only weakness to extension methods is that they do not allow you to override methods that already exist with new behavior. Really, it’s just a compiler hack, which is why it’s a language feature and not a runtime feature, but it’s a really good compiler hack.

Breaking down the syntax is easy. Any static method, located in a static class, with a first argument prefixed with the ‘this’ keyword, will be interpreted by the compiler as an extension method, and the compiler will allow the method to be called as object.Method(), instead of StaticClass.Method(object). Essentially, those two calls are functionally identical, but the extension method provides code that is cleaner and easier to read. However, it’s real power is when paired with interfaces and generics, as with LINQ, which my code above just adds on to.

The final version of the query looks like this:

IEnumerable<AcademicEvent> _events = GetAcademicCalendarEvents
                        (data.Year, data.Term, data.Campus)
                .SkipWhile(c => c.EventType != "TermBegins")
                .TakeUntilInclusive(c => c.EventKey == "TermEnds");

For clarity, I could create a SkipUntil wrapper around SkipWhile, but given that SkipWhile is doing what I want anyway, I didn’t feel it was necessary. Now, this code works against an IEnumerable, not an IQueryable, so it can really only be used on data sets small enough to be handled in memory. I’m not entirely sure how I’d implement this in an IQueryable, as that object expects to be able to translate to SQL (or similar) at some point, and this isn’t the sort of thing that I can think of how to do in a single query.

Writing Extension methods is really easy, and LINQ is the best example of their power so far. The fact that you can so easily extend upon this existing work is fantastic, and I can think of a few other problems I’m liable to have to solve in the coming months where I’ll be using these extension methods, or some new ones.

  • StumbleUpon Toolbar Stumble It!

No TrackBacks

TrackBack URL: http://blog.foxxtrot.net/cgi-bin/mt-tb.cgi/340

2 Comments

I googled “TakeUntil” for the same reason as you - TakeWhile is not inclusive. However your TakeUntilInclusive doesn’t support deferred execution, which can be important when you’re working with large sets of data. Or am I reading it wrong?

No, you’re right, I don’t support deferred execution in this code. My use case was small sets of data, which I just wanted to manipulate in memory. I’ll probably take a look at doing the deferred execution route, as a mental exercise if nothing else, and I’ll be sure to post the results of that when I’m done with it.

Leave a comment

About this Entry

This page contains a single entry by Jeff Craig published on June 26, 2009 11:59 AM.

Sub-$20 Apartment-Friendly Clothesline was the previous entry in this blog.

Getting Mulitple Select Values the Computer Science Way is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Once You Know, You Newegg

μ-updates

  • No Updates!
OpenID accepted here Learn more about OpenID
Powered by Movable Type 4.21-en