Match: ([x], x → bool) → [(bool, x)]
Like Where except instead of filtering, it couples the predicate's result with the tested element of the sequence.
Example
var strs = new[] { "foo", "bar", "baz" };
foreach (var (matched, s) in strs.Match(s => Regex.IsMatch(s, @"^b")))
Console.WriteLine($"{s} => {matched}");
Prototype
public static IEnumerable<(bool Success, T Element)>
Match<T>(this IEnumerable<T> source, Func<T, bool> predicate) =>
from e in source
select (predicate(e), e);
It's very trivial so the only value proposition here is tuple construction with reasonably named tuple elements (although alternatives for Success would be Matched and IsMatch).
In your proposition, the projected value type is limited to bool.
I propose a more generalized Map method :
public static IEnumerable<(TSource element, TResult projection)> Map<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> func);
I reversed the order of the element in the tuple since this order is more logical for me.
Implementation is straightforward.
The two line below are equivalent:
source.Map(func);
source.Select(v=>(v, func(v));
In your proposition, the projected value type is limited to
bool.
But that's the whole idea! Map or Select already exists.
Actually Map doesn't exist in MoreLinq or I can't find it.
I neither can't find a method that do what I propose for Map.
It's generic and work with your example:
var strs = new[] { "foo", "bar", "baz" };
foreach (var (s, matched) in strs.Map(s => Regex.IsMatch(s, @"^b")))
Console.WriteLine($"{s} => {matched}");
It's replace .Select(v => (v, ...)) by .Map(v => ...).
every penny counts
Your foreach example is fine but it's one example where it looks good. However, the deconstruction into s and matched doesn't work (yet) with neither lambdas (when using method chaining) nor from binding in LINQ query syntax. The closest you can come to is this:
var outputs =
from (string Input, bool Matched) e in strs.Map(s => Regex.IsMatch(s, @"^b"))
select $"{e.Input} => {e.Matched}";
foreach (var output in outputs)
Console.WriteLine(output);
That's a Cast in disguise, and even worse, doesn't work without explicit types so throws anonymous types out of the window. You might as well just use a normal projection:
var outputs =
from e in strs.Select(s => (Input: s, Matched: Regex.IsMatch(s, @"^b")))
select $"{e.Input} => {e.Matched}";
I am completely happy with closing this as too trivial and I was hoping to be challenged to that conclusion. We reject trivial additions (especially one to two liners that don't provide much algorithmic intelligence) all the time. My idea, frankly, with Match was to somewhat complement Choose:
var items =
strs.Match(s => Regex.IsMatch(s, @"^b"))
.Pipe(e => Console.WriteLine($"{e.Element} => {e.Success}")) // log
.Choose(e => e);
foreach (var item in items)
Console.WriteLine(item);
This is why it's not just a generic mapping. This is just making the following Select easier, especially with respect to tuple naming and element ordering:
var items =
strs.Select(s => (Success: Regex.IsMatch(s, @"^b"), Element: s))
.Pipe(e => Console.WriteLine($"{e.Element} => {e.Success}"))
.Choose(e => e);
Your Map works fine too but requires Choose to reverse the tuple elements:
var items =
strs.Map(s => Regex.IsMatch(s, @"^b"))
.Pipe(e => Console.WriteLine($"{e.Element} => {e.Result}"))
.Choose(e => (e.Result, e.Element));
every penny counts
What are the pennies we are measuring or talking about here?