Functional Patterns in C#: MapWhile
I had already solved Project Euler 23 in F# but wanted to translate my solution to C# for a friend. Those translations always tend to become very functional. This time around I needed a MapWhile method. This method should take a list and map a function over all elements as long as the while condition is true. This is actually very simple to implement, if you know how.
public static IEnumerable<T> MapWhile<T>(this IEnumerable<T> list, Func<int, T, T> mapFunction, Func<int, T, bool> whilePredicate)
{
var enumerator = list.GetEnumerator();
var index = 0;
while (enumerator.MoveNext())
{
if (!whilePredicate(index, enumerator.Current))
{
break;
}
yield return mapFunction(index, enumerator.Current);
index++;
}
}
}
And some unit tests on usage.
/* Test */
var result = list.MapWhile(x => x * 2, x => true);
/* Assert */
Assert.That(result.Sum(), Is.EqualTo(20));
}
[Test]
public void BreakWhenWhileConditionIsFalse()
{
/* Setup */
var list = new[] { 1, 2, 3, 4 };
/* Test */
var result = list.MapWhile(x => x * 2, x => x < 3);
/* Assert */
Assert.That(result.Sum(), Is.EqualTo(6));
}
[Test]
public void ReturnEmptyListWhenWhileConditionIsFalseByDefault()
{
/* Setup */
var list = new[] { 1, 2, 3, 4 };
/* Test */
var result = list.MapWhile(x => x * 2, x => false);
/* Assert */
Assert.That(result.Count(), Is.EqualTo(0));
}
[Test]
public void WorkWithStrings()
{
/* Setup */
var list = new[] { "Hello", "World", "Santa", "Claudius" };
/* Test */
var result = list.MapWhile((i, s) => i % 2 == 0 ? s.ToUpper() : s, (i, s) => true);
/* Assert */
Assert.That(string.Join(string.Empty, result), Is.EqualTo("HELLOWorldSANTAClaudius"));
}
}
Usage in Project Euler 23
This is how I solved Project Euler 23 with the MapWhile function. This is not the most effective solution imaginable, but it is short and readable.
public static void Main(string[] args) { // Just create this once var defaultRange = Enumerable.Range(1, AbundantSumMax);
// Returns true if n is abundant
Func<int, bool> isAbundant = n => Enumerable.Range(1, n / 2).Where(x => n % x == 0).Sum() > n;
// Get all abundants up to 28123
var abundants = defaultRange.Where(isAbundant).ToList();
// Get abundant sums
var abundantSums = GetAbundantSums(abundants);
// Invert abundant sums
var result = defaultRange.Except(abundantSums).Sum();
// Print
Console.WriteLine("Result: {0}", result);
}
private static IEnumerable<int> GetAbundantSums(IList<int> abundants) { // Unique set of numbers var result = new HashSet<int>();
// The Tortoise and the Hare
foreach (var abundant in abundants)
foreach (var abundantSum in abundants.MapWhile(x => x + abundant, x => x <= abundant))
{
result.Add(abundantSum);
}
return result;
}
You can find my F# solution at bitbucket.