10
\$\begingroup\$

I built an extension method to cycle through all items of an IEnumerable starting at some index:

public static IEnumerable<T> Circle<T>(this IEnumerable<T> list, int startIndex)
{
    if (list != null)
    {
        List<T> firstList = new List<T>();
        using (var enumerator = list.GetEnumerator())
        {
            int i = 0;
            while (enumerator.MoveNext())
            {
                if (i < startIndex)
                {
                    firstList.Add(enumerator.Current);
                    i++;
                }
                else
                {
                    yield return enumerator.Current;
                }
            }
        }
        foreach (var first in firstList)
        {
            yield return first;
        }
    }
    yield break;
}

So when you do

Enumerable.Range(1,10).Circle(5);

The result is 6,7,8,9,10,1,2,3,4,5.

What I don't like is the use of the firstList variable. Is there a way to do this with the enumerator only, without storing an intermediate result?

EDIT:
I use an enumerator to iterate the enumerable not more than once.

\$\endgroup\$
10
\$\begingroup\$

A shorter form of Trevor's answer (but essentially doing the same thing):

public static IEnumerable<T> Circle<T>(this IEnumerable<T> list, int startIndex)
{
    return list.Skip(startIndex).Concat(list.Take(startIndex));
}

This will still lazy evaluate just like Trevor's answer, because Concat lazily evaluates.

Further more, if you have written a method to cycle through an IEnumerable, why not call it Cycle?

Lastly, I'd actually recommend taking advantage of lazy evaluation to give you a more useful method. The one below will continue to cycle indefinitely, starting with an optional index:

public static IEnumerable<T> Cycle<T>(this IEnumerable<T> list, int index = 0)
{

    var count = list.Count();
    index = index % count;

    while(true)
    {
        yield return list.ElementAt(index);
        index = (index + 1) % count;
    }
}

Then you can do something like:

foreach(var num in Enumerable.Range(1, 10).Cycle(4).Take(30))
{
    Console.WriteLine(num.ToString());
}

And this lets you specify just how many repeated items you want.

\$\endgroup\$
  • 1
    \$\begingroup\$ list.ElementAt will have terrible performance if the underlying collection doesn't support random access. Your algorithm will be O(n^2) in that case. \$\endgroup\$ – Pierre Menard May 28 '15 at 15:12
  • \$\begingroup\$ Very true, but my review was intended to suggest alternative methods of structuring the entire method. The exact way of getting the element isn't important in my answer at all and could trivially be replaced. \$\endgroup\$ – Nick Udell May 28 '15 at 15:14
  • 2
    \$\begingroup\$ Thanks, yes it's a bit shorter and it basically does the same thing. Your second method isn't useful in my use case (I really need one complete cycle without knowing the number of elements in advance), but I can imagine suitable use cases. I don't like that it requires a Take to come to an end though. (And I swapped two lines to make it work). \$\endgroup\$ – Gert Arnold May 29 '15 at 11:59
  • \$\begingroup\$ Haha, oops, looks like I copy-pasted those lines in the wrong order! \$\endgroup\$ – Nick Udell May 29 '15 at 16:08
1
\$\begingroup\$

The background, not mentioned in the question, was that I wanted this to work on IQueryables from a database backend. Therefore, repeated execution of the input variable should be prevented and the best solution for this more limited case is:

public static IEnumerable<T> Circle<T>(this IQueryable<T> query, int startIndex)
{
    var localList = query.ToList();
    return localList.GetRange(startIndex, localList.Count - startIndex)
                    .Concat(localList.GetRange(0,startIndex));
}

When testing this with 107 integers, the statement var localList = query.ToList(); took approx. 70% of the time. But List.GetRange is a highly efficient method, so all in all this is the winner.

\$\endgroup\$
  • \$\begingroup\$ A .ToList() in an extension method that takes IEnumerable<T> and returns IEnumerable<T> needs a darn good comment on it to explain exactly why the .ToList() is required. \$\endgroup\$ – Paul Suart Feb 6 at 16:28
  • \$\begingroup\$ @PaulSuart Well, obviously it is to prevent multiple execution of list which is a pretty standard pattern. \$\endgroup\$ – Gert Arnold Feb 6 at 22:37
  • 1
    \$\begingroup\$ But if the IEnumerable<T> you pass in is very large, and/or expensive to obtain... this method simply does not scale. For example, you can represent infinite collections with IEnumerable - this method would not work at all. \$\endgroup\$ – Paul Suart Feb 7 at 8:28
  • \$\begingroup\$ @PaulSuart My prime focus was on IQueryables from a database which shouldn't be enumerated multiple times. Since that wasn't mentioned in the question my answer has a smaller usability scope than Nick's. Your comment made me aware of this. For that reason I'm marking the other answer as accepted. \$\endgroup\$ – Gert Arnold Feb 8 at 15:44
  • 1
    \$\begingroup\$ @PaulSuart OK, good suggestion. Well, after all I think this Q&A looks better now than before your comments. \$\endgroup\$ – Gert Arnold Feb 8 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.