patterncsharpMinor
Searching a sequence for a pattern
Viewed 0 times
patternforsearchingsequence
Problem
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.
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) => cu
public static IEnumerable SearchPattern(this IEnumerable seq, params Func[] matches)
{
Contract.Requires(seq != null);
Contract.Requires(matches != null);
Contract.Requires(matches.Length > 0);
var matchedItems = new List(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) => cu
Solution
Personally, here is how I would do it. I think this is pretty straight-forward. I think it is also no less efficient than the other (longer, more complex) answer. The method
Subarray is something I wrote before, I didn’t write it just for this./// Similar to , only for arrays. Returns a new
/// array containing items from the specified
/// onwards.
public static T[] Subarray(this T[] array, int startIndex, int length)
{
if (array == null)
throw new ArgumentNullException("array");
if (startIndex array.Length)
throw new ArgumentOutOfRangeException("length", "length cannot be negative or extend beyond the end of the array.");
T[] result = new T[length];
Array.Copy(array, startIndex, result, 0, length);
return result;
}
public static IEnumerable SearchPattern(this IEnumerable seq, params Func[] matches)
{
Contract.Requires(seq != null);
Contract.Requires(matches != null);
Contract.Requires(matches.Length > 0);
// No need to create a new array if seq is already one
var seqArray = seq as T[] ?? seq.ToArray();
// Check every applicable position for the matching pattern
for (int j = 0; j
matches[i](seqArray.Subarray(j, i), seqArray[i + j])))
{
// ... yield it
yield return seqArray.Subarray(j, matches.Length);
// and jump to the item after the match so we don’t get overlapping matches
j += matches.Length - 1;
}
}
}Code Snippets
/// <summary>Similar to <see cref="string.Substring(int,int)"/>, only for arrays. Returns a new
/// array containing <paramref name="length"/> items from the specified
/// <paramref name="startIndex"/> onwards.</summary>
public static T[] Subarray<T>(this T[] array, int startIndex, int length)
{
if (array == null)
throw new ArgumentNullException("array");
if (startIndex < 0)
throw new ArgumentOutOfRangeException("startIndex", "startIndex cannot be negative.");
if (length < 0 || startIndex + length > array.Length)
throw new ArgumentOutOfRangeException("length", "length cannot be negative or extend beyond the end of the array.");
T[] result = new T[length];
Array.Copy(array, startIndex, result, 0, length);
return result;
}
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);
// No need to create a new array if seq is already one
var seqArray = seq as T[] ?? seq.ToArray();
// Check every applicable position for the matching pattern
for (int j = 0; j <= seqArray.Length - matches.Length; j++)
{
// If this position matches...
if (Enumerable.Range(0, matches.Length).All(i =>
matches[i](seqArray.Subarray(j, i), seqArray[i + j])))
{
// ... yield it
yield return seqArray.Subarray(j, matches.Length);
// and jump to the item after the match so we don’t get overlapping matches
j += matches.Length - 1;
}
}
}Context
StackExchange Code Review Q#1753, answer score: 4
Revisions (0)
No revisions yet.