What is the fastest way to represent number as the sum of powers of two in python

点点圈 提交于 2020-01-04 07:49:43

问题


For example

>>> two_powers(42)
>>> (2, 8, 32)

My current naive implementation (taken from here) looks like that

def two_powers(num):
    return tuple(2 ** i for i, j in enumerate(bin(num)[-1: 1: -1]) if j == '1')

But I hope there're faster ways to do this.


回答1:


Try this:

def two_powers(num):
    powers = []
    while num != 0:
        powers.append(num & -num)
        num = num & (num - 1)
    return powers



回答2:


Your solution is good actually, with a slight (big!) detail of efficiency:

Use

1<<i 

(bitwise shift) instead of

2**i

So, to copy you, consider the following :

def two_powers(num):
    return set(1 << i for i, j in enumerate(bin(num)[-1: 1: -1]) if j == '1')
print two_powers(42)



回答3:


You can make use of the log2 and generator expression until you are run out of powers of two.

import math

def two_powers(num):
    while num > 0:
        power = int(math.log(num, 2))
        yield 2**power
        num = num - 2**power

Sample run:

>>> tuple(two_powers(42)))
(32, 8, 2)
>>> tuple(two_powers(43)))
(32, 8, 2, 1)



回答4:


You can do this:

import math

def two_powers(num):
    # Compute number of bits for big numbers
    num_bits = math.floor(math.log2(num)) + 1 if num >= (1 << 32) else 32
    # Take those bits where there is a "one" in the number
    return [1 << p for p in range(num_bits) if num & (1 << p)]

print(two_powers(42))
# [2, 8, 32]

EDIT: Wrt the number of bits, you can make more splits if you are really concerned about performance, either down to save iterations or up to avoid computing the logarithm (or if you know your input numbers are going to be in some particular range):

import math

def two_powers(num):
    # Compute number of bits for big numbers
    if num < (1 << 8):
        num_bits = 8
    elif num < (1 << 16):
        num_bits = 16
    elif num < (1 << 24):
        num_bits = 24
    elif num < (1 << 32):
        num_bits = 32
    else:
        num_bits = math.floor(math.log2(num)) + 1
    # Take those bits where there is a "one" in the number
    return [1 << p for p in range(num_bits) if num & (1 << p)]

print(two_powers(42))
# [2, 8, 32]



回答5:


You can use a generator with a shifting bit mask:

def two_powers(n):
    m = 1
    while n >= m:
        if n & m:
            yield m
        m <<= 1

So that:

tuple(two_powers(42))

would be:

(2, 8, 32)



回答6:


>>> n=42
>>> {1<<i for i,d in enumerate(reversed(bin(n)[2:])) if d=='1'}
{8, 2, 32}



回答7:


Attn: Late Posters: Please feel free to run this small benchmark including your code, and amend the results accordingly. You will need to re-run the tests for everyone as hardware differences will happen.

Gentlemen, here are your scores:

The winner is @tsionyx

timeit:

Benchmark <=10^2
[97, 48, 31, 39, 33, 69, 71, 21, 50, 17]
two_powers_op_____ 17.8 µs ± 1.2 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_jdehesa 15.1 µs ± 888 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_matina_ 14.6 µs ± 755 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_agile_e 7.87 µs ± 524 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_taras__ 25.8 µs ± 1.29 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_sunitha 12 µs ± 1.19 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_blhsing 11.5 µs ± 566 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_tsionyx 5.77 µs ± 57.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Benchmark <=10^3
[682, 124, 42, 275, 743, 837, 474, 186, 739, 290]
two_powers_op_____ 22.1 µs ± 710 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_jdehesa 17.9 µs ± 829 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_matina_ 17.6 µs ± 881 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_agile_e 12.7 µs ± 763 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_taras__ 49.2 µs ± 3.85 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_sunitha 18.1 µs ± 2.56 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_blhsing 19.2 µs ± 2.79 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_tsionyx 10.4 µs ± 1.14 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Benchmark <=10^4
[4641, 5675, 3355, 4746, 9948, 5192, 3446, 7174, 1683, 7611]
two_powers_op_____ 30.8 µs ± 3.36 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_jdehesa 22.2 µs ± 2.37 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_matina_ 21.7 µs ± 1.13 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_agile_e 17.5 µs ± 2.46 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_taras__ 64.3 µs ± 12.1 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_sunitha 18.5 µs ± 1.24 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_blhsing 19.2 µs ± 193 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_tsionyx 11.6 µs ± 43.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Benchmark <=10^5
[20885, 23810, 25330, 32967, 34183, 16847, 54905, 85767, 37069, 32379]
two_powers_op_____ 32.8 µs ± 1.76 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_jdehesa 24.2 µs ± 534 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_matina_ 27.1 µs ± 2.99 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_agile_e 18.7 µs ± 246 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
two_powers_taras__ 68.9 µs ± 3.16 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_sunitha 20.6 µs ± 486 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_blhsing 22.7 µs ± 883 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_tsionyx 14.2 µs ± 244 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Benchmark <=10^6
[182928, 93105, 710309, 926572, 859733, 818327, 654197, 829750, 358363, 946684]
two_powers_op_____ 40.6 µs ± 236 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_jdehesa 28.2 µs ± 310 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_matina_ 27.9 µs ± 936 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_agile_e 23.8 µs ± 364 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_taras__ 89.9 µs ± 406 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_sunitha 24.4 µs ± 493 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_blhsing 26.6 µs ± 366 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
two_powers_tsionyx 19.3 µs ± 95.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

code used:

import functools
import math
import random
from collections import defaultdict


def two_powers_op_____(num):
    return tuple(2 ** i for i, j in enumerate(bin(num)[-1: 1: -1]) if j == '1')


def two_powers_jdehesa(num):
    num_bits = math.floor(math.log2(num)) + 1
    return [1 << p for p in range(num_bits) if num & (1 << p)]


def two_powers_matina_(num):
    return set(1 << i for i, j in enumerate(bin(num)[-1: 1: -1]) if j == '1')


def two_powers_agile_e(num):
    powers = []
    while num != 0:
        powers.append(num & -num)
        num = num & (num - 1)
    return powers


def _two_powers_taras(num):
    while num > 0:
        power = int(math.log(num, 2))
        yield 2 ** power
        num = num - 2 ** power


def two_powers_taras__(num):
    return tuple(_two_powers_taras(num))


def two_powers_sunitha(num):
    return {1 << i for i, d in enumerate(reversed(bin(num)[2:])) if d == '1'}


def _two_powers_blhsing(n):
    m = 1
    while n >= m:
        if n & m:
            yield m
        m <<= 1


def two_powers_blhsing(n):
    return tuple(_two_powers_blhsing(n))


def two_powers_tsionyx(num):
    powers = []
    while num > 0:
        rest = num & (num - 1)
        powers.append(num - rest)
        num = rest

    return powers


funcs = [
    two_powers_op_____,
    two_powers_jdehesa,
    two_powers_matina_,
    two_powers_agile_e,
    two_powers_taras__,
    two_powers_sunitha,
    two_powers_blhsing,
    two_powers_tsionyx,
]


# ================== UTILITY FUNCTIONS ======================= #

def _partial_map(f, vals):
    """Run function on a range of inputs as a single function"""
    p = functools.partial(map, f, vals)
    p.__name__ = f.__name__
    return p


def _sanity_check(f, n):
    factors = f(n)
    assert len(factors) > 0
    # factors are unique
    assert len(set(factors)) == len(factors)
    assert sum(factors) == n

    for f in factors:
        b = bin(f)
        assert b == '0b1' + '0' * (len(b) - 3)


def benchmark(fs, inputs):
    for f in fs:
        for n in inputs:
            _sanity_check(f, n)

    aggr_funcs = [_partial_map(f, inputs) for f in fs]

    res = dict()
    print(inputs)
    for f in aggr_funcs:
        print(f.__name__, end=' ')
        tres = %timeit -o tuple(f())
        res[f.__name__] = tres.average

    return res


def plot(results):
    %matplotlib inline
    import matplotlib.pyplot as plt
    import matplotlib

    plt.figure(figsize=(10, 10))
    matplotlib.rcParams.update({'font.size': 18})

    leg = []
    for k, v in results.items():
        x, y = zip(*sorted(v.items()))
        plt.plot(x, [i * 10 ** 6 for i in y])
        leg.append(k)

    plt.legend(leg, loc='upper left')
    plt.ylabel('μs')
    plt.show()


full_res = defaultdict(dict)
for degree in range(2, 7):
    print('Benchmark <=10^%i' % degree)
    for name, t in benchmark(funcs, [random.randint(1, 10 ** degree) for _ in range(10)]).items():
        full_res[name][degree] = t

# you can view the results if you run it inside a jupyter notebook
# just uncomment the following line
# plot(full_res)

measured on Lenovo ThinkPad E480




回答8:


Inspired by Agile_Eagle's answer

def two_powers(num):
    powers = []
    while num > 0:
        rest = num & (num - 1)
        powers.append(num - rest)
        num = rest

    return powers


来源:https://stackoverflow.com/questions/51786324/what-is-the-fastest-way-to-represent-number-as-the-sum-of-powers-of-two-in-pytho

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!