Generic Map/Reduce List Extensions in C#
Solution 1
These look very similar to extensions in Linq already:
//takes a function that matches the Func<T,R> delegate
listInstance.Aggregate(
startingValue,
(x, y) => /* aggregate two subsequent values */ );
//takes a function that matches the Action<T> delegate
listInstance.ForEach(
x => /* do something with x */);
Why is the 2nd example called Transform? Do you intend to change the values in the list somehow? If that's the case you may be better off using ConvertAll<T>
or Select<T>
.
Solution 2
According to this link Functional Programming in C# 3.0: How Map/Reduce/Filter can Rock your World the following are the equivalent in C# under the System.Linq namespace:
- map --> Enumerable.Select
- reduce --> Enumerable.Aggregate
- filter --> Enumerable.Where
Solution 3
I would use the built in Func delegates instead. This same code would work on any IEnumerable. Your code would turn into:
public static R Reduce<T,R>(this IEnumerable<T> list, Func<T,R> r, R initial)
{
var aggregate = initial;
foreach(var t in list)
aggregate = r(t,aggregate);
return aggregate;
}
public static void Transform<T>(this IEnumerable<T> list, Func<T> f)
{
foreach(var t in list)
f(t);
}
Solution 4
I would recommend to create extension methods that internally use LinQ like this:
public static IEnumerable<R> Map<T, R>(this IEnumerable<T> self, Func<T, R> selector) {
return self.Select(selector);
}
public static T Reduce<T>(this IEnumerable<T> self, Func<T, T, T> func) {
return self.Aggregate(func);
}
public static IEnumerable<T> Filter<T>(this IEnumerable<T> self, Func<T, bool> predicate) {
return self.Where(predicate);
}
Here some example usages:
IEnumerable<string> myStrings = new List<string>() { "1", "2", "3", "4", "5" };
IEnumerable<int> convertedToInts = myStrings.Map(s => int.Parse(s));
IEnumerable<int> filteredInts = convertedToInts.Filter(i => i <= 3); // Keep 1,2,3
int sumOfAllInts = filteredInts.Reduce((sum, i) => sum + i); // Sum up all ints
Assert.Equal(6, sumOfAllInts); // 1+2+3 is 6
(See https://github.com/cs-util-com/cscore#ienumerable-extensions for more examples)
Solution 5
You might want to add a way to do a map but return a new list, instead of working on the list passed in (and returning the list can prove useful to chain other operations)... perhaps an overloaded version with a boolean that indicates if you want to return a new list or not, as such:
public static List<T> Transform<T>(this List<T> list, TransformFunction<T> f,
params object [] args)
{
return Transform(list, f, false, args);
}
public static List<T> Transform<T>(this List<T> list, TransformFunction<T> f,
bool create, params object [] args)
{
// Add code to create if create is true (sorry,
// too lazy to actually code this up)
foreach(var t in list)
f(t,args);
return list;
}
Comments
-
Kazim Zaidi about 4 years
I am writing a few extensions to mimic the map and reduce functions in Lisp.
public delegate R ReduceFunction<T,R>(T t, R previous); public delegate void TransformFunction<T>(T t, params object[] args); public static R Reduce<T,R>(this List<T> list, ReduceFunction<T,R> r, R initial) { var aggregate = initial; foreach(var t in list) aggregate = r(t,aggregate); return aggregate; } public static void Transform<T>(this List<T> list, TransformFunction<T> f, params object [] args) { foreach(var t in list) f(t,args); }
The transform function will cut down on cruft like:
foreach(var t in list) if(conditions && moreconditions) //do work etc
Does this make sense? Could it be better?