Welcome to Honest Illusion Sign in | Join | Help

More Fun with C# Iterators: Take, Skip, TakeWhile, SkipWhile

As I was reading this article by Granville Barnett on some of the new operators available on LINQ queries, I thought, "That's all well and good, but for the time being, we're living in a .Net 2.0 world.  I wonder if I could emulate those with just generics & iterators?"  As it turned out, it was quite easy.

First up is Take:  Given a collection, we want to return the subset which is just the first N items.  To handle this, we just return items while counting down to zero. When we reach zero, we stop. 

        static public IEnumerable<T> Take<T>(IEnumerable<T> enm, int take)
        {
            foreach (T t in enm)
            {
                if (take-- == 0)
                    break;
                yield return t;
            } // foreach 
         }

            string[] peopleinit = new string[] { "Granville", "Rachel", "Monica", "John", "Ross", "Joey" };
            List<string> people = new List<string>(peopleinit);
 
            Console.WriteLine("Take Test....");
            foreach (string person in Iter.Take(people, 2))
            {
                Console.WriteLine(person);   // Prints  Granville, Rachel
            }

Next, is Skip.  This is essentially the reverse of Take: Given a collection, we want to return the subset which is everything after the first N items.  And handling Skip is essnetially the reverse of Take as well:  We still count down to zero as we iterator through the collection, but we don't return anything until we reach zero.  (Of course, after we reach zero, we have to stop decrementing the count, or the if() will fail when skip equal -1.)

static public IEnumerable<T> Skip<T>(IEnumerable<T> enm, int skip)
{
    foreach (T t in enm)
    {
        if (skip == 0)
            yield return t;
        else
            --skip;
    } // foreach 
}

Console.WriteLine("Skip Test....");
foreach (string person in Iter.Skip(people, 4))
{
    Console.WriteLine(person);   // Prints  Ross, Joey
}

Now, once we have Skip, SkipFirst (which we discussed before) just become an instance of that.

static public IEnumerable<T> SkipFirst<T>(IEnumerable<T> enm)
{
    return Skip(enm, 1);
} 

There is no simple way to expand SkipLast, which was also discussed in that previous article, beyond just one item, so we'll leave it's implementation.

A bit more advanced are TakeWhile and SkipWhile.  And, unlike the LINQ versions, we can't use lambda expression, so we'll have to make do with delagates.

The essense of TakeWhile is, given a collection, we return items until we reach one which fails a given predicate.  Note that we stop at the first failure we reach (even if there may be later item which pass the predicate test)

public delegate bool whilecond<T>(T t);
 
static public IEnumerable<T> TakeWhile<T>(IEnumerable<T> enm, whilecond<T> take)
{
      foreach (T t in enm)
    {
        if (take(t))
            yield return t;
        else
            break;
 
    } // foreach 
 
}

public static bool LongerThan5(string a)
{
    return a.Length > 5;
}
        // :
        //:
        
 Console.WriteLine("TakeWhile Test....");
foreach (string person in Iter.TakeWhile(people, LongerThan5))
{
    Console.WriteLine(person);   // Prints  Granville, "Rachel", "Monica", 
}

 

Final, this brings us to SkipWhile, which is the inverse of TakeWhile:  Given a collection, we skip items until one fails a given predicate, and then we return the rest (even though some of the rest may fail the predicate)

static public IEnumerable<T> SkipWhile<T>(IEnumerable<T> enm, whilecond<T> skip)
{
    bool doneskipping = false;
    foreach (T t in enm)
    {
        if (doneskipping || !skip(t))
        {
            doneskipping = true;
            yield return t;
         } // else
 
    } // foreach 
}

 
Console.WriteLine("SkipWhile Test....");
foreach (string person in Iter.SkipWhile(people, LongerThan5))
{
    Console.WriteLine(person);   // Prints  "John", "Ross", "Joey" 
}

 

 

kick it on DotNetKicks.com
Share this post: Email it! | bookmark it! | digg it! | reddit!
Readability Stats: Word Count: 617; Sentence Count: 47; Grade Level: 6.0, more info...
Published Friday, March 09, 2007 7:04 PM by James

Comments

# More Fun with C# Iterators: Take, Skip, TakeWhile, SkipWhile

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Friday, March 09, 2007 7:11 PM by DotNetKicks.com

# re: More Fun with C# Iterators: Take, Skip, TakeWhile, SkipWhile

Maybe you can use Predicate<T> in SkipWhile and TakeWhile?

Monday, March 12, 2007 11:38 PM by cpon

# re: More Fun with C# Iterators: Take, Skip, TakeWhile, SkipWhile

cpon:

You're right -- we can.  I forgot about the Predicate<T>.  Just replace "whilecond" with "Predicate".  

Tuesday, March 13, 2007 1:54 AM by James

# re: More Fun with C# Iterators: Take, Skip, TakeWhile, SkipWhile

You could also make your skip algorithms slightly more efficient by explicitly getting the IEnumerator<T> and skipping first:

static public IEnumerable<T> Skip<T>(IEnumerable<T> enm, int skip)

{

IEnumerator<T> enumerator = enm.GetEnumerator();

while (skip > 0)

{

enumerator.MoveNext();

--skip;

}

while (enumerator.MoveNext())

{

yield return enumerator.Current;

}

}

That way you get rid of the if test for every iteration. I just put this in a test harness to make sure the improvement worked. Here is the output:

Iterations: 5000000

Skipping: 2500000

OLD took 270ms, total: 9374998750000

NEW took 169ms, total: 9374998750000

The code being timed just uses Skip to total the 5 million integers that are in a list.

Sunday, March 18, 2007 10:18 PM by Kent Boogaart
New Comments to this post are disabled