Hooked on LINQ

Hooked on LINQ - Developers' Wiki
for .NET Language Integrated Query

Companion book for this site
LINQ to Objects Using C# 4.0:
Using and Extending LINQ to Objects and Parallel LINQ (PLINQ)
Quick Search

Advanced Search »
Edit

Chapter 7 - Extending LINQ to Objects





WARNING: Some of the Listing numbers DON'T MATCH the printed publication. I'll be synchronizing them as soon as possible, until then ignore the listing numbers. The code is the same, it is just the Listing numbers that do not match.

Edit

Writing New Operators

Edit

Listing 7-3 : RandomElement operator.

This sample shows how to write a Single element operator. The RandomElement operator returns a single element at random from a source sequence. This method demonstrates its usage.

public void Listing_7_3_RandomElement_Operator()
{
    var contacts = Contact.SampleData();
    Contact con = contacts.RandomElement();
    Console.WriteLine("{0} {1} – {2}",
        con.FirstName,
        con.LastName,
        con.Phone);
}
 
public static class Listing_7_3_Extensions
{
    public static T RandomElement<T>(
        this IEnumerable<T> source, 
        int seed = 0 /* optional parameter, new to C# 4.0 */ )
    {
        // check for invalid source conditions
        if (source == null) 
            throw new ArgumentNullException("source");
 
        // the Count() extension method already has this
        // optimization built–in. I'm showing it here to
        // make the point it is being considered!
        int count = 0;
        if (source is ICollection)
            count = ((ICollection)source).Count;
        else
            count = source.Count();
 
        if (count == 0)
            throw new InvalidOperationException("No elements");
 
        // use the passed in seed value, or use time–based seed
        Random random;
        if (seed == 0)
            random = new Random();
        else
            random = new Random(seed);
 
        // IList implementors, access by indexer. its faster. 
        IList<T> list = source as IList<T>;
        if (list != null)
        {
            int index = random.Next(0, count);
            return list[index];
        }
        else
        {
            // Other collection types must be 
            // enumerated (eg. Dictionary, IEnumerable)
            int index = random.Next(0, count);
            using (IEnumerator<T> e = source.GetEnumerator())
            {
                // move to the first element (the initial 
                // position is BEFORE the first element.
                e.MoveNext();
 
                // iterate and move until we hit our index
                while (index > 0)
                {
                    e.MoveNext();
                    index––;
                }
 
                return e.Current;
            }
        }
    }
 
    public static T RandomElementOrDefault<T>(
        this IEnumerable<T> source)
    {
        // check for invalid source conditions
        if (source == null)
            throw new ArgumentNullException("source");
 
        // the Count() extension method already has this
        // optimization built–in. I'm showing it here to
        // make the point it is being considered.
        int count = 0;
        if (source is ICollection)
            count = ((ICollection)source).Count;
        else
            count = source.Count();
 
        if (count == 0)
            return default(T);
 
        //... same as RandomElement operator code. 
 
        return default(T);
    }
}
 

Console output (Execution time: 9ms): [Hide/Show]


Top



Edit

Listing 7-4 : RandomElement operator + unit tests.

This sample shows how to write a Single element operator. The RandomElement operator returns a single element at random from a source sequence. This method runs the unit-tests for the operator.

public void Listing_7_4_RandomElement_Operator_UnitTests()
{
    RunUnitTestFixture("SampleQueries.RandomElementUnitTests");
}
 
public static class Listing_7_3_Extensions
{
    public static T RandomElement<T>(
        this IEnumerable<T> source, 
        int seed = 0 /* optional parameter, new to C# 4.0 */ )
    {
        // check for invalid source conditions
        if (source == null) 
            throw new ArgumentNullException("source");
 
        // the Count() extension method already has this
        // optimization built–in. I'm showing it here to
        // make the point it is being considered!
        int count = 0;
        if (source is ICollection)
            count = ((ICollection)source).Count;
        else
            count = source.Count();
 
        if (count == 0)
            throw new InvalidOperationException("No elements");
 
        // use the passed in seed value, or use time–based seed
        Random random;
        if (seed == 0)
            random = new Random();
        else
            random = new Random(seed);
 
        // IList implementors, access by indexer. its faster. 
        IList<T> list = source as IList<T>;
        if (list != null)
        {
            int index = random.Next(0, count);
            return list[index];
        }
        else
        {
            // Other collection types must be 
            // enumerated (eg. Dictionary, IEnumerable)
            int index = random.Next(0, count);
            using (IEnumerator<T> e = source.GetEnumerator())
            {
                // move to the first element (the initial 
                // position is BEFORE the first element.
                e.MoveNext();
 
                // iterate and move until we hit our index
                while (index > 0)
                {
                    e.MoveNext();
                    index––;
                }
 
                return e.Current;
            }
        }
    }
 
    public static T RandomElementOrDefault<T>(
        this IEnumerable<T> source)
    {
        // check for invalid source conditions
        if (source == null)
            throw new ArgumentNullException("source");
 
        // the Count() extension method already has this
        // optimization built–in. I'm showing it here to
        // make the point it is being considered.
        int count = 0;
        if (source is ICollection)
            count = ((ICollection)source).Count;
        else
            count = source.Count();
 
        if (count == 0)
            return default(T);
 
        //... same as RandomElement operator code. 
 
        return default(T);
    }
}
 
public class RandomElementUnitTests
{
    //3456789|123456789|123456789|123456789|123456789|12345678  
    [Test]
    [ExpectedException("System.ArgumentNullException")]
    public void NullSourceTest()
    {
        int[] values = null;
        int result = values.RandomElement();
    }
 
    [Test]
    [ExpectedException("System.InvalidOperationException")]
    public void EmptySourceTest()
    {
        var values = Enumerable.Empty<int>();
        int result = values.RandomElement();
    }
 
    [Test]
    public void IEnumerableTest()
    {
        // create a test enumerable of int's 0–999
        var values = Enumerable.Range(0, 1000);
        int result1 = values.RandomElement(1);
 
        // Random class seed is different
        var values2 = Enumerable.Range(0, 1000);
        int result2 = values2.RandomElement(2);
 
        // ensure a different result is returned
        // it is OK for this operator to return the same
        // element. However, we only have a 1 in 1000 chance.
        Assert.AreNotSame(result1, result2);
    }
 
    [Test]
    public void IListTest()
    {
        // create a test array of int's 0–999
        var values = Enumerable.Range(0, 1000).ToArray();
        int result1 = values.RandomElement(1);
 
        // use a different random seed
        int result2 = values.RandomElement(2);
 
        // it is OK for this operator to return the same
        // element. However, we only have a 1 in 1000 chance.
        Assert.AreNotSame(result1, result2);
    }
 
    [Test]
    public void SingleElementTest()
    {
        // for a single element source, 
        // it should be returned every time
 
        // IEnumerable<T>
        var values = Enumerable.Range(10, 1);
        int result1 = values.RandomElement(1);
        Assert.AreEqual(10, result1);
 
        // IList<T>
        var values2 = values.ToArray();
        int result2 = values.RandomElement(2);
        Assert.AreEqual(10, result2);
    }
 
    [Test]
    public void BoundaryEnumerableTest()
    {
        // for a two element source, we want
        // to check the boundaries can be returned
        var values = Enumerable.Range(10, 2);
 
        // check both results are returned at 
        // least once in 25 attempts using 
        // different random number generator seeds.
        int result1 = values.RandomElement();
        bool foundDifferent = false;
        for (int i = 0; i < 25; i++)
        {
            int result2 = values.RandomElement(i+100);
 
            if (result1 != result2)
            {
                foundDifferent = true;
                break;
            }
        }
        Assert.IsTrue(foundDifferent);
    }
 
    [Test]
    public void BoundaryIListTest()
    {
        // for a two element source, we want
        // to check the boundaries can be returned
        var values = Enumerable.Range(10, 2).ToArray();
 
        // check both results are returned at 
        // least once in 25 attempts using 
        // different random number generator seeds.
        int result1 = values.RandomElement();
        bool foundDifferent = false;
        for (int i = 0; i < 25; i++)
        {
            int result2 = values.RandomElement(i+100);
 
            if (result1 != result2)
            {
                foundDifferent = true;
                break;
            }
        }
        Assert.IsTrue(foundDifferent);
    }
}
 

Console output (Execution time: 189ms): [Hide/Show]


Top



Edit

Listing 7-4 to 7-7 : TakeRange operator + unit tests.

This sample shows how to write a Sequence operator. The TakeRange operator returns all elements starting with the first element to pass the Start predicate, and ending with the last element that passes the End predicate. This method runs the unit-tests for the operator.

public void Listing_7_7_TakeRange_Operator_UnitTests()
{
    RunUnitTestFixture("SampleQueries.TakeRangeUnitTests");
}
 
public static class Listing_7_4_to_7_6_Extensions
{
    private static IEnumerable<T> TakeRangeIterator<T>(
        this IEnumerable<T> source,
        Func<T, bool> startPredicate,
        Func<T, bool> endPredicate)
    {
        // check for invalid input argument conditions
        if (source == null)
            throw new ArgumentNullException("source");
 
        if (startPredicate == null)
            throw new ArgumentNullException("startPredicate");
 
        if (endPredicate == null)
            throw new ArgumentNullException("endPredicate");
        
        // begin the iteration ...
        bool foundStart = false;
        
        foreach (T element in source)
        {
            if (startPredicate(element))
                foundStart = true;
 
            if (foundStart)
                if (endPredicate(element))
                    yield break;
                else
                    yield return element;
        }
    }
 
    public static IEnumerable<T> TakeRange<T>(
        this IEnumerable<T> source,
        Func<T, bool> endPredicate)
    {
 
        return TakeRangeIterator<T>(
            source, b => true, endPredicate);
    }
 
    public static IEnumerable<T> TakeRange<T>(
        this IEnumerable<T> source,
        Func<T, bool> startPredicate,
        Func<T, bool> endPredicate)
    {
 
        return TakeRangeIterator<T>(
            source, startPredicate, endPredicate);
    }
}
 
public class TakeRangeUnitTests
{
    //3456789|123456789|123456789|123456789|123456789|12345678  
    [Test]
    [ExpectedException("System.ArgumentNullException")]
    public void NullSourceTest()
    {
        int[] values = null;
        var result = values.TakeRange(
            start => true, end => true).ToArray<int>();
    }
 
    [Test]
    [ExpectedException("System.ArgumentNullException")]
    public void NullStartPredicateTest()
    {
        int[] values = null;
        int[] result = values.TakeRange(
            null, b => true).ToArray<int>();
    }
 
    [Test]
    [ExpectedException("System.ArgumentNullException")]
    public void NullEndPredicateTest()
    {
        int[] values = null;
        int[] result = values.TakeRange(
            a => true, null).ToArray<int>();
    }
 
    [Test]
    public void IntArrayTest()
    {
        var values = Enumerable.Range(0, 20);
 
        int[] result = values.TakeRange(
                 start => start > 4, 
                 end => end > 9).ToArray<int>();
 
        Assert.AreEqual(
            5, result.Count(), "incorrect count returned");
 
        // expecting 5,6,7,8,9 to be returned
        for (int i = 0; i < result.Count(); i++)
            Assert.AreEqual(i + 5, result[i]);
    }
 
    [Test]
    public void IntArrayEndPredicateOnlyTest()
    {
        var values = Enumerable.Range(0, 20);
 
        int[] result = values.TakeRange(
                 end => end > 9).ToArray<int>();
 
        Assert.AreEqual(
            10, result.Count(), "incorrect count returned");
 
        // expecting 10–19 to be returned
        for (int i = 10; i < result.Count(); i++)
            Assert.AreEqual(i, result[i]);
    }
}
 

Console output (Execution time: 16ms): [Hide/Show]


Top



Edit

Listing 7-9 : LongSum operator + unit tests.

This sample shows how to write a Aggregate operator. The LongSum operator returns the sum of a sequence of integers as a long type. This method runs the unit-tests for the operator.

public void Listing_7_9_LongSum_Operator_UnitTests()
{
    RunUnitTestFixture("SampleQueries.LongSumUnitTests");
}
 
public static class Listing_7_8_Extensions
{
    public static long LongSum(
        this IEnumerable<int> source)
    {
        if (source == null)
            throw new ArgumentNullException("source");
 
        long sum = 0;
        checked
        {
            foreach (int v in source) 
                sum += v;
        }
 
        return sum;
    }
 
    public static long LongSum<T>(
        this IEnumerable<T> source,
        Func<T, int> selector)
    {
        return source
               .Select(selector)
               .LongSum();
    }
}
 
public class LongSumUnitTests
{
    [Test]
    public void LongSumIntTest()
    {
        int[] values = new int[] { 5, 4, 3, 2, 1, 6, 7, 8, 9, 0 };
        long result = values.LongSum();
        Assert.AreEqual(45, result, "LongSum incorrect result.");
    }
 
    [Test]
    public void LongSumNegativeIntTest()
    {
        int[] values = new int[] { int.MinValue, int.MaxValue, 1 };
        long result = values.LongSum();
        Assert.AreEqual(0, result, "Negative value LongSum incorrect result.");
    }
    
    [Test]
    public void LongSumIntBigTest()
    {
        int[] values = new int[] { int.MaxValue, 1, int.MaxValue };
        long result = values.LongSum();
        long correct = (long)int.MaxValue + (long)int.MaxValue + 1;
        Assert.AreEqual(correct, result);
    }
 
    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void LongSumIntNullSourceTest()
    {
        int[] values = null;
        long result = values.LongSum();
    }
 
    [Test]
    public void LongSumIntEmptySourceTest()
    {
        var values = Enumerable.Empty<int>();
        long result = values.LongSum();
        Assert.AreEqual(0, result);
    }
 
    [Test]
    public void LongSumIntWithSelectorTest()
    {
        int[] values = new int[] { 5, 4, 3, 2, 1, 6, 7, 8, 9, 0 };
        long result = values.LongSum(i => i + 1);
        Assert.AreEqual(55, result);
    }
 
}
 

Console output (Execution time: 21ms): [Hide/Show]


Top



Edit

Segment operator sample 1

.

public void Listing_7_x_Segment_Operator_Sample_1()
{
    string[] elements = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };
 
    var groups = elements.Segment(5);
 
    foreach (var g in groups)
    {
        Console.WriteLine("Group key: {0}", g.Key);
        foreach (var elm in g)
            Console.WriteLine("  {0}", elm);
    }
}
 

Console output (Execution time: 29ms): [Hide/Show]


Top



Edit

Segment operator sample 2

.

public void Listing_7_x_Segment_Operator_Sample_2()
{
    string[] elements = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };
 
    var groups = elements.Segment(4);
 
    foreach (var g in groups)
    {
        Console.WriteLine("Group key: {0}", g.Key);
        foreach (var elm in g)
            Console.WriteLine("  {0}", elm);
    }
}
 

Console output (Execution time: 1ms): [Hide/Show]


Top



Edit

Listing 7-16 - Segment operator over contact records

Demonstrate using the segment operator by creating two test groups from contact records.

public void Listing_7_16_Segment_Over_Contacts()
{
    List<Contact> contacts = Contact.SampleData();
 
    // split the contacts into 2 groups for A/B testing
    var groups = contacts.Segment(2);
 
    foreach (var g in groups)
    {
        Console.WriteLine("Group key: {0}", g.Key);
        foreach (var elm in g)
            Console.WriteLine("  {0}, {1}",
                elm.LastName.ToUpper(), elm.FirstName);
    }
}
 

Console output (Execution time: 2ms): [Hide/Show]


Top



Edit

Listing 7-12 : Segment operator + unit tests.

This sample shows how to write a Grouping operator. The Segment operator returns the number of requested groups (segments argument), populated with an equal number of elements (where possible). This method runs the unit-tests for the operator.

public void Listing_7_12_Segment_Operator_UnitTests()
{
    RunUnitTestFixture("SampleQueries.SegmentUnitTests");
}
 
public static class Listing_7_11_Extensions
{
    public static IEnumerable<IGrouping<int, T>> Segment<T>(
        this IEnumerable<T> source,
        int segments)
    {
        if (source == null)
            throw new ArgumentNullException("source");
 
        if (segments <= 0)
            throw new ArgumentOutOfRangeException("segments");
 
        return SegmentIterator<T>(source, segments);
    }
 
    private static IEnumerable<IGrouping<int, T>> 
        SegmentIterator<T>(
            IEnumerable<T> source,
            int segments)
    {
        // calculate the number of elements per segment
        int count = source.Count();
        int perSegment = (int)Math.Ceiling(
            (decimal)count / segments);
 
        // build the empty groups
        Grouping<int, T>[] groups =
            new Grouping<int, T>[segments];
 
        for (int i = 0; i < segments; i++)
        {
            Grouping<int, T> g =
                new Grouping<int, T>(perSegment);
 
            g.key = i + 1;
            groups[i] = g;
        }
 
        // fill the groups and yield results 
        // when each group full.
        int index = 0;
        int segment = 1;
        Grouping<int, T> group = groups[0];
        using (IEnumerator<T> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                group.Add(e.Current);
                index++;
 
                // yield return when we have filled each group
                if ((segment < segments) && 
                    (index == perSegment))
                {
                    yield return group;
                    index = 0;
                    segment++;
                    group = groups[segment – 1];
                }
            }
        }
 
        // return the last and any remaining groups
        // (these will be empty or partially populated)
        while (segment <= segments)
        {
            yield return groups[segment – 1];
            segment++;
        }
    }
}
 
public class SegmentUnitTests
{
    [Test]
    [ExpectedException("System.ArgumentNullException")]
    public void NullSourceTest()
    {
        int[] values = null;
        var result = values.Segment(1);
    }
 
    [Test]
    public void EmptySourceTest()
    {
        // expecting 4 empty groups
        int[] values = Enumerable.Empty<int>().ToArray();
        var result = values.Segment(4);
        Assert.AreEqual(4, result.Count());
    }
 
    [Test]
    public void EvenSegmentTest()
    {
        int[] values = 
            Enumerable.Range(1, 100).ToArray<int>();
 
        var result = values.Segment(4);
        Assert.AreEqual(4, result.Count());
 
        foreach (var g in result)
            Assert.AreEqual(25, g.Count());
    }
 
    [Test]
    public void MoreSegmentsThanElementsTest()
    {
        int[] values = Enumerable.Range(1, 3).ToArray<int>();
 
        var result = values.Segment(10);
 
        Assert.AreEqual(10, result.Count());
        int i = 1;
        foreach (var g in result)
        {
            if (i < 4)
                Assert.AreEqual(1, g.Count());
            else
                Assert.AreEqual(0, g.Count());
 
            i++;
        }
    }
 
    [Test]
    public void OddSegmentTest()
    {
        int[] values = 
            Enumerable.Range(1, 101).ToArray<int>();
 
        var result = values.Segment(4);
        Assert.AreEqual(4, result.Count());
 
        int i = 1;
        foreach (var g in result)
        {
            if (i < 4)
                Assert.AreEqual(26, g.Count());
            else
                Assert.AreEqual(23, g.Count());
 
            i++;
        }
    }
 
}
 

Console output (Execution time: 16ms): [Hide/Show]


Top



If you would like to comment on this page, click on the Discuss button located on the top-right of each page. Feel free to edit any mistakes or omissions you find. If you have an objection or find in-appropriate content then contact the administrator. This website is not affiliated with Microsoft®, all content and opinions are those of the specific author and some advice, solutions and article may contain unintentional errors - please use care. Powered by ScrewTurn Wiki version 2.0.33. Some of the icons created by FamFamFam.