A missing feature in most dynamic languages: take a random key from an hash table.
antirez.com
antirez.com
myhash[myhash.keys.rand]
Is an O(n) operation, for no apparent reason. At least in 1.9, they're obviously keeping track of the keys internally in an array to support the ordered hashes. However, when you do Hash.keys, it does a foreach on the hash and creates a brand new array.I'm still not sure why getting a random key is particularly useful, but the real problem is that getting the keys of a hash should be an O(1) operation, instead of an O(n) one.
You have N buckets, a hashing function for key => bucket #, and each bucket has a linked list of value pointers. You hash the key down to a bucket number, walk the list until you find your key, which will give you your value.
To get all the keys, you need to walk all the buckets' lists, which is O(n).
> they're obviously keeping track of the keys internally in an array to support the ordered hashes.
Which means that they can return them in an O(1) operation, but they choose not to for some reason.
Edit: I'm wrong- I just remembered why they can't return them, and it is because they're not storing the keys in an array. D'oh. They're using a doubly-linked list. So to return a list of keys you'd need to walk the linked list- an O(n) operation.
See: http://www.igvita.com/2009/02/04/ruby-19-internals-ordered-h... for more info.
Even better, assuming you _really_ need to get the value for a random key out of the hash, might be:
class Hash
def random
self[self.keys.sort{rand}.first]
end
end
...
myhash.random
This doesn't require activesupport, just for comparison.I am not very experienced with Ruby, but this sounds like a really bad idea? In the worst case, sort might run forever?
Don't know what sort algorithms Ruby uses by default, maybe for some it doesn't matter, but it seems best to not make assumptions about the underlying algo?
I was curious so I looked Array#rand in the Active Support source code[1], the implementation uses the following code to get a random element:
def rand
self[Kernel.rand(length)]
end
So, this is much better than sorting the whole array in random order and pulling one off the top :) But it still uses `rand`.[1] http://github.com/rails/rails/tree/e56b3e4c0b60b2b86f5ca9c5e...
Languages in general should leave out things that are specialized use cases but easy to implement using simpler pieces. If you fill a language's standard library up with "useful" stuff like this, you eventually end up with a morass like PHP.
hash keys atRandomhttp://docs.python.org/library/stdtypes.html#dict.popitem
Edit: This method returns arbitrary (not random) results; each element is not equally likely to be picked.
The algorithm the author uses is not a good example. A simpler way to find the approximate most common elements in a large collection is to use a heap to store the (object, count) pairs. Still O(1), and you can remove the element with the lowest count each time instead of getting an approximation.
I think lacker was probably thinking of a correct algorithm for finding the n largest values in a set, instead of the n most frequent values.
EDIT: I analyzed a bit the differences between Yale University algorithm using M conters and my algorithm. It is interesting that while the Yale's algo gives you all the top elements with a given minimal frequency it is O(M) because you have to decrement M counters for every new element not matching any of elements in the M registers.
My algorithm even if randomized (so you are not sure you track all the top M elements, but the output will be an approximation of that) can bring the M-probably-top-elements using O(1) time for element. The memory used is O(M) in both the algorithms. I wonder if the algorithm I proposed was described or not.
Note how you can trade time for accuracy selecting more accurately what element to drop to add the next one. If you sample three elements dropping the minimum of the three the accuracy will be a given one, but if you sample ten elements it will be different. I wonder if somebody is able here to analyze this algorithm more in depth.
This is probably also not the simplest way to get a reasonable approximation. Certainly those Hayes algorithms are better. You could also try something like, read in 2N elements, sort them, and drop the least-frequent N of them. Then read in N more and repeat. Pretty hacky though. You could also try bloom filters.
To sort the set from time to time is not valid under this assumptions for example. This is going to be M log M.
Registers is O(M).
p.s. M here is the number of top-elements tracked.
This code seems like it fails when the number of keys in the table exceeds the number of buckets. Assuming that 'table.size' returns the number of keys, and not the number of buckets, he'll also be hitting 'index out of bounds' errors.
Of course, I could be wrong. Pseudocode can have a funky syntax. :)