Good Knuth, Bad Knuth
dev.netcetera.org
dev.netcetera.org
It is obvious from the numbers, too: the loop goes from $length - 1 to 1, so it has $length - 1 iterations, in the first iteration there are $length - 1 possible values for $r, in the second $length - 2, and the last iteration will always swap elements 0 and 1, for a total of (N-1)! different paths through the program, which are clearly not sufficient to generate N! different permutations.
[Edit: I had an off-by-one in my calculations, too. I hope it is correct now.]
[Edit: Actually, there still is an error in the first sentence: the algoritm will never leave the current element in its place, so the only element that is guaranteed not to stay at the same position is the last one. Run this:
for my $i (1..1000000) {
my @a = (1..10);
my @b = shuffle(@a);
if ($b[10] == 10) {
die "Criticism is wrong $i";
}
}
]Final proof:
for my $i (1..1000000) {
my @a = (0..10);
my @b = shuffle(@a);
for $j (0..10) {
if ($b[$j] == $j) {
die "Criticism is wrong $i";
}
}
} (defun butnth (index list)
(append (subseq list 0 index)
(subseq list (1+ index))))
(defun shuffle (list)
(unless (null list)
(let ((index (random (list-length list))))
(cons (nth index list)
(shuffle (butnth index list))))))
Building a second sequence by randomly selecting nodes from the old sequnece and inserting them into the new sequence seems more intuitive to me. It certainly works better with lists. If I where working with arays, the OPs method would be more efficient.It makes me realize how much whether one begins with lists which allow cheap insertions or not affects both one's choice of algorithm and one's thinking.
Actually, the algorithm is O(n), it's essentially a list reversal with a call to random inserted. In psuedocode:
oldlist = [...]
newlist = []
if oldlist not empty
do
x = remove random from oldlist
insert x into newlist
repeat
Of course, I'm ignoring the complexity of 'butnth' in that assesment, but this was just a 'first instinct' sort of deal. (defun shuffle-in-place (l)
(cond ((null l) nil)
(t (rotatef (nth 0 l)
(nth (random (length l)) l))
(shuffle-in-place (rest l))))
l)
(defun shuffle (l)
(shuffle-in-place (copy-list l)))I do. Always.
One could use nconc instead of append, but that's not where the processor cycles are being lost as far as I can tell.
I'm also fairly certain there is no purely functional O(n) algorithm.
(defun nth (index list)
(if (zerop index)
(first index)
(nth (1- index) (rest list))))This is for you, since I've already had three today & the brainos in your nth implementation are a strong indication for its prescription:
http://www.espressoguide.net/resources/images/articles/A002I...
Cheers!
use List::Util qw(shuffle);
shuffle(@list); def shuffle(x):
for i in reversed(xrange(1, len(x))):
j = int(random() * (i+1))
x[i], x[j] = x[j], x[i]
In the first loop the last element in the list 'x' has a chance to be exchanged with itself or any of the other elements. Then the second to last element has a chance to be exchanged with itself or any other element.All the way down to the second to last element, which has the chance to be exchanged with itself or the first element.
This is exactly how the good Knuth shuffle algorithm works, right?
And of course it has the usual problem of generating permutations using a pseudo random number generater: Even for moderately sized collections, the number of permutations is far larger than the number of differnt initialization states of many common PRNGs, so even if an algorithm looks correct, it may not be able to generate all permutations. For example, a 63 bit linear congruential shift generator has 2^63 = 9x10^18 possible sequences, while there are about 52! = 80x10^66 permutations of 52 cards.
is there some random number generator in a C-like language i should investigate, as opposed to using srand() and rand()?
Actually, if your game is not about real money, this problem might be more of an academic curiosity, but there are some algorithms with very long periods, start at one of these pages to look for an algorithm with weaknesses that you can live with:
- http://en.wikipedia.org/wiki/Mersenne_twister
- http://en.wikipedia.org/wiki/Blum_Blum_Shub
- http://en.wikipedia.org/wiki/Pseudorandom_number_generator
This is a problem, but it will be a problem no matter what algorithm you choose. You can always use something like http://www.random.org/ or special hardware, but I'd just look to /dev/random and whatever your default psudo-random generator is for your platform.
2^32 = 4294967296
100! is about 10^158
So for just 100 items, you're already missing most of your outcome space. You might switch to a generator with a bigger seed. For this example with 100 items, you need at least
log(100 !) / log(2) = 524.764993
bits in your seed. For N items to be sorted, you need about NlogN bits in your seed. So if you're trying to actually make each outcome possible, then no matter which clever algorithm you use to go from random numbers to a shuffled sequence, you've already lost.
But, as I remarked earlier, if this is just for a game that is for fun where opponents can't cheat you or other players out of large amounts of money by predicting which permutations may or may not occurr, you may get away with whatever PRNG you have at hand and only swap is out once many players start to complain that they had this very hand one or two billon games ago.
(N.b.: this post ended up being mostly the same point as fhars, only phrased differently.)
However, this is tricky to achieve. It requires either:
- an infinite source of random numbers
- a psuedo random number generator whose pool size (the # of random numbers it can generate) is an even multiple of N! (If not, then it have bias towards certain shuffles almost by definition.)
Here's what happens when someone does it all wrong -- the wrong shuffle algorithm and, far more devastatingly, a poor PRNG choice:
http://www.cigital.com/papers/download/developer_gambling.ph...
(Consider that if your PRNG takes a 32-bit seed, there will be at most be 2^32 possible shuffles, even with a correctly perfect shuffling algorithm --- few enough for an attacker to pre-calculate every possible shuffle to use against you.)