Você está na página 1de 4

from util import multi_lcm

def p1_sum_of_multiples(p, i_0=1, i_n=1000):


"""Sum of multiples of p in range(i_0, i_n)"""
i_n -= 1 # (Now we compute the sum over a 'closed' interval, [i_0 .. i_n])
i_n //= p
i_0, remainder = divmod(i_0, p)
if remainder: i_0 += 1
if i_0 > i_n: return 0
return p * (i_n - i_0 + 1) * (i_0 + i_n) // 2

def p1_with_combinations(numbers, i_0=1, i_n=1000):


"""
Same as p1_general(...), but using itertools.combinations.
Basic idea:
Sum of multiples of a,b,c := {a,b,c} ==
== {a} + {b} + {c} - {lcm(a,b)} - {lcm(b,c)} - {lcm(a,c)} + {lcm(a,b,c)}.
"""
from itertools import combinations
s, sign = 0, 1
for k in range(1, len(numbers)+1):
for comb in combinations(numbers, k):
p = multi_lcm(*comb)
s += sign * p1_sum_of_multiples(p, i_0, i_n)
sign *= -1
return s

def p1_memkill(numbers, i_0=1, i_n=1000, mem_warning=22):


"""
Generalization of Problem 1 from projecteuler.net
Example: sum of multiples of 8, 10 or 15 in range(5,80) is given by
p1_memkill([8,10,15],5,80).
"""
s = 0
L = [(1, 1)] #=> list of (current lcm and signal) used to compute lcm of
'siblings'
for new_number in numbers:
for i, (leaf_lcm, signal) in enumerate(L[::-1], start=1):
new_lcm = multi_lcm(leaf_lcm, new_number)
L.append((new_lcm, -signal))
s += signal * p1_sum_of_multiples(new_lcm, i_0, i_n)
if mem_warning and len(numbers) >= mem_warning:
print("# Warning: len(L) = {}; sizeof(L) = {}.".format(len(L),
L.__sizeof__()))
return s

def p1_dumb(numbers, i_0=1, i_n=1000):


from util import multi_gcd
step = multi_gcd(*numbers)
r = i_0 % step
if r:
i_0 += (step - r)
numbers = tuple(numbers)
s = 0
for k in range(i_0, i_n, step):
if not all(k%j for j in numbers):
s += k
return s
## print("???", s == sum(k for k in range(i_0, i_n, step) if not all(k%j for
j in numbers)))
return sum(k for k in range(i_0, i_n, step) if not all(k%j for j in numbers))

def _p1_reduce_numbers(numbers):
assert len(numbers) > 0, "empty list of numbers in _p1_reduce_numbers"
from array import array

numbers = sorted(set(abs(k) for k in numbers))


while len(numbers) > 1 and numbers[0] <= 1: numbers.pop(0)

d = {value:key for key,value in enumerate(numbers)}


allowed = array('B', (1 for _ in numbers))

for i,ni in enumerate(numbers[:len(numbers)//2]): #(=> something better?


probably)
for nj in numbers[i+1:]: #todo: go backwards (?)
if nj % ni == 0:
allowed[d[nj]] = 0

numbers = array('H', (n for n,j in zip(numbers, allowed) if j))


return numbers

def _p1_general_recursion(numbers, i_0=1, i_n=1000, _lcm_depth=0, _lcm_start=1):


if _lcm_depth >= len(numbers)-1: #=> ending
new_lcm = multi_lcm(_lcm_start, numbers[-1])
res = p1_sum_of_multiples(new_lcm, i_0, i_n)
if _lcm_start != 1:
res -= p1_sum_of_multiples(_lcm_start, i_0, i_n)
return res
else:
s = _p1_general_recursion(numbers, i_0, i_n, _lcm_depth+1, _lcm_start)
new_lcm = multi_lcm(_lcm_start, numbers[_lcm_depth])
s -= _p1_general_recursion(numbers, i_0, i_n, _lcm_depth+1, new_lcm)
return s

def p1_general(numbers, i_0=1, i_n=1000):


"""
Generalization of Project Euler's Problem 1.
Ex: sum of multiples of 8, 10 or 15 in range(5,80) is given by
p1_general([8,10,15], 5, 80).
(`numbers` is a list of integers)
"""
return _p1_general_recursion(numbers, i_0, i_n)

def p1(numbers=None, i_0=1, i_n=1000, method="auto", verbose=0):


"""
Multiples of 3 and 5
https://projecteuler.net/problem=1
Find the sum of all the multiples of 3 or 5 below 1000.
[Allowed methods: "tree", "dumb", "memkill" and "auto"]
"""
from math import log2
if numbers is None: numbers = [3, 5]
if len(numbers) == 1:
return p1_sum_of_multiples(numbers[0], i_0, i_n)
numbers = _p1_reduce_numbers(numbers)

if method == "auto":
LOG2 = log2(i_n - i_0)
method = "tree" if (LOG2 > len(numbers)) else "dumb"
if verbose:
print(method, "[factors: {} ; interval:
2**{:.2f}]".format(len(numbers), LOG2), end=" ; ")

if method == "tree":
return p1_general(numbers, i_0, i_n)

if method == "dumb":
return p1_dumb(numbers, i_0, i_n)

if method == "memkill":
return p1_memkill(numbers, i_0, i_n)

raise ValueError("p1(): Invalid method ({})".format(method))

def p1_tree_printer(L, i=0, prefix="", show=True):


if i >= len(L)-1:
if show:
print(prefix, "-")
print(prefix, L[-1])
else:
p1_tree_printer(L, i+1, "{} -".format(prefix), show)
p1_tree_printer(L, i+1, "{} {}".format(prefix, L[i]), show)

def p1_tree_printer_times():
from util import euler_timer, plot_times

ii = tuple(range(4,13))
L = tuple(euler_timer(p1_tree_printer, (range(1, k), 0, "", False), 5) for k
in ii)

plot_times(ii, L, (lambda k: 2**(k-20)), '*-', logy=True)

def p1_demo(methods=None, verbose=1):


from time import time
if methods is None:
methods = ("auto", "dumb", "memkill", "tree")
i_n = 2**15

results = {method:[] for method in methods}


times = {method:[] for method in methods}

for method in methods:


for N in range(10, 50): # (avoid "memkill" or "tree" if N > 60)
t = time()
res = p1(range(2,N), 1, i_n, method)
t = (time() - t)
results[method].append((N, res))
times[method].append(t)

if verbose: print("{:7s}: sum={:d};\t


time={:.3e}".format(method, res, t))

if verbose: print()

from numpy import array


results = {k:array(v) for k,v in results.items()}
times = {k:array(v) for k,v in times.items()}
return results, times

# Teste 1
if False:
params = ([3, 5, 6, 7, 9, 12], 1, 10**5)
print(p1_dumb(*params) == p1_general(*params) ==
p1_with_combinations(*params))

# Teste 2
if False:
methods = ("auto", "dumb", "memkill", "tree")
results, times = p1_demo(methods, verbose=0)
print("RESULTS:")
for method in methods:
print(method, *results[method], sep="\n ", end="\n\n")

print("\nTIMES:")
for method in methods:
print(method, *times[method], sep="\n ", end="\n\n")

if __name__ == "__main__":
print(p1(numbers=[3, 5], i_0=1, i_n=1000, method="auto", verbose=0))

Você também pode gostar