Is there a way to use a foreach
loop to iterate through a collection backwards or in a completely random order?
views:
2521answers:
11you 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);
}
I don't think there is a way to do so directly, but it's pretty much as good
to use an extension method that returns a new collection via the yield return
keyword. These could come from a pre-existing library; the others have pointed out that LINQ has a Reverse
method, and things like OrderBy
would also work.
Example: if you use the LINQ extension method Reverse()
on IEnumerable<T>
, which uses yield return
to give the collection in reverse order, then doing a foreach(var myThing in myCollection.Reverse())
will enumerate through the collection in reverse order.
Important: yield return
is key. It means "when I enumerate this collection, then go fetch things." As opposed to the alternative of just constructing a new, reversed collection, which is highly inefficient and possibly has side effects.
Using System.Linq
you could do...
// List<...> list;
foreach (var i in list.Reverse())
{
}
For a random order you'd have to sort it randomly using list.OrderBy
(another Linq extension) and then iterate that ordered list.
var rnd = new Random();
var randomlyOrdered = list.OrderBy(i => rnd.Next());
foreach (var i in randomlyOrdered)
{
}
You could sort the List by supplying your own Comparator and iterate over that one.
As other answers mention, the Reverse()
extension method will let you enumerate a sequence in reverse order.
Here's a random enumeration extension method:
public static IEnumerable<T> OrderRandomly<T>(this IEnumerable<T> sequence)
{
Random random = new Random();
List<T> copy = sequence.ToList();
while (copy.Count > 0)
{
int index = random.Next(copy.Count);
yield return copy[index];
copy.RemoveAt(index);
}
}
Your usage would be:
foreach (int n in Enumerable.Range(1, 10).OrderRandomly())
Console.WriteLine(n);
Do you want to rand a collection and interect with it?
If yes, try this:
Random rand = new Random(Environment.TickCount);
test.Sort((string v1, string v2) => {
if (v1.Equals(v2))
{
return 0;
}
int x = rand.Next();
int y = rand.Next();
if (x == y)
{
return 0;
}
else if (x > y)
{
return 1;
}
return -1;
});
for (string item in test)
{
Console.WriteLn(item);
}
// Note that test is List<string>;
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.
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 reversed 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)
{
}
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();