Show HN: A visual explanation of Fisher–Yates shuffle
bost.ocks.org
bost.ocks.org
t = array[--m];
Well I don't know if it trips me up so much as it makes me think too much about syntax especially when the focus is on the algorithm itself. When writing educational code I find the following more effective: m -= 1;
t = array[m];
Anyone else have any thoughts?In fact, I was actually thinking more of this example:
i = Math.floor(Math.random() * n--);
when I stumbled across your comment.Otherwise, that's a minor criticism and it's a good page overall. In fact, it was one of the interview questions I was asked when I interviewed at Microsoft a long time ago. At the time, I didn't realize the algorithm actually had a name.
't' equals the value of 'array' at index 'm' minus 1 and 'm' equals itself minus 1.
vs 'm' equals itself minus 1.
't' equals the value of 'array' at index 'm'.
To me one of those seems more concise for writing production code, and the other more useful for teaching algorithms.It sounds confusing that the pieces of such a statement are not executed in the order that they are written, but in reality it's not any more strange than the fact that "a = b;" reads "evaluate b and store in a".
The real problem happens when there are multiple operators like that in the same statement, or multiple appearances of a post/pre-inc/dec-remented variable and you need to parse operator precedence in order to figure out the order of execution. As usual, abusing the syntax leads to confusing code, but simple and straightforward cases like "a = b[--i];" are cleaner and easier to read for anyone competent enough to matter.
I think that in that particular case the fact the array[m] is used in the next line makes things not as obvious as they could be visually. I feel that the following requires less reading:
m -=1;
t = array[m];
array[m] = array[i];
array[i] = t;
To me, it's faster to parse.I don't name my functions benmathes_foo().
Unfortunately, it can be hard to generate names which are a) descriptive, b) distinct, c) short. But we've done pretty okay with sorting algorithms, and I definitely feel that more is possible.
I'm not complaining about Fisher or Yates, who are just following the example their/our research community uses.
// swap and pop in C++11
int offset = 2; // remove the second element from the vector
int end = vec.size() - 1;
auto tmp = vec[offset];
vec[offset] = vec[end];
vec.pop();
// `tmp` now contains the value removed from the std::vectorYou can implement this directly with hash sets in O(n) time, assuming you're willing to treat hash set insertion and deletion as O(1) operations:
# O(n) time
def shuffle(elems):
elems = set(elems) # O(n) time
xs = []
while elems:
x = choose(elems) # O(1) time (expected)
xs.append(x)
elems.remove(x) # O(1) time
return xs
This whole approach is obvious when you stop thinking of the problem as shuffling an array in place and start thinking of it as randomly generating a permutation of elements drawn from a set. In the final analysis, the optimized in-place implementation of the algorithm with the swap trick does provide in-place shuffling; the way the two halves of the array are used to contain respectively the partial result and an auxiliary set of remaining elements is very much like heap sort.Not coincidentally, if you interpret the algorithm as I wrote it above monadically, then it can be used to generate either a random permutation or all permutations, depending on whether choose() is interpreted in the probability monad or the list (non-determinism) monad. This shows conclusively that the shuffling algorithm is really just the standard combinatorial generation algorithm for permutations adapted to random sampling. If so, you'd also expect the swap trick to be useful for generating all permutations in place. Well, of course:
void perms(int *xs, int n, int i)
{
if (i == n) {
for (int j = 0; j < n; j++)
printf("%d ", xs[j]);
puts("");
return;
}
for (int j = i; j < n; j++) {
swap(xs + i, xs + j);
perms(xs, n, i + 1);
swap(xs + i, xs + j);
}
}It seems pretty obvious to me, so I'm not sure what is confusing you.
class Monad m => ChoiceMonad m where
choice :: [a] -> m a
For the list monad, choice is just the identity function. For the probability monad, choice picks a random element from the list. (Ideally choice would take a random access structure like a set as its argument, but I'm doing it the simplest way here.)To implement shuffle, you more or less just transliterate the Python code as you'd expect.
Before you tackle this, make you sure completely understand how the same situation plays out for the simpler case of subsets:
subset :: ChoiceMonad m => [a] -> m [a]
subset [] = return []
subset (x:xs) = do b <- choice [True, False]
xs' <- subset xs
return (if b then x:xs' else xs')
Over the list monad, this generates a list of all subsets. Over the probability monad, it generates a random subset. shuffle :: (ChoiceMonad m, Eq a) => [a] -> m [a]
shuffle [] = return []
shuffle elems = do
x <- choice elems
xs <- shuffle (x `delete` elems)
return (x:xs)Does anyone have suggestions for an algorithm for me to use d3.js to illustrate visually? This is such an effective learning tool that I would probably benefit from some review. I was thinking of trying a graph or tree search algorithm, maybe's Dijkstra's. Any other suggestions?
The average of the sampled values will be normally distributed, but the values themselves will be uniform.
If you sample every number once from [0,100], the average is 50, but that doesn't imply that the distribution of the sampled elements is normal - it's uniform.
His question of whether "on average, does the next card come from the middle" (which it doesn't) isn't the same as "is the mean of the next sampled card in the middle" (which it is).
The language was a little misleading - he said "on average" when he intended "most frequently".
And forget about t entirely.