Hooked on LINQ

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

Quick Search

Advanced Search »

The Segment Operator is a sample Grouping style extension method. Its job is to divide an input source into the specified number of segments. It will always return the given number of segments even if there are not enough elements in the input sequence, and if the number of elements per segment cannot be equal, the last segment will be reduced in quantity.

The code behind this operator shows remarkable similarity to the Sequence.cs source code supplied by Microsoft as part of the MAY 2006 LINQ CTP. This is intentional, the patterns shown in the Standard Query Operators should be the basis for all of our operators to ensure a consistent developer experience when using them. We should throw similar exceptions, and use the delegate prorotypes that all the other operators use.

Back to Writing Operators

Edit

The Code

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Query;
 
namespace Aspiring.Query
{
    public static class SegmentExtensions
    {
        public static IEnumerable<IGrouping<int, T>> Segment<T>(
            this IEnumerable<T> source, 
            int segments) {
 
                if (source == null) throw Error.ArgumentNull("source");
                if (segments <= 0) throw Error.ArgumentOutOfRange("segments");
                return SegmentIterator<T>(source, segments);
        }
 
        static IEnumerable<IGrouping<int, T>> SegmentIterator<T>(
            IEnumerable<T> source, 
            int segments) {
 
            // calculate the number of elements per segment, rounded up.
            int count = source.Count();
            decimal perSegment = Math.Ceiling((decimal)count / (decimal)segments);
            
            // build the empty groups
            SegmentGrouping<int,T>[] groups = new SegmentGrouping<int,T>[segments];
            for (int i = 0; i < segments; i++) {
                SegmentGrouping<int,T> g = new SegmentGrouping<int,T>((int)perSegment);
                g.key = i + 1;
                groups[i] = g;
            }
            
            // fill the groups and yield results when each group full
            int index   = 0;
            int segment = 1;
            SegmentGrouping<int,T> group = groups[0];
            using (IEnumerator<T> e = source.GetEnumerator()) {
                while (e.MoveNext()) {
                    group.Add(e.Current);
                    index++;
 
                    // when we have filled each group, yield return
                    if ((segment < segments) && (index == perSegment)) {
                        yield return group;
                        index = 0;
                        segment++;
                        group = groups[segment-1];
                    }
                }
            }
 
            // return the last and any remaining empty groups
            while(segment <= segments) {
                yield return groups[segment - 1];
                segment++;
            }
        }
 
        class Error {
            internal static Exception ArgumentNull(string paramName) {
                return new ArgumentNullException(paramName);
            }
 
            internal static Exception ArgumentOutOfRange(string paramName) {
                return new ArgumentOutOfRangeException(paramName);
            }
        }
    }
     
    public class SegmentGrouping<K, T> : IGrouping<K, T>, IList<T>
    {
        internal K key;
        internal T[] elements;
        internal int count;
 
        public SegmentGrouping(int count) {
            elements = new T[count];
        }
 
        internal void Add(T element) {
            if (elements.Length == count) Array.Resize(ref elements, count * 2);
            elements[count++] = element;
        }
 
        public IEnumerator<T> GetEnumerator() {
            for (int i = 0; i < count; i++) yield return elements[i];
        }
 
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
 
        K IGrouping<K, T>.Key {
            get { return key; }
        }
 
        int ICollection<T>.Count {
            get { return count; }
        }
 
        bool ICollection<T>.IsReadOnly {
            get { return true; }
        }
 
        void ICollection<T>.Add(T item) {
            throw Error.NotSupported();
        }
 
        void ICollection<T>.Clear() {
            throw Error.NotSupported();
        }
 
        bool ICollection<T>.Contains(T item) {
            return Array.IndexOf(elements, item, 0, count) >= 0;
        }
 
        void ICollection<T>.CopyTo(T[] array, int arrayIndex) {
            Array.Copy(elements, 0, array, arrayIndex, count);
        }
 
        bool ICollection<T>.Remove(T item) {
            throw Error.NotSupported();
        }
 
        int IList<T>.IndexOf(T item) {
            return Array.IndexOf(elements, item, 0, count);
        }
 
        void IList<T>.Insert(int index, T item) {
            throw Error.NotSupported();
        }
 
        void IList<T>.RemoveAt(int index) {
            throw Error.NotSupported();
        }
 
        T IList<T>.this[int index] {
            get {
                if (index < 0 || index >= count) throw Error.ArgumentOutOfRange("index");
                return elements[index];
            }
            set {
                throw Error.NotSupported();
            }
        }
 
        class Error {
            internal static Exception ArgumentNull(string paramName) {
                return new ArgumentNullException(paramName);
            }
 
            internal static Exception NotSupported() {
                return new NotSupportedException();
            }
 
            internal static Exception ArgumentOutOfRange(string paramName) {
                return new ArgumentOutOfRangeException(paramName);
            }
        }
    }
}



Edit

The Unit Tests

using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using NUnit.Framework;
using Aspiring.Query;
 
namespace Aspiring.Query.UnitTests
{
    [TestFixture]
    public class SegmentExtensionTests
    {
        [Test]
        public void EvenSegmentTest() {
            int[] values = Sequence.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 = Sequence.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 = Sequence.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++;
            }
        }
    }
}

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 ommissions 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 un-intentional errors - please use care. Powered by ScrewTurn Wiki version 2.0.33. Some of the icons created by FamFamFam.