Author tim.peters
Recipients kellerfuchs, mark.dickinson, rhettinger, serhiy.storchaka, steven.daprano, tim.peters
Date 2018-12-14.19:37:40
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1544816260.42.0.788709270274.issue35431@psf.upfronthosting.co.za>
In-reply-to
Content
Just for fun, here's a gonzo implementation (without arg-checking) using ideas from the sketch.  All factors of 2 are shifted out first, and all divisions are done before any multiplies.

For large arguments, this can run much faster than a dumb loop.  For example, combp(10**100, 400) takes about a quarter the time of a dumb-loop divide-each-time-thru implementation.

    # Return number of trailing zeroes in `n`.
    def tzc(n):
        result = 0
        if n:
            mask = 1
            while n & mask == 0:
                result += 1
                mask <<= 1
        return result

    # Return exponent of prime `p` in prime factorization of
    # factorial(k).
    def facexp(k, p):
        result = 0
        k //= p
        while k:
            result += k
            k //= p
        return result

    def combp(n, k):
        from heapq import heappop, heapify, heapreplace

        if n-k < k:
            k = n-k
        if k == 0:
            return 1
        if k == 1:
            return n
        firstnum = n - k + 1
        nums = list(range(firstnum, n+1))
        assert len(nums) == k

        # Shift all factors of 2 out of numerators.
        shift2 = 0
        for i in range(firstnum & 1, k, 2):
            val = nums[i]
            c = tzc(val)
            assert c
            nums[i] = val >> c
            shift2 += c
        shift2 -= facexp(k, 2) # cancel all 2's in factorial(k)
        assert shift2 >= 0

        # Any prime generator is fine here.  `k` can't be
        # huge, and we only want the primes through `k`.
        pgen = psieve()
        p = next(pgen)
        assert p == 2

        for p in pgen:
            if p > k:
                break
            pcount = facexp(k, p)
            assert pcount
            # Divide that many p's out of numerators.
            i = firstnum % p
            if i:
                i = p - i
            for i in range(i, k, p):
                val, r = divmod(nums[i], p)
                assert r == 0
                pcount -= 1
                while pcount:
                    val2, r = divmod(val, p)
                    if r:
                        break
                    else:
                        val = val2
                        pcount -= 1
                nums[i] = val
                if pcount == 0:
                    break
            assert pcount == 0

        heapify(nums)
        while len(nums) > 1:
            a = heappop(nums)
            heapreplace(nums, a * nums[0])
        return nums[0] << shift2

I'm NOT suggesting to adopt this.  Just for history in the unlikely case there's worldwide demand for faster `comb` of silly arguments ;-)
History
Date User Action Args
2018-12-14 19:37:40tim.peterssetrecipients: + tim.peters, rhettinger, mark.dickinson, steven.daprano, serhiy.storchaka, kellerfuchs
2018-12-14 19:37:40tim.peterssetmessageid: <1544816260.42.0.788709270274.issue35431@psf.upfronthosting.co.za>
2018-12-14 19:37:40tim.peterslinkissue35431 messages
2018-12-14 19:37:40tim.peterscreate