C#: Circular enumeration of IEnumerable<T>
Asked Answered
S

6

10

There is the command hierarchy in my current application.

public interface ICommand
{
    void Execute();
}

So, some commands are stateful, some are not.

I need to enumerate IEnumerable in the circular way for some command implementation during command execution.

public class GetNumberCommand : ICommand
{
    public GetNumberCommand()
    {
        List<int> numbers = new List<int>
            {
                1, 2, 3
            };
    }
    
    public void Execute()
    {
        // Circular iteration here.
        // 1 => 2 => 3 => 1 => 2 => 3 => ...
    }

    public void Stop()
    {
        // Log current value. (2 for example)
    }
}

Execute is called from time to time, so it is necessary to store the iteration state.

How to implement that circular enumeration?

I have found two solutions:

  1. Using the IEnumerator<T> interface. It looks like:

     if (!_enumerator.MoveNext())
     {
         _enumerator.Reset();
         _enumerator.MoveNext();
     }
    
  2. Using the circular IEnumerable<T> (yield forever the same sequence): “Implementing A Circular Iterator” - HonestIllusion.Com.

Maybe, there are more ways to achieve it.
What would you recommend to use and why?

Scene answered 22/5, 2012 at 5:57 Comment(1)
For the sake of reusability, I'd implement a Circular Iterator. Actually I just need one right now, and came here hoping to get some inspiration. But it seems that none of the solutions seems to work. Well, I had to implement one myself and I'm posting it here as an answer. Hope it helps others.Benzidine
I
1

You can use this extension method:

public static IEnumerable<T> Cyclic<T>(this IEnumerable<T> @this)
{
    while (true)
        foreach (var x in @this)
            yield return x;
}

In that way:

public class GetNumberCommand : ICommand
{
    private readonly IEnumerator<int> _commandState = new[] { 1, 2, 3 }.Cyclic().GetEnumerator();

    public void Execute()
    {
        _commandState.MoveNext();
        var state = _commandState.Current;
        
        //
        // Do stuff with state
        //
    }

    public void Stop()
    {
        var state = _commandState.Current;                
        // Log state value. (2 for example)
    }
}
Infuriate answered 13/10, 2021 at 10:24 Comment(2)
It seems that your solution is the best (simple and concise) one. It is the same as the solution #2 (see the question) combined with the iteration using the IEnumerator<T> interface.Scene
Just a TODO-note for readers. The appropriate disposal of the IEnumerator<T> instance must be implemented.Scene
K
6

Instead of dealing with IEnumerator interface,

foreach (var x in GetSomething())
{
     if (someCondition) break;
}



public IEnumerable<int> GetSomething()
{
    List<int> list = new List<int>() { 1, 2, 3 };
    int index=0;

    while (true)
        yield return list[index++ % list.Count];
}
Karlkarla answered 22/5, 2012 at 6:3 Comment(7)
I think you will get overflow on index very soon if do like thatLorou
In my case, Execute is called from time to time, so it is necessary to store the iteration state.Scene
see my solution in the end. you can store index in the collection and pass it to iteratorLorou
Okay, I see the point. Out of curiosity, how about IEnumerable<T> (not List<T>)?Scene
I used List only for example, you can use whatever you want :) My example is very ugly and not for production code. It was just being used to show the idea.Lorou
@Serge , "Execute is called from time to time, so it is necessary to store the iteration state." Is it so hard to move int index=0; out of GetSomething ?Karlkarla
@EugenePetrov "I think you will get overflow on index very soon if do like that." , Yes if you want to iterate 1 => 2 => 3 => 1 => 2 .... loop more than 2 billions times it may overflow. thanks to god that long exists ;)Karlkarla
B
5

Here's one I just implemented as an extension.

using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace DroopyExtensions
{
    public static class CircularEnumaratorExtensionMethod
    {

        public static IEnumerator<T> GetCircularEnumerator<T>(this IEnumerable<T> t) 
        {
            return new CircularEnumarator<T>(t.GetEnumerator());
        }

        private class CircularEnumarator<T> : IEnumerator<T>
        {
            private readonly IEnumerator _wrapedEnumerator;

            public CircularEnumarator(IEnumerator wrapedEnumerator)
            {
                this._wrapedEnumerator = wrapedEnumerator;
            }

            public object Current => _wrapedEnumerator.Current;

            T IEnumerator<T>.Current =>  (T)Current;

            public void Dispose()
            {

            }

            public bool MoveNext()
            {
                if (!_wrapedEnumerator.MoveNext())
                {
                    _wrapedEnumerator.Reset();
                    return _wrapedEnumerator.MoveNext();
                }
                return true;
            }

            public void Reset()
            {
                _wrapedEnumerator.Reset();
            }
        }
    }   
} 

To use it, all you have to do is

using DroopyExtensions;

class Program
{
    static void Main(string[] args)
    {
        var data = new List<string>() {"One", "Two", "Tree"};
        var dataEnumerator = data.GetCircularEnumerator();
        while(dataEnumerator.MoveNext())
        {
            Console.WriteLine(dataEnumerator.Current);
        }
    }
}
Benzidine answered 15/2, 2019 at 16:23 Comment(1)
I think you should swap the implementation of IEnumerator.Current and CircularEnumarator<T>.Current, because there is no need to unboxing.(_wrapedEnumerator should be IEnumerator<T> not IEnumerator)Pristine
A
1
while (!stop)
{
   foreach (var i in numbers)
   {
     // do something
   }
}
Anvil answered 22/5, 2012 at 6:0 Comment(1)
In my case, Execute is called from time to time, so it is necessary to store the iteration state.Scene
I
1

You can use this extension method:

public static IEnumerable<T> Cyclic<T>(this IEnumerable<T> @this)
{
    while (true)
        foreach (var x in @this)
            yield return x;
}

In that way:

public class GetNumberCommand : ICommand
{
    private readonly IEnumerator<int> _commandState = new[] { 1, 2, 3 }.Cyclic().GetEnumerator();

    public void Execute()
    {
        _commandState.MoveNext();
        var state = _commandState.Current;
        
        //
        // Do stuff with state
        //
    }

    public void Stop()
    {
        var state = _commandState.Current;                
        // Log state value. (2 for example)
    }
}
Infuriate answered 13/10, 2021 at 10:24 Comment(2)
It seems that your solution is the best (simple and concise) one. It is the same as the solution #2 (see the question) combined with the iteration using the IEnumerator<T> interface.Scene
Just a TODO-note for readers. The appropriate disposal of the IEnumerator<T> instance must be implemented.Scene
L
-1

I think, the most comfortable way wil be to implement custom collection with custom enumerator and encapsulate circular logic in it.

class Collection<T> : IEnumerable<T>
{
  bool circle;

  List<T> collection = new List<T>();

  public IEnumerable<T> IEnumerable<T>.GetEnumerator()
  {
     if(circle) return new CustomEnumerator<T>(this);
     return circle.GetEnumerator();
  }
}

class CustomEnumerator : Enumerator<T> {}

something like this...

Lorou answered 22/5, 2012 at 6:8 Comment(0)
T
-1

You can write a circular enumerable without yield returns.

  public class CircularEnumerable<T> : IEnumerable<T>
  {
    public CircularEnumerable (IEnumerable<T> sequence)
    {
      InfiniteLoop = sequence.Concat (this);
    }

    private readonly IEnumerable<T> InfiniteLoop;

    public IEnumerator<T> GetEnumerator ()
    {
      return InfiniteLoop.GetEnumerator ();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
    {
      return InfiniteLoop.GetEnumerator ();
    }
  }

public class GetNumberCommand : ICommand
{
    public GetNumberCommand()
    {
        List<int> numbers = new List<int>
            {
                1, 2, 3
            };
        infiniteLoopOnNumbers = new CircularEnumerable<int>(numbers).GetEnumerator();
    }

    IEnumerator<int> infiniteLoopOnNumbers; 

    public void Execute()
    {
        infiniteLoopOnNumbers.MoveNext();
    }

    public void Stop()
    {
        int value = infiniteLoopOnNumbers.Current;
    }
}
Thor answered 13/4, 2013 at 20:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.