Inefficient? but fun. :-)

J

jehugaleahsa

public static IEnumerable<T> RotateLeft<T>(IEnumerable<T>
collection)
{
using (IEnumerator<T> values = collection.GetEnumerator())
{
if (values.MoveNext())
{
T last = values.Current;
while (values.MoveNext())
{
yield return values.Current;
}
yield return last;
}
}
}


public static IEnumerable<T> RotateLeft<T>(IEnumerable<T> collection,
int toTheLeft)
{
if (toTheLeft < 0)
{
throw new ArgumentException("No negatives please");
}
IEnumerable<T> result = collection;
for (int i = 0; i != toTheLeft; ++i)
{
result = RotateLeft<T>(result);
}
return result;
}
 
J

Jon Skeet [C# MVP]

public static IEnumerable<T> RotateLeft<T>(IEnumerable<T>
collection)
{
using (IEnumerator<T> values = collection.GetEnumerator())
{
if (values.MoveNext())
{
T last = values.Current;
while (values.MoveNext())
{
yield return values.Current;
}
yield return last;
}
}
}


public static IEnumerable<T> RotateLeft<T>(IEnumerable<T> collection,
int toTheLeft)
{
if (toTheLeft < 0)
{
throw new ArgumentException("No negatives please");
}
IEnumerable<T> result = collection;
for (int i = 0; i != toTheLeft; ++i)
{
result = RotateLeft<T>(result);
}
return result;
}

Here's an alternative, for .NET 3.5:

public static IEnumerable<T> RotateLeft<T>(IEnumerable<T> collection)
{
return RotateLeft(collection, 1);
}

public static IEnumerable<T> RotateLeft<T>(IEnumerable<T> collection,
int places)
{
return collection.Skip(places).Concat(collection.Take(places));
}

Note that these could also be extension methods...
 
P

Peter Duniho

Here's an alternative, for .NET 3.5:

public static IEnumerable<T> RotateLeft<T>(IEnumerable<T> collection)
{
return RotateLeft(collection, 1);
}

public static IEnumerable<T> RotateLeft<T>(IEnumerable<T> collection,
int places)
{
return collection.Skip(places).Concat(collection.Take(places));
}

And since in 3.5 you get "Count()", it's simple enough to extend the idea
to handle left and right rotations:

public static IEnumerable<T> Rotate<T>(IEnumerable<T> collection, int
places)
{
if (places < 0)
{
places += collection.Count();
}

return collection.Skip(places).Concat(collection.Take(places));
}

Where "places" is assumed to be within range, of course (I figure that
since that seemed to be an assumption in your version, I'm allowed to make
the same assumption :)...note that the original handles out-of-range input
just fine, if inefficiently).

Though, one thing I'm wondering about is the efficiency of the Count()
method. Except for the 1-place rotate, the above would still always at
least as efficient as the originally-proposed "rotate by 1, once for each
place" approach. But it's tempting to throw in another call to Count() to
deal with out-of-range values of "place" (by using % to bring it back into
range), and if it's going to keep enumerating the entire collection each
time you call Count(), that would be nice to know (so you could cache the
result).

Even barring 3.5, the original need not be so inefficient. Here's a
version that does only left-shifts without all the repeated enumerations
(has the same assumption that the specified rotation is within range):

public static IEnumerable<T> RotateLeft<T>(IEnumerable<T>
collection, int crotate)
{
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
T[] rgt = new T[crotate];

for (int irotate = 0; irotate < crotate; irotate++)
{
enumerator.MoveNext()
rgt[irotate] = enumerator.Current;
}

while (enumerator.MoveNext())
{
yield return enumerator.Current;
}

foreach (T t in rgt)
{
yield return t;
}
}
}

The above also doesn't deal with out-of-range input, but that's just for
the purpose of illustration. It wouldn't be difficult to detect hitting
the end of the collection in the initial for() loop and handling that
appropriately (i.e. at that point, you now know how large the collection
is and can calculate the size-constrained rotation that is equivalent to
the asked-for one; if handling that case one would probably want to use a
List<T> instead of a T[] to store the intermediate result as well, to
avoid allocating an arbitrarily huge array).

Also, noting that the non-3.5 versions rely heavily on the custom
enumerator syntax, avoiding allocation of a lot of extra data, at least
for small rotates to the left. The original has no real memory bounding
issues, and my version above is fine as long as the number of rotations
doesn't get too large (and frankly, if it's large enough to cause an
out-of-memory problem, I suspect that the nested iteration approach would
cause a "this algorithm can't finish in a reasonable amount of time"
problem :) ).

How does the 3.5 syntax handle this? Will it internally allocate a copy
or copies of the collection for the purpose of providing the various
intermediate results? Or is there something behind the scenes that is
essentially equivalent to these custom enumerator approaches?

And finally, how does this relate to the article/web page you posted
awhile back regarding custom enumerators attached to asynchronous i/o?
The two non-3.5 versions here only enumerate through the collection once;
does the 3.5 version break if you've got an enumerator that is only valid
for a single enumeration?

Pete
 
J

Jon Skeet [C# MVP]

Also, noting that the non-3.5 versions rely heavily on the custom
enumerator syntax, avoiding allocation of a lot of extra data, at least
for small rotates to the left. The original has no real memory bounding
issues, and my version above is fine as long as the number of rotations
doesn't get too large (and frankly, if it's large enough to cause an
out-of-memory problem, I suspect that the nested iteration approach would
cause a "this algorithm can't finish in a reasonable amount of time"
problem :) ).

How does the 3.5 syntax handle this? Will it internally allocate a copy
or copies of the collection for the purpose of providing the various
intermediate results? Or is there something behind the scenes that is
essentially equivalent to these custom enumerator approaches?

It will call GetEnumerator() twice - there's no actual need for
buffering here. Skip() will just call MoveNext() as many times as it
needs to without using the data; Take() will call MoveNext() as many
times as it needs to and then stop.
And finally, how does this relate to the article/web page you posted
awhile back regarding custom enumerators attached to asynchronous i/o?
The two non-3.5 versions here only enumerate through the collection once;
does the 3.5 version break if you've got an enumerator that is only valid
for a single enumeration?

Yes, it would. On the other hand, it copes with the situation where the
rotation size is massive. Swings and roundabouts, as is so often the
case...
 
P

Peter Duniho

[...]
It will call GetEnumerator() twice - there's no actual need for
buffering here. Skip() will just call MoveNext() as many times as it
needs to without using the data; Take() will call MoveNext() as many
times as it needs to and then stop.

And I infer from the statement that "there's no actual need for buffering
here" that the Concat() method sequences through the two enumerators as
appropriate as the returned enumerator is actually processed?
Yes, it would. On the other hand, it copes with the situation where the
rotation size is massive. Swings and roundabouts, as is so often the
case...

Cool. Thanks.

Pete
 
J

Jon Skeet [C# MVP]

Peter Duniho said:
[...]
It will call GetEnumerator() twice - there's no actual need for
buffering here. Skip() will just call MoveNext() as many times as it
needs to without using the data; Take() will call MoveNext() as many
times as it needs to and then stop.

And I infer from the statement that "there's no actual need for buffering
here" that the Concat() method sequences through the two enumerators as
appropriate as the returned enumerator is actually processed?

Yes. Concat uses deferred execution.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top