Vitter's reservoir sampling algorithm D: randomly selecting unique items
getkerf.wordpress.com
getkerf.wordpress.com
I don't think this is true!
First note that if you want to pick 98 values from 100, it is much simpler to instead decide which 2 to skip. Similarly when picking 75 out of 100 - better to just cross out 25. The hardest case is picking half: N/2 out of N, without replacement.
This is the Coupon Collector's problem, except we're stopping at half the coupons. We can use the analysis given in the Wikipedia page [1], except we only take the second half of the series.
This gives an expected time of
N * (H(N) - H(N/2))
where H(X) is the Xth Harmonic number.In the asymptote this yields:
N * (γ + ln(N) - γ - ln(N/2))
= N * (ln 2)
so the expected time of the "retry on duplicates" approach is linear in N. (Of course, the worst case is infinite, if you keep redrawing the same card.)edit: To complete the analysis, the requirement is that the answer be linear in k, the number of items selected. But we have k < N, so if we are O(N) we are also O(k) or better. So I think the argument works.
[1] https://en.wikipedia.org/wiki/Coupon_collector%27s_problem
For the cases where k > n/2, the "full table scan" portion is of course also linear in k.
We only need to consider values in the range 0 < k <= N/2. If k > N/2, we "pick the losers instead of winners" and that reduces k to that range.
The expected time E to pick k out of N, is N times the last k terms of H(N):
E(k) = N * (H(N) - H(N-k))
Applying the asymptote: E(k) ~ N * (γ + ln(N) - γ - ln(N-k))
= N * (ln(N) - ln(N-k))
We wish to determine its behavior as a function of k. Taking the derivative: dE/dk ~ N/(N - k)
which is positive and bounded above by 2 for 0 < k <= N/2: dE/dk <= 2
Using the fact that E(1) = 1, we have: E(k) <= 2k
So E cannot be worse than linear in k.There are several steps where your method could (will) fail. Since you don't introduce any machinery for the hand, you imply an O(k^2) checking algorithm. You also imply an unsorted hand. If you generate the hand in random order, then you fall to an O(k*lg(k)) sort. "Sorted" is an implicit requirement that we initially omit, but revisit later, because introducing it early makes the discussion harder to follow.
If you try to introduce machinery like a hashtable, the next thing you might suggest, you violate an implicit O(1) additional space requirement, which I've grudgingly reintroduced to the more-formal description ("why is this here?"). This conversation keeps going, but other failure paths we leave unexamined.
(I personally find Kevin's code very hard to follow)
The former is well known, while the latter is more obscure. This is probably because the extra complexity of Algorithm D is rarely necessary. If k and n are on the same order of magnitude, there is not much difference, and if k << n then simple random sampling would work nearly as well.
Use a multiplicative linear congruential generator with a period 2^n , where n is ceil(log2(m)), where m is the size of the list.
Seed the generator, start generating values and drop all values that are larger than m. In the worst case you will have to drop half of the values, in the best case you will drop none. This depends on how close to a power of 2 m is.
The generator is very short, one line of code, and very fast: one multiplication, one addition, and one bitshift. Space complexity is O(1).
If your tweets are generated out from an integer value, then you can use this value to generate them. The tweets will be unique (assuming the tweet generator will generate unique tweets from unique values), and you will have m of them.
Nevertheless, if you don't use the least significant bits, and if the constants are carefully chosen, MLCG passes most of the hardest statistical tests. For example it passes all DIEHARD tests, and most of TESTU01.
You also need an MLCG over the range 0..2^64-1 to match the given algorithm. I believe that requires some extra code to handle potential overflow in a MLCG, so adds a few more lines to your estimate.
It's not quite the same problem though; the algorithm described returns samples in sequential order. They mention performance reasons for this (C-f "tape sampling"). It also has the limitation that you need to know the sample size in advance.
A MLCG will return numbers all over the place; sorting them will take an additional O(n log n) time.
There are "N choose k" combinations, which in the worst case (k = N/2) grows as ~ 2^N. The number of combinations quickly outstrips the number of possible states of the LCG, and most combinations will never be found. For example, we have a list of length 16 and wish to choose 8. We have a choice of 16 distinct seeds, so there are only 16 different combinations (out of 12870 possible) that we can get for any given LCG.
https://www.cigital.com/papers/download/developer_gambling.p...
http://superuser.com/questions/712551/how-are-pseudorandom-a...
A SPN will generate a unique random permutation, from which you can draw your random list of unique items. With a little of post-ptocessing, you can generate random permutations of arbitrary size.
I have implemented it in matlab and python:
http://nl.mathworks.com/matlabcentral/fileexchange/36626-alg...
Nope, it just gave me ugly c code at the end.
For practical purposes when list.length << total.lenght shouldn't that be good enough?
The nicer way to do this is to have two lists. Put all of your cards into the first list. Choose a number between 1 and the length of list 1, then remove that element from the list and add it to the end of list 2. Repeat until list 1 is empty. It's linear-time (with a linked list), and there's no chance of duplicates.
But this isn't the scenario presented in the article - It's an algorithm for a very specialized version of a shuffle, where you need a random sample from an unbounded sequential (meaning, you don't know it's length ahead of time) list. The algorithm presented is for getting a good random sample from a dataset well beyond the size of memory, not for doing a perfect shuffle on a small list.
I have to add my $0.02. This is correct, your algorithm will give correct result with no chance of duplicates. However in some context this absolutely must not be done - I've worked in a company which made online gambling games and the auditing requirement was 1-to-1 correspondence of number drawn from RNG and the card player sees. For example Ace of spades must always be 49, whether it was drawn as first or last card from the deck.
>>> random.sample(xrange(0, 1000000000), 5)
[5258132, 23096612, 43529214, 91062733, 4912658]
This ran in a few milliseconds on my old macbook in python. It is an iterator over 1 billion element list. Looking at the source, they seem to track previous selections for large populations in a set, for which lookup of "x in y" is avg O(1).https://svn.python.org/projects/python/tags/r32/Lib/random.p...
So the inductive step is: given k'th sample a[k], let a[k+1] = a[k] + S, where S is drawn from a discrete exponential distribution with mean 1/λ = N/n. That's the `exp(log(rand()) * stuff)' part of the code.
This almost works; there's more details [1] if you want it to actually work, and have accurate statistics, not overflow N, etc.
[0] https://en.wikipedia.org/wiki/Exponential_distribution
[1] https://hal.archives-ouvertes.fr/file/index/docid/75929/file...
So if N is really large and doesn't fit into main memory, how does one iterate over such large list in a reasonable amount of time? When I am taught algorithm my sample input is relatively small, from 10,000 elements to 1M integer numbers. Big deal. But I am not sure how to really think when N is really huge and whether we'd ever really have to deal with 10B integers in one pass. In that case, it's more likely I am paging through my database cursor... someone please shine light.
It's like reading a big file. Just call read() repeatedly, and discard old data each time you call read() again.
You are selecting n of N items, where N is unknown, in one pass, where every item is treated as unique, and where n is your sample size.
First get n items from N in an array of size n of candidates, which we'll call C.
Then, for each item k in N until you reach the end you know the current highest count, so select a random integer representing a location in an array of that count. If it's 0 to n - 1 (assuming a zero indexed array) replace the item in C with the current item k.
This means that each item will have an equal chance of being a final candidate when you reach N. If you care about order of the random sample, you can always shuffle n after you reach N.
For example this command never finishes, but consumes a constant (small) amount of mem.
seq inf | shuf -n1Nonetheless, I find this coding style unreadable. "Y" is not a very good name for an exponentially distributed random variate, especially in a function that defines 24 variables with mostly meaningless names. :-(
:P
U = random_double(); negSreal=-S;
y1=exp(log(U*Nreal/qu1real)*nmin1inv);
Vprime = y1 * (-X/Nreal+1.0)*(qu1real/(negSreal+qu1real));
if(Vprime <= 1.0) break; y2=1.0; top = -1.0+Nreal; if(-1+n > S){bottom=-nreal+Nreal;limit=-S+N;}
else{bottom=-1.0+negSreal+Nreal; limit=qu1;}
Sometimes two statements per line, sometimes one. Sometimes spaces before and after equals, sometimes none. Sometimes multiple ifs, breaks and statements on one line.It looks like it was copy-pasted from a pdf. Maybe it was.
Considering there are somewhere over 1 trillion tweets (over 200 billion/year), this is a very easy problem, you do not even have to check for duplicates because the chance of getting a duplicate is so small it would be statistically unlikely to ever happen.
for i = 1 to k
pickedCardIndex = randomInt(n - i)
hand[i - 1] = deck[pickedCardIndex]
swap(deck[pickedCardIndex], deck[n - i])
As far as I can see this satisfies all conditions stated by the author (assuming that randomInt(x) produces a uniformly random integer in the range [0, x], there are n cards in the deck array, and the arrays are 0-indexed).http://www.ittc.ku.edu/~jsv/Papers/Vit87.RandomSampling.pdf
In short, it's worth reading Vitter's paper, the original post is just a post containing the C code, but the discussion of the algorithm and the comparison with the alternatives is in the paper.
(Edit: corrected the notation of O() as robrenaud suggested)
There is a difference between them. Little oh roughly means "grows strictly slower than, in the limit", where as big oh roughly means "grows no faster than, in the limit".
http://stackoverflow.com/questions/1364444/difference-betwee...
void increasingRandomSequence(arrayptr, base, k, n)
{
if (k == 0) return;
int i = randInt(n - k);
*(arrayptr) = base + i;
increasingRandomSequence(arrayptr + 1, base + i + 1, k - 1, n - (i + 1));
}
increasingRandomSequence(hand, 0, k, n) fills the array hand with a sequence which is picked with uniform distribution over all increasing sequences of length k with numbers in [0, n - 1].This is O(k), and we can shuffle this in O(k). Why doesn't this solve the problem?
Assuming I translated your above code correctly into Python, as:
import random
def increasingRandomSequence(base, k, n):
while k > 0:
i = random.randrange(n - k + 1)
yield base + i
base += i+1
k -= 1
n -= i+1
then I checked the above using: N = 6
counters = [0] * N
for i in range(100000):
for value in increasingRandomSequence(0, 5, N):
counters[value] += 1
print(counters)
and found a strong bias towards larger numbers. The counts are: [50033, 75037, 87438, 93686, 96900, 96906]
which means 5 is in the hand much more often than 0.