2762 words
14 minutes
Category Theory via C# (15) IEnumerable<> Monad And LINQ: SelectMany For All

[LINQ via C# series]#

[Category Theory via C# series]#

Latest version: https://weblogs.asp.net/dixin/category-theory-via-csharp-7-monad-and-linq-to-monads#

Previous part introduced SelectMany for monad IEnumerable<>. Actually SelectMany is more than meets the eye, and can be used to implement other LINQ queries.

Query methods implemented by SelectMany#

This part will demonstrate how to use SelectMany to implement following LINQ query methods:

  • Restriction: Where
  • Projection: Select
  • Join: Join, GroupJoin
  • Grouping: GroupBy
  • Set: Zip, Distinct, Union, Intersect, Except
  • Partitioning: Take, Skip, TakeWhile, SkipWhile
  • Cancatening: Concat

First, create a help method to make the code shorter:

[Pure]
public static partial class EnumerableSelectManyExtensions
{
// value.Enumerable(isNotEmpty) is the alias of (isNotEmpty ? value.Enumerable() : Enumerable.Empty<TSource>())
public static IEnumerable<TSource> Enumerable<TSource>(this TSource value, bool isNotEmpty = false)
{
// return isNotEmpty ? EnumerableEx.Return(value) : Enumerable.Empty<TSource>();
if (isNotEmpty)
{
yield return value;
}
}
}

And here comes this long list of 15 methods:

public static IEnumerable<TSource> Concat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
{
return new IEnumerable<TSource>[] { first, second }
.SelectMany(Functions.Id);
}
public static IEnumerable<TSource> Distinct<TSource>(
this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
{
HashSet<TSource> hashSet = new HashSet<TSource>(comparer);
return source
.SelectMany(value => value.Enumerable(hashSet.Add(value)));
}
public static IEnumerable<TSource> Except<TSource>(
this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
HashSet<TSource> hashSet = new HashSet<TSource>(second, comparer);
return first
.SelectMany(firstValue => firstValue.Enumerable(hashSet.Add(firstValue)));
}
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> comparer)
{
// return source.ToLookup(keySelector, elementSelector, comparer);
HashSet<TKey> hashSet = new HashSet<TKey>(comparer);
return source
.SelectMany(value => keySelector(value).Enumerable())
.SelectMany(key => key.Enumerable(hashSet.Add(key)))
// Microsoft.FSharp.Linq.RuntimeHelpers.Grouping<K, T>
.SelectMany(key => new Grouping<TKey, TElement>(key, source
// SelectMany inside SelectMany. Time complexity is O(N * N).
.SelectMany(value => elementSelector(value).Enumerable(comparer.Equals(key, keySelector(value))))).Enumerable());
}
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, IEnumerable<TInner>, TResult> resultSelector,
IEqualityComparer<TKey> comparer)
{
ILookup<TKey, TInner> lookup = inner.ToLookup(innerKeySelector, comparer); // Lookup<TKey, TInner> cannot be created by public API.
return outer
.SelectMany(outerValue => resultSelector(outerValue, lookup[outerKeySelector(outerValue)]).Enumerable());
}
public static IEnumerable<TSource> Intersect<TSource>(
this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
HashSet<TSource> hashSet = new HashSet<TSource>(second, comparer);
return first
.SelectMany(firstValue => firstValue.Enumerable(hashSet.Remove(firstValue)));
}
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector,
IEqualityComparer<TKey> comparer)
{
ILookup<TKey, TInner> lookup = inner.ToLookup(innerKeySelector, comparer); // Lookup<TKey, TInner> cannot be created by public API.
return outer
.SelectMany(outerValue => lookup[outerKeySelector(outerValue)]
.SelectMany(innerValue => resultSelector(outerValue, innerValue).Enumerable()));
}
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
return source.SelectMany(
sourceValue => selector(sourceValue).Enumerable());
}
public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)
{
return source
.SelectMany((value, index) => value.Enumerable(index >= count));
}
public static IEnumerable<TSource> SkipWhile<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
bool flag = false;
return source
.SelectMany(value =>
{
if (!flag && !predicate(value))
{
flag = true; // Imperative.
}
return value.Enumerable(flag);
});
}
public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
return source
.SelectMany((value, index) => value.Enumerable(index < count));
}
public static IEnumerable<TSource> TakeWhile<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
bool flag = true;
return source
.SelectMany(value =>
{
if (!predicate(value))
{
flag = false; // Imperative.
}
return value.Enumerable(flag);
});
}
public static IEnumerable<TSource> Union<TSource>(
this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
HashSet<TSource> hashSet = new HashSet<TSource>(comparer);
return new IEnumerable<TSource>[] { first, second }
.SelectMany(Functions.Id)
.SelectMany(value => value.Enumerable(hashSet.Add(value)));
}
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return source
.SelectMany(value => value.Enumerable(predicate(value)));
}
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
return first
.SelectMany((firstValue, index) => second
// SelectMany inside SelectMany. Time complexity is O(N * N).
.SelectMany((value, index2) => value.Enumerable(index2 == index)), resultSelector);
}

Again these code are just for demonstration purpose. The performance of above code is no better than the built-in implementation in System.Linq.Enumerable. Also, some of above SelectMany code is imperative, for example:

  • Skip, Take, Zip use the index
  • SkipWhile and TakeWhile use a variable

So these SelectMany usages are not authentically functional.

Query methods in LINQ syntax#

Above implementations in LINQ syntax:

public static IEnumerable<TSource> Concat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
{
return from enumerable in new IEnumerable<TSource>[] { first, second }
from value in enumerable
select value;
}
public static IEnumerable<TSource> Distinct<TSource>(
this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
{
HashSet<TSource> hashSet = new HashSet<TSource>(comparer);
return from value in source
// where hashSet.Add(value)
from distinct in value.Enumerable(hashSet.Add(value))
select distinct;
}
public static IEnumerable<TSource> Except<TSource>(
this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
HashSet<TSource> hashSet = new HashSet<TSource>(second, comparer);
return from value in first
// where hashSet.Add(value)
from except in value.Enumerable(hashSet.Add(value))
select except;
}
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> comparer)
{
HashSet<TKey> hashSet = new HashSet<TKey>(comparer);
return from value in source
let key = keySelector(value)
// where hashSet.Add(key)
from distinctKey in key.Enumerable(hashSet.Add(key))
select new Grouping<TKey, TElement>(
distinctKey,
from value2 in source
// where comparer.Equals(distinctKey, keySelector(value2))
from element in elementSelector(value).Enumerable(comparer.Equals(key, keySelector(value2)))
select elementSelector(value2));
}
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, IEnumerable<TInner>, TResult> resultSelector,
IEqualityComparer<TKey> comparer)
{
ILookup<TKey, TInner> lookup = inner.ToLookup(innerKeySelector, comparer);
return from outerValue in outer
// select resultSelector(outerValue, lookup[outerKeySelector(outerValue)])
from result in resultSelector(outerValue, lookup[outerKeySelector(outerValue)]).Enumerable()
select result;
}
public static IEnumerable<TSource> Intersect<TSource>(
this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
HashSet<TSource> hashSet = new HashSet<TSource>(second, comparer);
return from firstValue in first
// where hashSet.Remove(firstValue)
from intersect in firstValue.Enumerable(hashSet.Remove(firstValue))
select intersect;
}
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector,
IEqualityComparer<TKey> comparer)
{
ILookup<TKey, TInner> lookup = inner.ToLookup(innerKeySelector, comparer); // Lookup<TKey, TInner> cannot be created by public API.
return from outerValue in outer
from result in
(from innerValue in lookup[outerKeySelector(outerValue)]
// select resultSelector(outerValue, innerValue)
from result2 in resultSelector(outerValue, innerValue).Enumerable()
select result2)
select result;
}
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
return from value in source
// select selector(value)
from result in selector(value).Enumerable()
select result;
}
public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)
{
int index = 0;
return from value in source
// where index++ >= count
from result in value.Enumerable(index++ >= count)
select result;
}
public static IEnumerable<TSource> SkipWhile<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
bool flag = false;
return from value in source
let _ = (!flag && !predicate(value)) && (flag = true)
from result in value.Enumerable(flag)
select result;
}
public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
int index = 0;
return from value in source
// where index++ < count
from result in value.Enumerable(index++ < count)
select result;
}
public static IEnumerable<TSource> TakeWhile<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
bool flag = true;
return from value in source
let _ = (predicate(value)) || (flag = false)
from result in value.Enumerable(flag)
select result;
}
public static IEnumerable<TSource> Union<TSource>(
this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
HashSet<TSource> hashSet = new HashSet<TSource>(comparer);
return from enumerable in new IEnumerable<TSource>[] { first, second }
from value in enumerable
// where hashSet.Add(value)
from result in value.Enumerable(hashSet.Add(value))
select result;
}
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return from value in source
from result in value.Enumerable(predicate(value))
select result;
}
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
int firstIndex = 0;
int secondIndex = 0;
return from firstValue in first
let currentFirstIndex = firstIndex++
let _ = secondIndex = 0
from secondResult in
(from secondValue in second
// where firstIndex2 == secondIndex++
// let secondIndex3 = secondIndex++
from secondResult in secondValue.Enumerable(currentFirstIndex == secondIndex++)
select secondResult)
select resultSelector(firstValue, secondResult);
}

Please notice that Skip, Take, Zip, SkipWhile and TakeWhile uses let clause as trick.

Unit tests#

Once again, a long tedious list of test code:

[TestClass()]
public class EnumerableSelectManyExtensionsTests
{
[TestMethod()]
public void ConcatTest()
{
int[] first = new int[] { 0, 1, 2 };
int[] second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Concat(first, second),
EnumerableSelectManyExtensions.Concat(first, second));
first = new int[] { };
second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Concat(first, second),
EnumerableSelectManyExtensions.Concat(first, second));
first = new int[] { 0, 1, 2 };
second = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Concat(first, second),
EnumerableSelectManyExtensions.Concat(first, second));
}
[TestMethod()]
public void DistinctTest()
{
int[] enumerable = new int[] { 0, 1, 2 };
EnumerableAssert.AreEqual(
Enumerable.Distinct(enumerable),
EnumerableSelectManyExtensions.Distinct(enumerable, EqualityComparer<int>.Default));
enumerable = new int[] { 0, 1, 1, 1, 2, 2 };
EnumerableAssert.AreEqual(
Enumerable.Distinct(enumerable),
EnumerableSelectManyExtensions.Distinct(enumerable, EqualityComparer<int>.Default));
}
[TestMethod()]
public void ExceptTest()
{
int[] first = new int[] { 0, 1, 2 };
int[] second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Except(first, second),
EnumerableSelectManyExtensions.Except(first, second, EqualityComparer<int>.Default));
first = new int[] { 0, 1, 2 };
second = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Except(first, second),
EnumerableSelectManyExtensions.Except(first, second, EqualityComparer<int>.Default));
first = new int[] { };
second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Except(first, second),
EnumerableSelectManyExtensions.Except(first, second, EqualityComparer<int>.Default));
first = new int[] { 0, 1, 2 };
second = new int[] { 2, 3, 4 };
EnumerableAssert.AreEqual(
Enumerable.Except(first, second),
EnumerableSelectManyExtensions.Except(first, second, EqualityComparer<int>.Default));
}
[TestMethod()]
public void GroupByTest()
{
int[] enumerable = new int[] { 0, 1, 2, 4, 5, 6 };
IGrouping<int, int>[] expected = Enumerable.GroupBy(enumerable, value => value % 3, value => value).ToArray();
IGrouping<int, int>[] actual = EnumerableSelectManyExtensions.GroupBy(enumerable, value => value % 3, value => value, EqualityComparer<int>.Default).ToArray();
Assert.AreEqual(expected.Count(), actual.Count());
expected.ForEach((group, index) =>
{
Assert.AreEqual(group.Key, actual[index].Key);
EnumerableAssert.AreEqual(group, actual[index]);
});
enumerable = new int[] { };
expected = Enumerable.GroupBy(enumerable, value => value % 3, value => value).ToArray();
actual = EnumerableSelectManyExtensions.GroupBy(enumerable, value => value % 3, value => value, EqualityComparer<int>.Default).ToArray();
Assert.AreEqual(expected.Count(), actual.Count());
expected.ForEach((group, index) =>
{
Assert.AreEqual(group.Key, actual[index].Key);
EnumerableAssert.AreEqual(group, actual[index]);
});
}
[TestMethod()]
public void GroupJoinTest()
{
Tuple<int, string>[] categories = new Tuple<int, string>[]
{
new Tuple<int, string>(1, "A"),
new Tuple<int, string>(2, "B"),
new Tuple<int, string>(3, "C"),
};
Tuple<int, string, int>[] products = new Tuple<int, string, int>[]
{
new Tuple<int, string, int>(1, "aa", 1),
new Tuple<int, string, int>(2, "bb", 1),
new Tuple<int, string, int>(3, "cc", 2),
new Tuple<int, string, int>(4, "dd", 2),
new Tuple<int, string, int>(5, "ee", 2),
};
Tuple<string, int>[] expected = Enumerable.GroupJoin(
categories,
products,
category => category.Item1,
product => product.Item3,
(category, categoryProducts) => new Tuple<string, int>(category.Item2, categoryProducts.Count())).ToArray();
Tuple<string, int>[] actual = EnumerableSelectManyExtensions.GroupJoin(
categories,
products,
category => category.Item1,
product => product.Item3,
(category, categoryProducts) => new Tuple<string, int>(category.Item2, categoryProducts.Count()),
EqualityComparer<int>.Default).ToArray();
Assert.AreEqual(expected.Count(), actual.Count());
expected.ForEach((product, index) =>
{
Assert.AreEqual(product.Item1, actual[index].Item1);
Assert.AreEqual(product.Item2, actual[index].Item2);
});
}
[TestMethod()]
public void IntersectTest()
{
int[] first = new int[] { 0, 1, 2 };
int[] second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Intersect(first, second),
EnumerableSelectManyExtensions.Intersect(first, second, EqualityComparer<int>.Default));
first = new int[] { 0, 1, 2 };
second = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Intersect(first, second),
EnumerableSelectManyExtensions.Intersect(first, second, EqualityComparer<int>.Default));
first = new int[] { };
second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Intersect(first, second),
EnumerableSelectManyExtensions.Intersect(first, second, EqualityComparer<int>.Default));
first = new int[] { 0, 1, 2 };
second = new int[] { 2, 3, 4 };
EnumerableAssert.AreEqual(
Enumerable.Intersect(first, second),
EnumerableSelectManyExtensions.Intersect(first, second, EqualityComparer<int>.Default));
}
[TestMethod()]
public void JoinTest()
{
Tuple<int, string, int>[] products = new Tuple<int, string, int>[]
{
new Tuple<int, string, int>(1, "aa", 1),
new Tuple<int, string, int>(2, "bb", 1),
new Tuple<int, string, int>(3, "cc", 2),
new Tuple<int, string, int>(4, "dd", 2),
new Tuple<int, string, int>(5, "ee", 2),
};
Tuple<int, string>[] categories = new Tuple<int, string>[]
{
new Tuple<int, string>(1, "A"),
new Tuple<int, string>(2, "B"),
new Tuple<int, string>(3, "C"),
};
Tuple<string, string>[] expected = Enumerable.Join(
products,
categories,
product => product.Item3,
category => category.Item1,
(product, category) => new Tuple<string, string>(category.Item2, product.Item2)).ToArray();
Tuple<string, string>[] actual = EnumerableSelectManyExtensions.Join(
products,
categories,
product => product.Item3,
category => category.Item1,
(product, category) => new Tuple<string, string>(category.Item2, product.Item2),
EqualityComparer<int>.Default).ToArray();
Assert.AreEqual(expected.Count(), actual.Count());
expected.ForEach((product, index) =>
{
Assert.AreEqual(product.Item1, actual[index].Item1);
Assert.AreEqual(product.Item2, actual[index].Item2);
});
products = new Tuple<int, string, int>[]
{
new Tuple<int, string, int>(1, "aa", 1),
new Tuple<int, string, int>(2, "bb", 1),
new Tuple<int, string, int>(3, "cc", 2),
new Tuple<int, string, int>(4, "dd", 2),
new Tuple<int, string, int>(5, "ee", 2),
};
categories = new Tuple<int, string>[] { };
expected = Enumerable.Join(
products,
categories,
product => product.Item3,
category => category.Item1,
(product, category) => new Tuple<string, string>(category.Item2, product.Item2)).ToArray();
actual = EnumerableSelectManyExtensions.Join(
products,
categories,
product => product.Item3,
category => category.Item1,
(product, category) => new Tuple<string, string>(category.Item2, product.Item2),
EqualityComparer<int>.Default).ToArray();
Assert.AreEqual(expected.Count(), actual.Count());
expected.ForEach((product, index) =>
{
Assert.AreEqual(product.Item1, actual[index].Item1);
Assert.AreEqual(product.Item2, actual[index].Item2);
});
products = new Tuple<int, string, int>[] { };
categories = new Tuple<int, string>[]
{
new Tuple<int, string>(1, "A"),
new Tuple<int, string>(1, "B"),
new Tuple<int, string>(1, "C"),
};
expected = Enumerable.Join(
products,
categories,
product => product.Item3,
category => category.Item1,
(product, category) => new Tuple<string, string>(category.Item2, product.Item2)).ToArray();
actual = EnumerableSelectManyExtensions.Join(
products,
categories,
product => product.Item3,
category => category.Item1,
(product, category) => new Tuple<string, string>(category.Item2, product.Item2),
EqualityComparer<int>.Default).ToArray();
Assert.AreEqual(expected.Count(), actual.Count());
expected.ForEach((product, index) =>
{
Assert.AreEqual(product.Item1, actual[index].Item1);
Assert.AreEqual(product.Item2, actual[index].Item2);
});
}
[TestMethod()]
public void SelectTest()
{
int[] enumerable = new int[] { 0, 1, 2 };
EnumerableAssert.AreEqual(
Enumerable.Select(enumerable, x => x.ToString()),
EnumerableSelectManyExtensions.Select4(enumerable, x => x.ToString()));
enumerable = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Select(enumerable, x => x.ToString()),
EnumerableSelectManyExtensions.Select4(enumerable, x => x.ToString()));
}
[TestMethod()]
public void SkipTest()
{
int[] enumerable = new int[] { 0, 1, 2 };
EnumerableAssert.AreEqual(
Enumerable.Skip(enumerable, 2),
EnumerableSelectManyExtensions.Skip(enumerable, 2));
enumerable = new int[] { 0, 1, 1, 1, 2, 2 };
EnumerableAssert.AreEqual(
Enumerable.Skip(enumerable, 0),
EnumerableSelectManyExtensions.Skip(enumerable, 0));
enumerable = new int[] { 0, 1, 1, 1, 2, 2 };
EnumerableAssert.AreEqual(
Enumerable.Skip(enumerable, -1),
EnumerableSelectManyExtensions.Skip(enumerable, -1));
enumerable = new int[] { 0, 1, 1, 1, 2, 2 };
EnumerableAssert.AreEqual(
Enumerable.Skip(enumerable, 100),
EnumerableSelectManyExtensions.Skip(enumerable, 100));
enumerable = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Skip(enumerable, 100),
EnumerableSelectManyExtensions.Skip(enumerable, 100));
}
[TestMethod()]
public void SkipWhileTest()
{
int[] enumerable = new int[] { 0, 1, 2 };
EnumerableAssert.AreEqual(
Enumerable.SkipWhile(enumerable, x => x > 0),
EnumerableSelectManyExtensions.SkipWhile(enumerable, x => x > 0));
enumerable = new int[] { 2, 1, 0, -1 };
EnumerableAssert.AreEqual(
Enumerable.SkipWhile(enumerable, x => x > 0),
EnumerableSelectManyExtensions.SkipWhile(enumerable, x => x > 0));
enumerable = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.SkipWhile(enumerable, x => x > 0),
EnumerableSelectManyExtensions.SkipWhile(enumerable, x => x > 0));
}
[TestMethod()]
public void TakeTest()
{
int[] enumerable = new int[] { 0, 1, 2 };
EnumerableAssert.AreEqual(
Enumerable.Take(enumerable, 2),
EnumerableSelectManyExtensions.Take(enumerable, 2));
enumerable = new int[] { 0, 1, 1, 1, 2, 2 };
EnumerableAssert.AreEqual(
Enumerable.Take(enumerable, 0),
EnumerableSelectManyExtensions.Take(enumerable, 0));
enumerable = new int[] { 0, 1, 1, 1, 2, 2 };
EnumerableAssert.AreEqual(
Enumerable.Take(enumerable, -1),
EnumerableSelectManyExtensions.Take(enumerable, -1));
enumerable = new int[] { 0, 1, 1, 1, 2, 2 };
EnumerableAssert.AreEqual(
Enumerable.Take(enumerable, 100),
EnumerableSelectManyExtensions.Take(enumerable, 100));
enumerable = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Take(enumerable, 100),
EnumerableSelectManyExtensions.Take(enumerable, 100));
}
[TestMethod()]
public void TakeWhileTest()
{
int[] enumerable = new int[] { 0, 1, 2 };
EnumerableAssert.AreEqual(
Enumerable.TakeWhile(enumerable, x => x > 0),
EnumerableSelectManyExtensions.TakeWhile(enumerable, x => x > 0));
enumerable = new int[] { 2, 1, 0, -1 };
EnumerableAssert.AreEqual(
Enumerable.TakeWhile(enumerable, x => x > 0),
EnumerableSelectManyExtensions.TakeWhile(enumerable, x => x > 0));
enumerable = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.TakeWhile(enumerable, x => x > 0),
EnumerableSelectManyExtensions.TakeWhile(enumerable, x => x > 0));
}
[TestMethod()]
public void UnionTest()
{
int[] first = new int[] { 0, 1, 2 };
int[] second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Union(first, second),
EnumerableSelectManyExtensions.Union(first, second, EqualityComparer<int>.Default));
first = new int[] { 0, 1, 2 };
second = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Union(first, second),
EnumerableSelectManyExtensions.Union(first, second, EqualityComparer<int>.Default));
first = new int[] { };
second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Union(first, second),
EnumerableSelectManyExtensions.Union(first, second, EqualityComparer<int>.Default));
first = new int[] { 0, 1, 2 };
second = new int[] { 2, 3, 4 };
EnumerableAssert.AreEqual(
Enumerable.Union(first, second),
EnumerableSelectManyExtensions.Union(first, second, EqualityComparer<int>.Default));
}
[TestMethod()]
public void WhereTest()
{
int[] enumerable = new int[] { 0, 1, 2 };
EnumerableAssert.AreEqual(
Enumerable.Where(enumerable, x => x > 0),
EnumerableSelectManyExtensions.Where(enumerable, x => x > 0));
enumerable = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Where(enumerable, x => x > 0),
EnumerableSelectManyExtensions.Where(enumerable, x => x > 0));
}
[TestMethod()]
public void ZipTest()
{
int[] first = new int[] { 0, 1, 2 };
int[] second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Zip(first, second, (x, y) => x * y),
EnumerableSelectManyExtensions.Zip(first, second, (x, y) => x * y));
first = new int[] { 0, 1, 2 };
second = new int[] { };
EnumerableAssert.AreEqual(
Enumerable.Zip(first, second, (x, y) => x * y),
EnumerableSelectManyExtensions.Zip(first, second, (x, y) => x * y));
first = new int[] { };
second = new int[] { 3, 4, 5 };
EnumerableAssert.AreEqual(
Enumerable.Zip(first, second, (x, y) => x * y),
EnumerableSelectManyExtensions.Zip(first, second, (x, y) => x * y));
first = new int[] { 0, 1, 2 };
second = new int[] { 2, 3, 4 };
EnumerableAssert.AreEqual(
Enumerable.Zip(first, second, (x, y) => x * y),
EnumerableSelectManyExtensions.Zip(first, second, (x, y) => x * y));
first = new int[] { 0, 1 };
second = new int[] { 2, 3, 4 };
EnumerableAssert.AreEqual(
Enumerable.Zip(first, second, (x, y) => x * y),
EnumerableSelectManyExtensions.Zip(first, second, (x, y) => x * y));
first = new int[] { 0, 1, 2 };
second = new int[] { 2, 3 };
EnumerableAssert.AreEqual(
Enumerable.Zip(first, second, (x, y) => x * y),
EnumerableSelectManyExtensions.Zip(first, second, (x, y) => x * y));
}
}
Category Theory via C# (15) IEnumerable<> Monad And LINQ: SelectMany For All
https://dixin.github.io/posts/category-theory-via-c-sharp-15-ienumerable-monad-and-linq-selectmany-for-all/
Author
Dixin
Published at
2018-12-16
License
CC BY-NC-SA 4.0