tags:

views:

2521

answers:

11

Is there a way to use a foreach loop to iterate through a collection backwards or in a completely random order?

A: 

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);
}
Stephen Wrighton
He said foreach.
Domenic
You're off by one. You need to use i-1 or do for (int i = col.Count - 1; i >= 0; i--)
Jon B
@Domenic - There's not a fundamental USAGE difference@Jon B - thanks!
Stephen Wrighton
You're removing items from the original collection in your random example - unless you create a copy of if.
Sam Meldrum
#Sam Meldrum - the first line of the random example is copying the original collection.
Stephen Wrighton
Uh, no, false, Collection is presumably a reference type, so it is indeed removed from the original.
Domenic
+1  A: 

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.

Domenic
+9  A: 

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)
{
}
cfeduke
The i=> rnd.Next() is a little dangerous - it assumes that the delegate is only invoked once per item (i,e. an implementation detail). If not, the Sort could explode. As it happens, I think you get away with it - but an explicit Select first (to a temp object) might be adviseable...
Marc Gravell
@Marc: wow, very interesting point.
Domenic
Also, doing OrderBy(g => Guid.NewGuid()) is miles faster (and more random)
Kirschstein
Good point, I use this same technique when I randomly order results in a query in SQL Server but it didn't even cross my mind to use it here.
cfeduke
A: 

You could sort the List by supplying your own Comparator and iterate over that one.

Tigraine
+7  A: 

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);
Jacob Carpenter
cfeduke's OrderBy solution is much nicer.
Domenic
Though I must admit I like the extension method better, because really, extension methods are my hammer and all problems in my code are nails. Seriously, its gotten to that point for me. If only extension methods could be declared privately I'd be in heaven.
cfeduke
A: 

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>;
Zote
Uh, ew? How about test.OrderBy(t => rand.Next())? Granted, it's not a stable sort like yours is, but presumably it'd be easy to modify in that direction. Also, Environment.TickCount is unnecessary when initializing a Random.
Domenic
Domenic, I liked so much (t => rand.Next()) but I already writed this too big example :) and in my tests, I need to use Environment.TickCount because without it, I get same results too many times...
Zote
A: 

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.

+1  A: 

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];
                }
            }
        }
    }
}
TheCodeJunkie
+1  A: 

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);
}
TheCodeJunkie
+1  A: 

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)
{
}
Marcus Griep
A: 

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();
Ramesh Soni