Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find rank of a decimal number based on function F( N ) = rank

I found this question here, but this is not the answer i am looking for. Hence, posting again.

A function of the form:

F( N ) = rank

means that Given a number N in decimal representation, its rank is given as:

Starting from 0 to N, how many numbers are there with same number of set bits in its
binary representation.

I will go through an example to make it more clear.

N = 6 ( 0110 )
Its rank is 3.
1. 3 ( 0011 )
2. 5 ( 0101 )
3. 6 ( 0110 )

Now, given a number, find its rank.

The obvious approach is to start from 0 and check for number of set bits for each number till N-1.

The question is:

Is there any logN solution?

like image 246
Green goblin Avatar asked Sep 03 '12 18:09

Green goblin


1 Answers

Yes, there is a log n solution.

Let n have k bits set, the most significant bit being in position p (starting to count positions from 0, so 2^p <= n < 2^(p+1)). Then there are pCk (binomial coefficient, also choose(p,k)) ways to place k bits in the positions 0, ..., p-1, all these give numbers with exactly k set bits that are smaller than n. If k == 1, that's it, otherwise there remain the numbers with k set bits and the p-th bit set that are smaller than n to consider. Those can be counted by determining the rank of n - 2^p.

Code (not optimal, does some unnecessary recomputation, and doesn't all that it could do to avoid overflow):

unsigned long long binom(unsigned n, unsigned k) {
    if (k == 0 || n == k) return 1;
    if (n < k) return 0;
    if (k > (n >> 1)) k = n-k;
    unsigned long long res = n, j;
    // We're not doing all we can to avoid overflow, as this is a proof of concept,
    // not production code.
    for(j = 2; j <= k; ++j) {
        res *= (n+1-j);
        res /= j;
    }
    return res;
}

unsigned popcount(unsigned long long n) {
    unsigned k = 0;
    while(n) {
        ++k;
        n &= (n-1);
    }
    return k;
}

unsigned long long rank(unsigned long long n) {
    if (n == 0) return 1;
    unsigned p = 0, k = popcount(n);
    unsigned long long mask = 1,h = n >> 1;
    while(h > 0) {
        ++p;
        h >>= 1;
    }
    mask <<= p;
    unsigned long long r = binom(p,k);
    r += rank(n-mask);
    return r;
}

Tested in a loop for 0 <= n < 10^8 to check for mistakes without any mismatch found.

Check output here.

like image 133
Daniel Fischer Avatar answered Oct 17 '22 23:10

Daniel Fischer