Is there a way to use a foreach
loop to iterate through a collection backwards or in a completely random order?
you can do it backwards:
for (int i=col.count-1; i>0; i--){
DoSomething ( col.item[i]) ;
}
Not certain about the exact syntax, but that's the paradigm.
As for completely random order, you can access a collection element via it's index. To ensure you hit every item, you would need to keep track of which elements you had already processed (probably by copying the collection and then removing the element after access).
EDIT: More details for random access The code for the random access could look something like this:
collection c = originalCollection;
while (c.count > 0) {
int i = randomNumber(seed) mod c.count
element d = c[i];
c.remove(d);
DoSomething(d);
}
As of C# 2.0 you have the ability to use the yield keyword to implement custom iterators really easy. You can read more about the yield keyword over at MSDN http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx
You can think of a yield as the ability to return a value from inside a loop, but you should refer to the link above for a full explanation of what they are and what they can do.
I wrote a short example on how to implement a couple of custom iterators. I've implemented them as extension methods (http://msdn.microsoft.com/en-us/library/bb383977.aspx) to make the code a bit more stream lined and I also use array initializers (http://msdn.microsoft.com/en-us/library/aa664573.aspx) to set the initial values for the list of integers.
Neither extension methods nor array initializers are necessary for implementing custom iterators but they are nice features of c# 3.0 which helps write cleaner code
Here are my examples. It shows how to iterate over a list of integers by only returning Odd numbers, Even numbers, the numbers in reversed or in a completly random fashion.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<int> ints =
new List<int> { 1,2,3,4,5,6,7,8,9,10};
Console.WriteLine("Iterating over Odd numbers only.");
foreach (int i in ints.Odd())
{
Console.WriteLine(i);
}
Console.WriteLine("Iterating over Even numbers only.");
foreach (int i in ints.Even())
{
Console.WriteLine(i);
}
Console.WriteLine("Iterating over the list in reversed order.");
foreach (int i in ints.Reversed())
{
Console.WriteLine(i);
}
Console.WriteLine("Iterating over the list in random order.");
foreach (int i in ints.Random())
{
Console.WriteLine(i);
}
Console.ReadLine();
}
}
public static class ListExtensions
{
/// <summary>
/// Iterates over the list only returns even numbers
/// </summary>
/// <param name="list"></param>
public static IEnumerable<int> Even(this List<int> list)
{
foreach (var i in list)
{
if (i % 2 == 0)
{
yield return i;
}
}
}
/// <summary>
/// Iterates over the list only returns odd numbers
/// </summary>
public static IEnumerable<int> Odd(this List<int> list)
{
foreach (var i in list)
{
if (i % 2 != 0)
{
yield return i;
}
}
}
/// <summary>
/// Iterates over the list in reversed order
/// </summary>
public static IEnumerable<int> Reversed(this List<int> list)
{
for (int i = list.Count; i >= 0; i--)
{
yield return i;
}
}
/// <summary>
/// Iterates over the list in random order
/// </summary>
public static IEnumerable<int> Random(this List<int> list)
{
// Initialize a random number generator with a seed.
System.Random rnd =
new Random((int)DateTime.Now.Ticks);
// Create a list to keep track of which indexes we've
// already returned
List<int> visited =
new List<int>();
// loop until we've returned the value of all indexes
// in the list
while (visited.Count < list.Count)
{
int index =
rnd.Next(0, list.Count);
// Make sure we've not returned it already
if (!visited.Contains(index))
{
visited.Add(index);
yield return list[index];
}
}
}
}
}
I actually liked cfeduke approach with LINQ and it bugs me that it slipped my mind. To add to my previous example. If you want to do the Odd and Even iterations with the help of LINQ you can use
// Even
foreach (var i in ints.FindAll(number => number % 2 == 0))
{
Console.WriteLine(i);
}
// Odd
foreach (var i in ints.FindAll(number => number % 2 != 0))
{
Console.WriteLine(i);
}
Using an IList<T>
from the C5 Generic Collection Library, Reverse iteration is a feature, rather than extension:
foreach (var i in list.Reverse())
{
}
As well, you can use the Shuffle()
method to get a random ordering:
var listClone = (IList<T>) list.Clone();
listClone.Shuffle();
foreach (var i in listClone)
{
}
From my reading of the C# Language Specification, the foreach iteration statement depends on the struct/class/interface which is being iterated having the GetEnumerator() function defined upon it. The object returned by GetEnumerator() must have MoveNext() defined as a member function. MoveNext() is defined as accessing the "first" object in the list on its first call, then the "next" on subsequent calls, returning true until no further elements exist in the list, upon which it returns false.
The feature Domenic refers to, yield return, first appears in the 2.0 version of the specification, and does appear to be useful for this purpose. For version 1.1, your only option would be to derive a new struct/class/interface from your base and override GetEnumerator() to return a new IEnumerator, where the MoveNext() function would follow different rules in select the first collection element and any subsequent collection element.
My own recommendation would be to use an indexed collection, then use a for loop with an appropriate index calculation (here one could use a random number generator if necessary, with an integer array or some other technique for verifying that the same index value is not used twice) if you have to do this in actual practice.
Use random ordering
http://www.dailycoding.com/..using_linq.aspx
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "Davolio Nancy" });
list.Add(new Employee { Id = 2, Name = "Fuller Andrew" });
list.Add(new Employee { Id = 3, Name = "Leverling Janet" });
list.Add(new Employee { Id = 4, Name = "Peacock Margaret" });
list.Add(new Employee { Id = 5, Name = "Buchanan Steven" });
list.Add(new Employee { Id = 6, Name = "Suyama Michael" });
list.Add(new Employee { Id = 7, Name = "King Robert" });
list.Add(new Employee { Id = 8, Name = "Callahan Laura" });
list.Add(new Employee { Id = 9, Name = "Dodsworth Anne" });
list = list.OrderBy(emp => Guid.NewGuid()).ToList();