I'd like to split a list into two lists, one which can be handled directly, and the other being the remainder, which will be passed down a chain to other handlers.
Input:
Output:
Does this already exist? Perhaps a Linq method that I'm not thinking of at the moment? Otherwise, does anyone have a good C# example?
Here is one simple method. Note that ToLookup
eagerly evaluates the input sequence.
List<int> list = new List<int> { 1, 2, 3, 4, 5, 6 };
var lookup = list.ToLookup(num => num % 2 == 0);
IEnumerable<int> trueList = lookup[true];
IEnumerable<int> falseList = lookup[false];
You can use GroupBy
to get lazy evaluation of the input sequence, but it's not quite as pretty:
var groups = list.GroupBy(num => num % 2 == 0);
IEnumerable<int> trueList = groups.Where(group => group.Key).FirstOrDefault();
IEnumerable<int> falseList = groups.Where(group => !group.Key).FirstOrDefault();
After some consideration and some rather rubbish ideas, I've come to the conclusion: don't try to bend LINQ into doing this for you.
Have a simple couple of loops that consume your input sequence, pass each element to the first "handler" that can cope with it, and either ensure your last handler catches everything or at worst return a List
rather than an IEnumerable
.
public static void Handle(
IEnumerable<T> source,
Action<T> catchAll,
params Func<T, bool>[] handlers)
{
foreach (T t in source)
{
int i = 0; bool handled = false;
while (i < handlers.Length && !handled)
handled = handlers[i++](t);
if (!handled) catchAll(t);
}
}
// e.g.
public bool handleP(int input, int p)
{
if (input % p == 0)
{
Console.WriteLine("{0} is a multiple of {1}", input, p);
return true;
}
return false;
}
Handle(
source,
i => { Console.WriteLine("{0} has no small prime factor"); },
i => handleP(i, 2),
i => handleP(i, 3),
...
);
This has the advantage of handling each element in the input order rather than dividing them into groups and losing the ordering prior to whatever you do subsequently.
I agree with Servy's answer, but after the comments going on I thought that this approach could be interesting:
static class EnumerableExtensions
{
public static IEnumerable<TSource> Fork<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> filter,
Action<TSource> secondary)
{
if (source == null) throw new ArgumentNullException("source");
//...
return ForkImpl(source, filter, secondary);
}
private static IEnumerable<TSource> ForkImpl<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> filter,
Action<TSource> secondary)
{
foreach(var e in source)
if (filter(e))
yield return e;
else
secondary(e);
}
}
this could be used like this:
var ints = new [] { 1,2,3,4,5,6,7,8,9 };
// one possible use of the secondary sequence: accumulation
var acc = new List<int>();
foreach (var i in ints.Fork(x => x % 2 == 0, t => acc.Add(t)))
{
//...
}
// later on we can process the accumulated secondary sequence
process(acc);
Here we do accumulation on the secondary sequence (the "false" values), but live processing of this secondary sequence would be possible too, therefore with just one enumeration of the source.
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