I\'m trying to create a function that receives a number as an argument and performs actions on that number to find out its closest powers of 2 that will then add up to that
Most efficient way of doing this:
def myfunc(x):
powers = []
i = 1
while i <= x:
if i & x:
powers.append(i)
i <<= 1
return powers
We have some nice answers for this question now. I figured I'd analyze them a bit with dis
and timeit
.
Here is the test code I used:
import dis
import timeit
def gbriones_gdl(num):
result = []
binary = bin(num)[:1:-1]
for x in range(len(binary)):
if int(binary[x]):
result.append(2**x)
return result
def nullptr(num):
powers = []
i = 1
while i <= num:
if i & num:
powers.append(i)
i <<= 1
return powers
def t3_gen(num):
d = []
while sum(d) < num:
p = powers_of_2()
a=b=1
while sum(d)+a<=num:
b=a
a = next(p)
d.append(b)
d.reverse()
return d
def powers_of_2():
n=1
while 1:
yield n
n*=2
def t3_enum(num):
return [2**p for p,v in enumerate(bin(num)[:1:-1]) if int(v)]
def moose(num):
get_bin = lambda x: x >= 0 and str(bin(x))[2:] or "-" + str(bin(x))[3:]
bin_str = get_bin(num) # convert num to a binary string
powers = []
for i, digit in enumerate(bin_str[::-1]):
if digit == '1':
powers.append(2**i)
return powers
print('Each function gives correct results:', nullptr(1048575) == moose(1048575) ==
t3_enum(1048575) == gbriones_gdl(1048575) ==
t3_gen(1048575))
print()
print('nullptr'.ljust(15), timeit.timeit('nullptr(1048575)', 'from __main__ import nullptr', number=100000))
print('moose'.ljust(15), timeit.timeit('moose(1048575)', 'from __main__ import moose', number=100000))
print('t3_enum'.ljust(15), timeit.timeit('t3_enum(1048575)', 'from __main__ import t3_enum', number=100000))
print('gbriones_gdl'.ljust(15), timeit.timeit('gbriones_gdl(1048575)', 'from __main__ import gbriones_gdl', number=100000))
print('t3_gen'.ljust(15), timeit.timeit('t3_gen(1048575)', 'from __main__ import t3_gen', number=100000))
print('\nnullptr:\n===========================')
print(dis.dis(nullptr))
print('\nmoose:\n===========================')
print(dis.dis(moose))
print('\nt3_enum:\n===========================')
print(dis.dis(t3_gen))
print('gbriones_gdl:\n===========================')
print(dis.dis(t3_enum))
print('\nt3_gen:\n===========================')
print(dis.dis(gbriones_gdl))
And here are the results:
Each function gives correct results: True
nullptr 0.7847449885390462
moose 1.810839785503465
t3_enum 2.898256901365956
gbriones_gdl 3.0904670146624778
t3_gen 21.366890624367063
nullptr:
===========================
14 0 BUILD_LIST 0
3 STORE_FAST 1 (powers)
15 6 LOAD_CONST 1 (1)
9 STORE_FAST 2 (i)
16 12 SETUP_LOOP 52 (to 67)
>> 15 LOAD_FAST 2 (i)
18 LOAD_FAST 0 (num)
21 COMPARE_OP 1 (<=)
24 POP_JUMP_IF_FALSE 66
17 27 LOAD_FAST 2 (i)
30 LOAD_FAST 0 (num)
33 BINARY_AND
34 POP_JUMP_IF_FALSE 53
18 37 LOAD_FAST 1 (powers)
40 LOAD_ATTR 0 (append)
43 LOAD_FAST 2 (i)
46 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
49 POP_TOP
50 JUMP_FORWARD 0 (to 53)
19 >> 53 LOAD_FAST 2 (i)
56 LOAD_CONST 1 (1)
59 INPLACE_LSHIFT
60 STORE_FAST 2 (i)
63 JUMP_ABSOLUTE 15
>> 66 POP_BLOCK
20 >> 67 LOAD_FAST 1 (powers)
70 RETURN_VALUE
None
moose:
===========================
44 0 LOAD_CONST 1 (<code object <lambda> at 0x0000000002A8E660, file "power_2_adder.py", line 44>)
3 LOAD_CONST 2 ('moose.<locals>.<lambda>')
6 MAKE_FUNCTION 0
9 STORE_FAST 1 (get_bin)
45 12 LOAD_FAST 1 (get_bin)
15 LOAD_FAST 0 (num)
18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
21 STORE_FAST 2 (bin_str)
46 24 BUILD_LIST 0
27 STORE_FAST 3 (powers)
47 30 SETUP_LOOP 71 (to 104)
33 LOAD_GLOBAL 0 (enumerate)
36 LOAD_FAST 2 (bin_str)
39 LOAD_CONST 0 (None)
42 LOAD_CONST 0 (None)
45 LOAD_CONST 6 (-1)
48 BUILD_SLICE 3
51 BINARY_SUBSCR
52 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
55 GET_ITER
>> 56 FOR_ITER 44 (to 103)
59 UNPACK_SEQUENCE 2
62 STORE_FAST 4 (i)
65 STORE_FAST 5 (digit)
48 68 LOAD_FAST 5 (digit)
71 LOAD_CONST 4 ('1')
74 COMPARE_OP 2 (==)
77 POP_JUMP_IF_FALSE 56
49 80 LOAD_FAST 3 (powers)
83 LOAD_ATTR 1 (append)
86 LOAD_CONST 5 (2)
89 LOAD_FAST 4 (i)
92 BINARY_POWER
93 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
96 POP_TOP
97 JUMP_ABSOLUTE 56
100 JUMP_ABSOLUTE 56
>> 103 POP_BLOCK
50 >> 104 LOAD_FAST 3 (powers)
107 RETURN_VALUE
None
t3_enum:
===========================
23 0 BUILD_LIST 0
3 STORE_FAST 1 (d)
24 6 SETUP_LOOP 101 (to 110)
>> 9 LOAD_GLOBAL 0 (sum)
12 LOAD_FAST 1 (d)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 LOAD_FAST 0 (num)
21 COMPARE_OP 0 (<)
24 POP_JUMP_IF_FALSE 109
25 27 LOAD_GLOBAL 1 (powers_of_2)
30 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
33 STORE_FAST 2 (p)
26 36 LOAD_CONST 1 (1)
39 DUP_TOP
40 STORE_FAST 3 (a)
43 STORE_FAST 4 (b)
27 46 SETUP_LOOP 44 (to 93)
>> 49 LOAD_GLOBAL 0 (sum)
52 LOAD_FAST 1 (d)
55 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
58 LOAD_FAST 3 (a)
61 BINARY_ADD
62 LOAD_FAST 0 (num)
65 COMPARE_OP 1 (<=)
68 POP_JUMP_IF_FALSE 92
28 71 LOAD_FAST 3 (a)
74 STORE_FAST 4 (b)
29 77 LOAD_GLOBAL 2 (next)
80 LOAD_FAST 2 (p)
83 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
86 STORE_FAST 3 (a)
89 JUMP_ABSOLUTE 49
>> 92 POP_BLOCK
30 >> 93 LOAD_FAST 1 (d)
96 LOAD_ATTR 3 (append)
99 LOAD_FAST 4 (b)
102 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
105 POP_TOP
106 JUMP_ABSOLUTE 9
>> 109 POP_BLOCK
31 >> 110 LOAD_FAST 1 (d)
113 LOAD_ATTR 4 (reverse)
116 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
119 POP_TOP
32 120 LOAD_FAST 1 (d)
123 RETURN_VALUE
None
gbriones_gdl:
===========================
41 0 LOAD_CONST 1 (<code object <listcomp> at 0x0000000002A8E540, file "power_2_adder.py", line 41>)
3 LOAD_CONST 2 ('t3_enum.<locals>.<listcomp>')
6 MAKE_FUNCTION 0
9 LOAD_GLOBAL 0 (enumerate)
12 LOAD_GLOBAL 1 (bin)
15 LOAD_FAST 0 (num)
18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
21 LOAD_CONST 0 (None)
24 LOAD_CONST 3 (1)
27 LOAD_CONST 4 (-1)
30 BUILD_SLICE 3
33 BINARY_SUBSCR
34 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
37 GET_ITER
38 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
41 RETURN_VALUE
None
t3_gen:
===========================
6 0 BUILD_LIST 0
3 STORE_FAST 1 (result)
7 6 LOAD_GLOBAL 0 (bin)
9 LOAD_FAST 0 (num)
12 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
15 LOAD_CONST 0 (None)
18 LOAD_CONST 1 (1)
21 LOAD_CONST 3 (-1)
24 BUILD_SLICE 3
27 BINARY_SUBSCR
28 STORE_FAST 2 (binary)
8 31 SETUP_LOOP 62 (to 96)
34 LOAD_GLOBAL 1 (range)
37 LOAD_GLOBAL 2 (len)
40 LOAD_FAST 2 (binary)
43 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
46 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
49 GET_ITER
>> 50 FOR_ITER 42 (to 95)
53 STORE_FAST 3 (x)
9 56 LOAD_GLOBAL 3 (int)
59 LOAD_FAST 2 (binary)
62 LOAD_FAST 3 (x)
65 BINARY_SUBSCR
66 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
69 POP_JUMP_IF_FALSE 50
10 72 LOAD_FAST 1 (result)
75 LOAD_ATTR 4 (append)
78 LOAD_CONST 2 (2)
81 LOAD_FAST 3 (x)
84 BINARY_POWER
85 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
88 POP_TOP
89 JUMP_ABSOLUTE 50
92 JUMP_ABSOLUTE 50
>> 95 POP_BLOCK
11 >> 96 LOAD_FAST 1 (result)
99 RETURN_VALUE
None
From the timeit
results, we can see that @nullptr's solution is quite nice, as I suspected, followed by @moose's solution. After that came my combination of @moose's and @gbriones.gdl's solutions, followed very closely by @gbriones.gdl's solution. My generator solution was, shall we say, very suboptimal, but I kind of expected that.
dis
results are included for completeness.
The idea is to convert the number to binary and then get powers of two from the binary representation:
#!/usr/bin/env python
def get_powers(n):
"""Get positive powers of two which add up to n.
Parameters
----------
n : positive integer
Returns
-------
list of integers which are powers of 2
Examples
--------
>>> get_powers(14)
[2, 4, 8]
>>> get_powers(5)
[1, 4]
"""
get_bin = lambda x: x >= 0 and str(bin(x))[2:] or "-" + str(bin(x))[3:]
bin_str = get_bin(n) # convert n to a binary string
powers = []
for i, digit in enumerate(bin_str[::-1]):
if digit == '1':
powers.append(2**i)
return powers
One simple (but really not effective) way of doing this is to use backtracking. Note that the closest power of two is easily founded by using math.log function (The closest power of two of n is 2^round(log(n, 2))
):
from math import log
def powers_finder(n):
return powers_finder_rec(n, [2**x for x in range(round(log(n, 2)+1))])
def powers_finder_rec(n, powers):
if sum(powers) == n:
return powers
for i, j in enumerate(powers):
(res1, res2) = (powers_finder_rec(n-j, powers[:i] + powers[i+1:]),
powers_finder_rec(n, powers[:i] + powers[i+1:]))
if res1 or res2:
return [j] + res1 if res1 else res2
return []
print(powers_finder(13))
print(powers_finder(112))
Output:
[1, 4, 8]
[16, 32, 64]
Try with binaries:
def power_find(n):
result = []
binary = bin(n)[:1:-1]
for x in range(len(binary)):
if int(binary[x]):
result.append(2**x)
return result
>>> power_find(11)
[1, 2, 8]
The following binary solution combines @moose's use of enumerate()
with @gbriones.gdl's use of stride indexing and @gbriones.gdl's comment about one-lining it (actually, it was a comment about not one-lining it, but one-lining is fun).
def powers(n):
return [2**p for p,v in enumerate(bin(n)[:1:-1]) if int(v)]