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
EditThe 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);
}
}
}
}EditThe 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++;
}
}
}
}