Is there a LINQ equivalent to the following?
static T Compose<T>(T source,
IEnumerable<Func<T, T>> funcs,
Func<T, bool> cond)
{
foreach (var func in funcs)
{
source = func(source);
if (cond(source)) break;
}
return source;
}
// This ends the composition as soon as a null is returned
var result = Compose(x, funcs, x => x == null);
Essentially, I wish to compose functions on top of each other, and break if the current value matches a particular condition.
result = f(g(h(x))) // if condition is met somewhere within this chain,
// it will immediately break and return
EDIT: I thought of something along the lines of
funcs.Aggregate(seed, (x, f) => cond(x) ? x : f(x));
but that looks confusing and also ends up enumerating over the entire collection, rather than breaking when the condition is met.
One way of doing this is to Aggregate
until the condition is met. So, I define an extension method:
public static TAccumulate AggregateUntil<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func,
Func<TAccumulate, bool> predicate)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (func == null)
throw new ArgumentNullException(nameof(func));
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
var accumulate = seed;
foreach (var item in source)
{
accumulate = func(accumulate, item);
if (predicate(accumulate)) break;
}
return accumulate;
}
We may now write ComposeUntil
as:
public static T ComposeUntil<T>(
this IEnumerable<Func<T, T>> source,
T seed,
Func<T, bool> predicate)
{
return source.AggregateUntil(seed, (x, f) => f(x), predicate);
}
And use it in the following manner:
var funcs = new List<Func<int, int>>
{
x => x + 1,
x => x + 2,
x => x + 3,
x => x + 4
};
var result = funcs.ComposeUntil(0, x => x >= 6);
// result == 6
This works nicely:
var funcs = new Func<string, string>[]
{
t => t + "1",
t => t + "2",
t => t + "3",
};
Func<string, bool> cond = t => t.Length == 3;
Func<string, string> func =
t => funcs.Aggregate(t, (a, f) => !cond(a) ? f(a) : a);
Console.WriteLine(func("A"));
It produces "A12"
a string of length 3
.
To avoid iterating the collection then you need to introduce a new operator, .Scan(...)
, which I get from the Reactive Extensions team's Interactive Extensions (NuGet "Ix-Main").
.Scan(...)
works like .Aggregate(...)
but it produces a value at each step.
Func<string, string> func =
t => funcs
.Scan(t, (a, f) => f(a))
.SkipWhile(x => !cond(x))
.FirstOrDefault();
That produces the same result as above, but doesn't iterate the entire collection.
Here are both options inserted into the Compose
method:
(1)
static T Compose<T>(T source,
IEnumerable<Func<T, T>> funcs,
Func<T, bool> cond)
{
return funcs.Aggregate(source, (a, f) => !cond(a) ? f(a) : a);
}
(2)
static T Compose<T>(T source,
IEnumerable<Func<T, T>> funcs,
Func<T, bool> cond)
{
return
funcs
.Scan(source, (a, f) => f(a))
.SkipWhile(t => !cond(t))
.FirstOrDefault();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With