I have written the following tiny extension method to help me when I'm working with sequences in which I have to find a pattern.
public static IEnumerable<T[]> SearchPattern<T>(this IEnumerable<T> seq, params Func<T[], T, bool>[] matches)
{
Contract.Requires(seq != null);
Contract.Requires(matches != null);
Contract.Requires(matches.Length > 0);
var matchedItems = new List<T>(matches.Length);
int itemIndex = 0;
foreach (T item in seq)
{
if (matches[matchedItems.Count](matchedItems.ToArray(), item))
{
matchedItems.Add(item);
if (matchedItems.Count == matches.Length)
{
yield return matchedItems.ToArray();
matchedItems.Clear();
}
}
else
{
if (matchedItems.Any())
{
foreach (T[] results in seq.Skip(itemIndex - matchedItems.Count + 1).SearchPattern(matches)) // is this a tail call? can it be optimized?
{
yield return results;
}
break;
}
}
itemIndex++;
}
}
It appears to be working fine, but I'm wondering if there's a better way to do it. Primarily I'm concerned with the recursive call (at the nested foreach), which makes the method very inefficient, not to mention potential stack-overflows when working with very large collections.
I know about tail-calls, and that they can be optimized into a loop (like in F#). I also recall having seen something about tail-calls on an IL level. What confuses me is that I'm already using the iterator pattern (yield return) - so is it a tail call at all? If it is, is it one I can eliminate?
Any input would be much appreciated!
Edit: usage sample:
var results = (new int[] { 0, 1, 2, 3, 3, 4, 4, 5, 7, 9 }).SearchPattern(
(prevs, curr) => true,
(prevs, curr) => prevs[0] == curr,
(prevs, curr) => curr % 2 != 0
); // => { [ 4, 4, 5 ] }
yield
statements -- are very inefficient. \$\endgroup\$