Numpy Project Euler 1 Обобщенная оптимизация

Я начал изучать Numpy и ищу способы написать это. Я написал обобщение Эйлера 1. Он принимает список делителей и число, например [3, 5] и 1000 в Эйлере 1.

Наивно в чистом питоне

def subn1(divisors, n):
    return sum(i for i in range(1,n) if not all(i % d for d in divisors))

Это работает примерно за 2,5 секунды для диапазона (2,20), 1000000.

Моя первая и лучшая на данный момент попытка ошеломить выглядит так:

def subn2(divisors, n):
    a = np.arange(1,n) 
    b = np.zeros(a.shape[0], dtype=bool) 
    for d in divisors:
        b += a % d == 0
    return np.sum(a[b]) 

И работает около 0,45 секунд для диапазона (2,20), 1000000.

Моя третья попытка состояла в том, чтобы удалить петлю foor и использовать чистый numpy, однако он проигрывает в отделе скорости с небольшим отрывом и использует больше памяти.

def subn3(divisors, n):
    nums = np.arange(1,n)     
    divs = np.array([divisors]).T
    return np.sum(nums[np.logical_or.reduce(np.mod(nums, divs) == 0, axis=0)])

Это работает примерно за 0,5 секунды для диапазона (2,20), 100000.

Есть ли более быстрый способ записать это в "чистом" виде или нет, чтобы не уклоняться от циклов?

Примечание: я знаю, что вы можете оптимизировать его, уменьшив список делителей, поэтому нет необходимости комментировать это:)

2 ответа

Один NumPythonic векторизованный подход с broadcasting -

def subn_broadcasting(divisors,n):
    a = np.arange(1,n)
    return (a[(a % np.array(divisors)[:,None] == 0).any(0)]).sum()

Испытания во время выполнения и проверка -

In [14]: # Inputs
    ...: n = 1000
    ...: divisors = range(2,20)
    ...: 

In [15]: print subn1(divisors, n)
    ...: print subn2(divisors, n)
    ...: print subn3(divisors, n)
    ...: print subn_broadcasting(divisors, n)
    ...: 
416056
416056
416056
416056

In [16]: %timeit subn1(divisors, n)
    ...: %timeit subn2(divisors, n)
    ...: %timeit subn3(divisors, n)
    ...: %timeit subn_broadcasting(divisors, n)
    ...: 
1000 loops, best of 3: 1.39 ms per loop
1000 loops, best of 3: 480 µs per loop
1000 loops, best of 3: 434 µs per loop
1000 loops, best of 3: 428 µs per loop

Хм, не похоже, что улучшение там по сравнению с n2 а также n3 версии.

Вы можете использовать np.where следующим образом:

def subn4(divisors, n):
    a = np.arange(np.min(divisors),n) 
    b = np.zeros(a.shape[0], dtype=bool) 
    for d in divisors:
        b += a % d == 0
    return np.sum(a[np.where(b)])

def subn4_(divisors, n):
    a = np.arange(1,n) 
    b = np.zeros(a.shape[0], dtype=bool) 
    for d in divisors:
        b += a % d == 0
    return np.sum(a[np.where(b)]) 

Тесты, как предлагалось ранее:

%timeit subn1(divisors, n)
%timeit subn2(divisors, n)
%timeit subn3(divisors, n)
%timeit subn_broadcasting(divisors, n)
%timeit subn4(divisors, n)
%timeit subn4_(divisors, n)


1 loops, best of 3: 596 ms per loop
10 loops, best of 3: 30.1 ms per loop
10 loops, best of 3: 32 ms per loop
10 loops, best of 3: 31.9 ms per loop
10 loops, best of 3: 28.2 ms per loop
10 loops, best of 3: 27.4 ms per loop
Другие вопросы по тегам