开发者

Calling a list of methods in a random sequence?

I have a list of 10 methods开发者_开发知识库. Now I want to call this methods in a random sequence. The sequence should be generated at runtime. Whats the best way to do this?


It is always astonishing to me the number of incorrect and inefficient answers one sees whenever anyone asks how to shuffle a list of things on StackOverflow. Here we have several examples of code which is brittle (because it assumes that key collisions are impossible when in fact they are merely rare) or slow for large lists. (In this case the problem is stated to be only ten elements, but when possible surely it is better to give a solution that scales to thousands of elements if doing so is not difficult.)

This is not a hard problem to solve correctly. The correct, fast way to do this is to create an array of actions, and then shuffle that array in-place using a Fisher-Yates Shuffle.

http://en.wikipedia.org/wiki/Fisher-Yates_shuffle

Some things not to do:

  • Do not implement Fischer-Yates shuffle incorrectly. One sees more incorrect than correct implementations of this trivial algorithm. In particular, make sure you are choosing the random number from the correct range. Choosing it from the wrong range produces a biased shuffle.

  • If the shuffle algorithm must actually be unpredictable then use a source of randomness other than Random, which is only pseudo-random. Remember, Random only has 232 possible seeds, and therefore there are fewer than that many possible shuffles.

  • If you are going to be producing many shuffles in a short amount of time, do not create a new instance of Random every time. Save and re-use the old one, or use a different source of randomness entirely. Random chooses its seed based on the time; many Randoms created in close succession will produce the same sequence of "random" numbers.

  • Do not sort on a "random" GUID as your key. GUIDs are guaranteed to be unique. They are not guaranteed to be randomly ordered. It is perfectly legal for an implementation to spit out consecutive GUIDs.

  • Do not use a random function as a comparator and feed that to a sorting algorithm. Sort algorithms are permitted to do anything they please if the comparator is bad, including crashing, and including producing non-random results. As Microsoft recently found out, it is extremely embarrassing to get a simple algorithm like this wrong.

  • Do not use the input to random as the key to a dictionary, and then sort the dictionary. There is nothing stopping the randomness source from choosing the same key twice, and therefore either crashing your application with a duplicate key exception, or silently losing one of your methods.

  • Do not use the algorithm "Create two lists. Add the elements to the first list. Repeatedly move a random element from the first list to the second list, removing the element from the first list". If the list is O(n) to remove an item then this is an O(n2) algorithm.

  • Do not use the algorithm "Create two lists. Add the elements to the first list. Repeatedly move a random non-null element from the first list to the second list, setting the element in the first list to null." Also do not do this crazy equivalent of that algorithm.If there are lots of items in the list then this gets slower and slower as you start hitting more and more nulls.


New, short answer

Starting from where Ilya Kogan left off, totally correct after we had Eric Lippert find the bug:

var methods = new Action[10];
var rng = new Random();
var shuffled = methods.Select(m => Tuple.Create(rng.Next(), m))
                      .OrderBy(t => t.Item1).Select(t => t.Item2);
foreach (var action in shuffled) {
    action();
}

Of course this is doing a lot behind the scenes. The method below should be much faster. But if LINQ is fast enough...

Old answer (much longer)

After stealing this code from here:

public static T[] RandomPermutation<T>(T[] array)
{
    T[] retArray = new T[array.Length];
    array.CopyTo(retArray, 0);

    Random random = new Random();
    for (int i = 0; i < array.Length; i += 1)
    {
        int swapIndex = random.Next(i, array.Length);
        if (swapIndex != i)
        {
            T temp = retArray[i];
            retArray[i] = retArray[swapIndex];
            retArray[swapIndex] = temp;
        }
    }

    return retArray;
}

the rest is easy:

var methods = new Action[10];
var perm = RandomPermutation(methods);

foreach (var method in perm)
{
    // call the method
}


Have an array of delegates. Suppose you have this:

class YourClass {
  public int YourFunction1(int x) { }
  public int YourFunction2(int x) { }
  public int YourFunction3(int x) { }
}

Now declare a delegate:

public delegate int MyDelegate(int x);

Now create an array of delegates:

MyDelegate delegates[] = new MyDelegate[10];
delegates[0] = new MyDelegate(YourClass.YourFunction1);
delegates[1] = new MyDelegate(YourClass.YourFunction2);
delegates[2] = new MyDelegate(YourClass.YourFunction3);

and now call it like this:

int result = delegates[randomIndex] (48);


You can create a shuffled collection of delegates, and then call all methods in the collection.

Here is an easy way of doing so using a dictionary. The keys of the dictionary are random numbers, and the values are delegates to your methods. When you iterate through the dictionary, it has the effect of shuffling.

var shuffledActions = actions.ToDictionary(
                                action => random.Next(), 
                                action => action);

foreach (var pair in shuffledActions.OrderBy(item => item.Key))
{
    pair.Value();
}
  • actions is an enumerable of your methods.
  • random is a of type Random.


Think that this is a list of objects and you want it to extract the objects randomly. You can get a random index using the Random.Next Method (always use current List.Count as parameter) and after that remove object from the list so it will not be drawn again.


When processing a list in a random order, the natural inclination is to shuffle a list.

Another approach is to just keep the list order, but randomly select and remove each item.

var actionList = new[]
                {
                    new Action( () => CallMethodOne() ),
                    new Action( () => CallMethodTwo() ),
                    new Action( () => CallMethodThree() )
                }.ToList();

var r = new Random();
while(actionList.Count() > 0) {
    var index = r.Next(actionList.Count());
    var action = actionList[index];
    actionList.RemoveAt(index);
    action();
}


I think:

  1. Via reflection get Method Objects;

  2. create an array of created Method Object;

  3. generate random index (normalize range);

  4. invoke method;

You can remove method from array to execute method one times.

Bye

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜