3

I have a collection of objects that I would like to iterate through to ultimately recognize some type of pattern. Currently I have a bunch of if statements that checks a flag for the expression. For example I have something along the lines of if (!foundFirstObjectOfTypeA). Once I found the first object of type A I would set the flag to true and we would no longer execute the block and move on to the next block. I have several types of patterns to recognize and because of this it has created a large block of if statements that are hard to understand and ugly.

Is there a way to peek/look past the identifier without the evaluation of the expression in the foreach loop? If I could do something along the lines of if (identifer == ObjectA && identifer.next == ObjectB) it would make my code much more readable and understandable and I could deal without having to set flags.

If there is no direct method/way does someone have some clever workarounds to simulate the effects I desire?

John Saunders
  • 157,405
  • 24
  • 229
  • 388
Chris
  • 755
  • 1
  • 12
  • 25
  • You probably want to explicitly create an enumerator for the collection first ... GetEnumerator maybe? Good question! – Hamish Grubijan Jul 23 '10 at 01:06
  • 2
    Please don't put tags like "[C#]" in the title. Once in the tags is enough. – John Saunders Jul 23 '10 at 01:34
  • 1
    This sounds like either a state machine or a d-graph. If you are not familiar with those, it might help you to do some research, as their theory might help you to come up with a good solution. – funkymushroom Jul 23 '10 at 02:17

5 Answers5

6

Use a for loop instead to increment i, then just look at object[i] and object[i+1]. What type of collection are you using? A list? An array? All a foreach loop does is hide the counter from you. There's absolutely no reason to use a foreach loop instead of a for loop, if I understand your situation.

This should work for any list and any sequence of objects:

Function:

public static bool ContainsSequence
    (System.Collections.Generic.IList<object> list, params object[] seq)
{
    for (int i = 0; i < list.Count() - seq.Count() + 1; i++)
    {
        int j;
        for (j = 0; j < seq.Count(); j++)
        {
            if (list[i + j] != seq[j])
                break;
        }
        if (j == seq.Count())
            return true;
    }
    return false;
}

Usage:

private static void Main(string[] args)
{
    var A = new object();
    var B = new object();
    var C = new object();
    var D = new object();

    var list = new[] {A, B, C, D};
    Console.WriteLine(ContainsSequence(list, B, C, D));
    Console.WriteLine(ContainsSequence(list, A, D, C, B, A, C));
    Console.WriteLine(ContainsSequence(list, A, B));
}

Output:

True
False
True

The function can be made simpler if you know how many objects you're looking for (if you're only looking for two, it's almost Hamid Nazari's answer - you just need to add AndrewS's bound checking, below.)

If you want the index of the start of the sequence, you can change the return type of my function to an int and return i instead of true (and return -1 instead of false.)

Community
  • 1
  • 1
dlras2
  • 8,048
  • 5
  • 46
  • 89
  • 1
    Yes - and remember to check within your for loop that you aren't on the last index otherwise object[i+1] with throw an error – Stuart Helwig Jul 23 '10 at 01:11
  • *@Stuart* +1 - A good point that will only come up when you least expect it. (Unless you're *certain* you will always match the pattern *somewhere* in the list.) – dlras2 Jul 23 '10 at 01:12
  • use 'for (int i = 0; i < list.Count - 1; i++)' and you'd be safe. – AndrewS Jul 23 '10 at 01:31
  • 1
    This could definitely be made into an extension method for the generic list – fletcher Jul 23 '10 at 04:52
2

I don't think there is direct support for it, but you can always emulate.

For example (part of a solution):

  static IEnumerable<Peekable<T>> ToPeekable(this IEnumerable<T> stream)
  {
     T prev = <get first item, and move...>
     foreach(T item in stream)
     {
       yield return new Peekable<T>(prev, item);
       prev = item;
    }
    yield return new Peekable<T>(item, default(T));
  }

Update: Missing parts

class Peekable<T>
{
    public Peekable(T current, T next)
    {
        Current = current;
        Next = next;
    }

    public T Current { get; private set; }
    public T Next { get; private set; }
}

static class PeekableAdaptor
{
    public static IEnumerable<Peekable<T>> ToPeekable<T>(this IEnumerable<T> stream)
    {
        var source = stream.GetEnumerator();

        if (!source.MoveNext()) 
            yield break;

        T prev = source.Current;

        while (source.MoveNext())
        {
            yield return new Peekable<T>(prev, source.Current);
            prev = source.Current;
        }

        yield return new Peekable<T>(prev, default(T));
    }
}

class Program
{
    static void Main(string[] args)
    {
        foreach (var pair in Enumerable.Range(1, 10).ToPeekable())
        {
            Console.WriteLine(pair.Current, pair.Next);
        }
    }
}
sukru
  • 2,171
  • 13
  • 15
  • I think this is *way* too complicated for what the OP wants... I think a simple `for` loop would be better. Always try and use the simplest solution! – dlras2 Jul 23 '10 at 01:11
  • @sukru, please elaborate on how this works. @Daniel - a for loop will not always be available. Assume that you are working with a ... stream , which does not know how many things will come. – Hamish Grubijan Jul 23 '10 at 01:15
  • @Hamish - then a while loop would work just as well, wouldn't it? I simply think you should work with the information the OP gave - a collection, and a `foreach` loop. – dlras2 Jul 23 '10 at 01:19
  • @Daniel Rasmussen - please add code examples of both `for` and `while` to your answer that are somewhat realistic. Code talks louder than words, and Devil is in the details. If your samples do not make my eyes bleed, then they are Ok. I need to see them. – Hamish Grubijan Jul 23 '10 at 01:22
  • @Hamish - Code posted. A while solution would be slightly trickier, but work very similarly. – dlras2 Jul 23 '10 at 01:35
  • @Hamish Grubijan I've added the complete implementation. One WARNING though. For the last iteration default(T) might not always be what you want. – sukru Jul 23 '10 at 18:19
0

If you use a for loop, you can do something like this.

for (int i = 0; i < list.Count - 1; i++)
    if (list[i] == ObjectA && list[i + 1] == ObjectB)
        ; // Your code
Hamid Nazari
  • 3,714
  • 2
  • 26
  • 30
  • 2
    The Capacity of a .Net List can be greater than its Count, and using list[i + 1] could cause an out of bounds exception. 'for (int i = 0; i < list.Count - 1; i++)' would be safer. – AndrewS Jul 23 '10 at 01:34
  • 1
    @Hamid - You're still missing the `- 1`, since you can never match the first object of a sequence with the last item of the list, and `Count` is a function, not a property, so you need parenthesis. – dlras2 Jul 23 '10 at 01:54
  • @Daniel Yeah you're right about the last item, but I beg to differ on `Count` being a function. – Hamid Nazari Jul 23 '10 at 02:08
  • 2
    The Count() method is a member of the Linq Namespace: http://msdn.microsoft.com/en-us/library/system.linq.enumerable.count.aspx. The Count Property is a member of the Generics Namespace: http://msdn.microsoft.com/en-us/library/a7f69ad7.aspx – Metro Smurf Jul 23 '10 at 02:48
  • If `list` is indeed a `List` then it does have a `Count` property so the point of whether it is a property or a function would be moot. Of course, this answer and most others assume the OP was using an indexable collection which was never actually stated. – Brian Gideon Jul 23 '10 at 04:13
  • @Metro Smurf - You're right - and to be honest I *had* thought it was a property myself. It's odd that it's inconsistent. @Brian - True, OP never stated that, but sometimes a little extrapolation is needed when not enough information is given (and OP seems to not be around for comment.) – dlras2 Jul 23 '10 at 07:22
0

IEnumerable is a very light forward-only iterator implementation. If you have a look at IEnumerator interface, there are only three members:

As a solution, you could iterate through the IEnumerable manually or use loops.

bool MoveNext()
void Reset()
object Current

So the only operations are to go forward, reset and to retrieve the current item. There is no built-in ability to peek what coming next.

IEnumerable version

bool someFlag = false;
IEnumerable<object> collection = new object[]{"string", true, 4, DateTime.Now};
using (var enumerator = collection.GetEnumerator()) {
  while (true ) {
   bool end = enumerator.MoveNext();
   if (!end) {
      if (enumerator.Current.GetType() == typeof(string)) {
        end = enumerator.MoveNext();
        if (!end && enumerator.Current.GetType() == typeof(bool))
          someFlag = true;
      }
   } 
   if (end) break;
  }
}

Loop version

bool someFlag = false;
List<object> collection = new object[]{"string", true, 4, DateTime.Now};
int i = 0;
while (true ) {
   bool end = i < collection.Count;
   if (!end) {
      if (collection[i] == typeof(string)) {
        i++;
        end = i < collection.Count;
        if (!end && collection[i] == typeof(bool))
          someFlag = true;
      }
   } 
   if (end) break;
  }
}
Community
  • 1
  • 1
Igor Zevaka
  • 69,206
  • 26
  • 104
  • 125
0

Using my answer to this question I came up with this variation as a possible solution. This will work for any type of collection; even those that cannot be indexed.

foreach (var item in ForEachHelper.Wrap(collection))
{
    Console.WriteLine("Position=" + item.Index.ToString());
    Console.WriteLine("Current=" + item.Current.ToString());
    if (item.HasNext)
    {
        Console.WriteLine("Next=" + item.Next.ToString());
    }
}

And here is the relevant code.

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Current { get; set; }
        public T Next { get; set; }
        public bool HasNext { get; set; }
    }

    public static IEnumerable<Item<T>> Wrap<T>(IEnumerable<T> enumerable)
    {
        IEnumerator<T> enumerator = enumerable.GetEnumerator();
        try
        {
            var item = new Item<T>();
            item.Index = 0;
            item.Current = default(T);
            item.Next = default(T);
            item.HasNext = false;
            if (enumerator.MoveNext())
            {
                item.Current = enumerator.Current;
                if (enumerator.MoveNext())
                {
                    item.Next = enumerator.Current;
                    item.HasNext = true;
                }
                while (item.HasNext)
                {
                    var next = new Item<T>();
                    next.Index = item.Index + 1;
                    next.Current = item.Next;
                    next.Next = default(T);
                    if (enumerator.MoveNext())
                    {
                        next.Next = enumerator.Current;
                        next.HasNext = true;
                    }
                    var current = item;
                    item = next;
                    yield return current;
                }
                yield return item;
            }
        }
        finally
        {
            enumerator.Dispose();
        }
    }
}
Community
  • 1
  • 1
Brian Gideon
  • 45,093
  • 12
  • 98
  • 145