Який найкращий спосіб отримати всі дільники числа?


106

Ось дуже німий спосіб:

def divisorGenerator(n):
    for i in xrange(1,n/2+1):
        if n%i == 0: yield i
    yield n

Результат, який я хотів би отримати, схожий на цей, але я хотів би розумніший алгоритм (у цьому він занадто повільний і тупий :-)

Я можу знайти основні фактори та їх кратність досить швидко. У мене є генератор, який генерує фактор таким чином:

(фактор1, кратність1)
(фактор2, кратність2)
(фактор3, кратність3)
і так далі ...

тобто вихід

for i in factorGenerator(100):
    print i

є:

(2, 2)
(5, 2)

Я не знаю, наскільки це корисно для того, що я хочу зробити (я зашифрував це для інших проблем), все одно я хотів би розумніший спосіб зробити

for i in divisorGen(100):
    print i

Виведіть це:

1
2
4
5
10
20
25
50
100

ОНОВЛЕННЯ: Велике спасибі Грегу Хьюгілу та його "розумному шляху" :) Підрахунок усіх дільників 100000000 зайняв 0,01 секунди вперед проти 39-х, які тупий шлях прийняв на мою машину, дуже круто: D

ОНОВЛЕННЯ 2: Перестаньте говорити, що це дублікат цієї публікації. Для обчислення кількості дільника заданого числа не потрібно обчислювати всі дільники. Це інша проблема, якщо ви думаєте, що це не тоді шукайте "Відокремлювальну функцію" у Вікіпедії. Прочитайте питання та відповідь перед публікацією, якщо ви не розумієте, що таке тема, просто не додайте не корисних і вже даних відповідей.


Причиною того, що було припущено, що це питання було майже дублікатом "Алгоритму обчислення кількості дільників даного числа", полягало в тому, що запропонованим першим кроком у цьому питанні було знайти всіх дільників , на які я вважаю саме що ви намагалися зробити?
Ендрю Еджкомб

4
Ендрю, щоб знайти скільки дільників, ти просто повинен знайти основні коефіцієнти, а потім використовувати їх для підрахунку кількості дільників. Пошук дільників у цьому випадку не потрібен.
Loïc Faure-Lacroix

1
@Andrea Ambu, будь ласка, виправте функціональні назви
мінерали

Відповіді:


77

З огляду на вашу factorGeneratorфункцію, ось divisorGenщо має працювати:

def divisorGen(n):
    factors = list(factorGenerator(n))
    nfactors = len(factors)
    f = [0] * nfactors
    while True:
        yield reduce(lambda x, y: x*y, [factors[x][0]**f[x] for x in range(nfactors)], 1)
        i = 0
        while True:
            f[i] += 1
            if f[i] <= factors[i][1]:
                break
            f[i] = 0
            i += 1
            if i >= nfactors:
                return

Загальна ефективність цього алгоритму буде повністю залежати від ефективності роботи factorGenerator.


2
Уау, знадобилося 0,01 для обчислення всіх дільників 100000000 проти 39-х, які взяли тупий шлях (зупиняючись на n / 2) дуже круто, дякую!
Андреа Амбу

47
Для нас із тих, хто не розуміє Pythonese, що це насправді?
Меттью Шарлі

1
монооксид: це обчислює всі мультиплікативні комбінації даних факторів. Більшість із них має бути роз'яснювальною; рядок "дохідності" - це як повернення, але продовжує йти після повернення значення. [0] * nfactors створює список нулів довжини nfactors. зменшити (...) обчислює добуток факторів.
Грег Хьюгілл

Позначення скорочення та лямбда - це ті, що насправді мене бентежать. Я спробував реалізувати алгоритм, щоб зробити це в C #, використовуючи рекурсивну функцію, щоб пройти масив факторів і помножити їх усі разом, але, схоже, вони мають жахливу ефективність на числах 1024, які мають багато факторів
Меттью Шарлі

3
Це, звичайно, суттєво краще, ніж ділення на кожне число на n / 2 або навіть sqrt (n), але ця конкретна реалізація має два недоліки: досить неефективна: тонни множення та експоненції, багаторазово множення одних і тих же сил тощо. Виглядає Pythonic, але я не думаю, що Python не стосується вбивства. Проблема друга: дільники не повертаються в порядку.
Томаш Гандор

34

Щоб розширити те, що сказав Шимі, вам слід запустити цикл лише від 1 до квадратного кореня n. Потім, щоб знайти пару, зробіть n / i, і це охопить весь простір проблеми.

Як також зазначалося, це НП, або "складна" проблема. Вичерпний пошук, як ви це робите, настільки ж хороший, як і для гарантованих відповідей. Цей факт використовується алгоритмами шифрування тощо, щоб захистити їх. Якби хтось вирішив цю проблему, більшість, якби не все наше сучасне "безпечне" спілкування, було б незахищеним.

Код Python:

import math

def divisorGenerator(n):
    large_divisors = []
    for i in xrange(1, int(math.sqrt(n) + 1)):
        if n % i == 0:
            yield i
            if i*i != n:
                large_divisors.append(n / i)
    for divisor in reversed(large_divisors):
        yield divisor

print list(divisorGenerator(100))

Який повинен вивести такий список, як:

[1, 2, 4, 5, 10, 20, 25, 50, 100]

2
Тому що, коли у вас є список елементів між 1..10, ви можете генерувати будь-який з елементів між тривалістю 11..100. Ви отримуєте {1, 2, 4, 5, 10}. Розділіть 100 на кожен з цих елементів і ви {100, 50, 20, 25, 10}.
Меттью Шарлі

2
Фактори ВИНАГИ генеруються парами в силу визначення. Тільки шукаючи sqrt (n), ви скорочуєте свою роботу потужністю 2.
Меттью Шарлі

Це дуже швидше, ніж версія в моєму дописі, але вона все ще надто повільна, ніж версія, яка використовує основні фактори
Андреа Амбу

Я згоден, це не найкраще рішення. Я просто вказував на "кращий" спосіб здійснення "німого" пошуку, який би вже економив багато часу.
Меттью Шарлі

Не було показано, що факторизація є NP-жорсткою. en.wikipedia.org/wiki/Integer_factorization І проблема полягала в тому, щоб знайти всіх дільників, враховуючи, що основні фактори (важка частина) вже знайдені.
Джеймі

19

Хоча для цього вже існує багато рішень, я справді маю це опублікувати :)

Це:

  • читабельний
  • короткий
  • самостійно, готово скопіювати та вставити
  • швидкий (у випадках з великою кількістю простих факторів і дільників,> в 10 разів швидше прийнятого рішення)
  • python3, python2 та pypy сумісні

Код:

def divisors(n):
    # get factors and their counts
    factors = {}
    nn = n
    i = 2
    while i*i <= nn:
        while nn % i == 0:
            factors[i] = factors.get(i, 0) + 1
            nn //= i
        i += 1
    if nn > 1:
        factors[nn] = factors.get(nn, 0) + 1

    primes = list(factors.keys())

    # generates factors from primes[k:] subset
    def generate(k):
        if k == len(primes):
            yield 1
        else:
            rest = generate(k+1)
            prime = primes[k]
            for factor in rest:
                prime_to_i = 1
                # prime_to_i iterates prime**i values, i being all possible exponents
                for _ in range(factors[prime] + 1):
                    yield factor * prime_to_i
                    prime_to_i *= prime

    # in python3, `yield from generate(0)` would also work
    for factor in generate(0):
        yield factor

Я хотів би замінити while i*i <= nnна while i <= limit, деlimit = math.sqrt(n)
Rafa0809

17

Я думаю, ви можете зупинитися на math.sqrt(n)замість n / 2.

Я надам вам приклад, щоб ви могли це легко зрозуміти. Тепер sqrt(28)це 5.29так ceil(5.29)буде 6. Так що , якщо я зупинюся на 6 , то я можу отримати все подільники. Як?

Спочатку перегляньте код, а потім побачите зображення:

import math
def divisors(n):
    divs = [1]
    for i in xrange(2,int(math.sqrt(n))+1):
        if n%i == 0:
            divs.extend([i,n/i])
    divs.extend([n])
    return list(set(divs))

Тепер дивіться зображення нижче:

Скажімо, я вже додав 1до свого списку дільників, і я починаю з i=2цього

Дільники 28

Тож наприкінці всіх ітерацій, коли я додав коефіцієнт і дільник до мого списку, усі дільники 28 заповнюються.

Джерело: Як визначити дільники числа


2
Чудово, чудово!! math.sqrt(n) instead of n/2обов'язковий для елегантності
Rafa0809

Це неправильно. Ви забули, що n ділиться самим собою.
Jasonleonhard

1
Гарна відповідь. Простий і зрозумілий. Але для python 3 є 2 необхідні зміни: n / i слід вводити за допомогою int (n / i), тому що n / i видає float число. Також pyxon є застарілим у python 3 і замінюється діапазоном.
Джеффрой КАЛА

7

Мені подобається рішення Грега, але я б хотів, щоб це було більше як пітон. Я відчуваю, що це було б швидше і читабельніше; тому через деякий час кодування я вийшов з цим.

Перші дві функції потрібні для складання декартового твору списків. І їх можна повторно використовувати, коли виникає ця проблема. До речі, мені довелося це запрограмувати самостійно, якщо хтось знає про стандартне рішення цієї проблеми, будь ласка, зв'яжіться зі мною.

"Factorgenerator" тепер повертає словник. А потім словник подається в "дільники", які використовують його для створення спочатку списку списків, де кожен список - це перелік факторів форми p ^ n з p простим. Тоді ми робимо декартовий добуток із цих списків і, нарешті, використовуємо рішення Грега для створення дільника. Ми їх сортуємо та повертаємо.

Я перевірив його, і, здається, це трохи швидше, ніж попередня версія. Я перевірив це як частину більшої програми, тому я не можу реально сказати, наскільки це швидше.

П'єтро Спероні (pietrosperoni)

from math import sqrt


##############################################################
### cartesian product of lists ##################################
##############################################################

def appendEs2Sequences(sequences,es):
    result=[]
    if not sequences:
        for e in es:
            result.append([e])
    else:
        for e in es:
            result+=[seq+[e] for seq in sequences]
    return result


def cartesianproduct(lists):
    """
    given a list of lists,
    returns all the possible combinations taking one element from each list
    The list does not have to be of equal length
    """
    return reduce(appendEs2Sequences,lists,[])

##############################################################
### prime factors of a natural ##################################
##############################################################

def primefactors(n):
    '''lists prime factors, from greatest to smallest'''  
    i = 2
    while i<=sqrt(n):
        if n%i==0:
            l = primefactors(n/i)
            l.append(i)
            return l
        i+=1
    return [n]      # n is prime


##############################################################
### factorization of a natural ##################################
##############################################################

def factorGenerator(n):
    p = primefactors(n)
    factors={}
    for p1 in p:
        try:
            factors[p1]+=1
        except KeyError:
            factors[p1]=1
    return factors

def divisors(n):
    factors = factorGenerator(n)
    divisors=[]
    listexponents=[map(lambda x:k**x,range(0,factors[k]+1)) for k in factors.keys()]
    listfactors=cartesianproduct(listexponents)
    for f in listfactors:
        divisors.append(reduce(lambda x, y: x*y, f, 1))
    divisors.sort()
    return divisors



print divisors(60668796879)

PS це перший раз, коли я публікую повідомлення в stackoverflow. Я з нетерпінням чекаю будь-яких відгуків.


У Python 2.6 є itertools.product ().
jfs

Версія, яка використовує генератори замість list.append скрізь, може бути чистішою.
jfs

Сито Ератосфена може бути використаний для генерації простих чисел менше або дорівнює SQRT (N) stackoverflow.com/questions/188425/project-euler-problem#193605
JFS

1
Стиль кодування: експоненти = [k ** x для k, v в фактори.items () для x у діапазоні (v + 1)]
jfs

Для слухачів: [[k ** x для x у діапазоні (v + 1)] для k, v у факторах.items ()]
klenwell

3

Ось розумний та швидкий спосіб зробити це для чисел до 10 та 16 ** у чистому Python 3.6,

from itertools import compress

def primes(n):
    """ Returns  a list of primes < n for n > 2 """
    sieve = bytearray([True]) * (n//2)
    for i in range(3,int(n**0.5)+1,2):
        if sieve[i//2]:
            sieve[i*i//2::i] = bytearray((n-i*i-1)//(2*i)+1)
    return [2,*compress(range(3,n,2), sieve[1:])]

def factorization(n):
    """ Returns a list of the prime factorization of n """
    pf = []
    for p in primeslist:
      if p*p > n : break
      count = 0
      while not n % p:
        n //= p
        count += 1
      if count > 0: pf.append((p, count))
    if n > 1: pf.append((n, 1))
    return pf

def divisors(n):
    """ Returns an unsorted list of the divisors of n """
    divs = [1]
    for p, e in factorization(n):
        divs += [x*p**k for k in range(1,e+1) for x in divs]
    return divs

n = 600851475143
primeslist = primes(int(n**0.5)+1) 
print(divisors(n))

Як називається алгоритм, який використовується для пошуку простих чисел та розподілу факторів? Тому що я хочу реалізувати це в C # ..
Kyu96

2

Адаптований з CodeReview , ось варіант, з яким працює num=1!

from itertools import product
import operator

def prod(ls):
   return reduce(operator.mul, ls, 1)

def powered(factors, powers):
   return prod(f**p for (f,p) in zip(factors, powers))


def divisors(num) :

   pf = dict(prime_factors(num))
   primes = pf.keys()
   #For each prime, possible exponents
   exponents = [range(i+1) for i in pf.values()]
   return (powered(primes,es) for es in product(*exponents))

1
Здається , я отримую повідомлення про помилку: NameError: global name 'prime_factors' is not defined. Жодна з інших відповідей, ані оригінальне питання не визначає, що це робить.
AnnanFay

2

Я просто додам трохи переглянуту версію Аніварта (оскільки я вважаю це найбільш пітонічним) для подальшої довідки.

from math import sqrt

def divisors(n):
    divs = {1,n}
    for i in range(2,int(sqrt(n))+1):
        if n%i == 0:
            divs.update((i,n//i))
    return divs

1

Старе питання, але ось моя думка:

def divs(n, m):
    if m == 1: return [1]
    if n % m == 0: return [m] + divs(n, m - 1)
    return divs(n, m - 1)

Ви можете проксі:

def divisorGenerator(n):
    for x in reversed(divs(n, n)):
        yield x

ПРИМІТКА. Для мов, які підтримують, це може бути рекурсивно хвостиком.


0

Якщо припустити, що factorsфункція повертає коефіцієнти n (наприклад, factors(60)повертає список [2, 2, 3, 5]), ось функція для обчислення дільників n :

function divisors(n)
    divs := [1]
    for fact in factors(n)
        temp := []
        for div in divs
            if fact * div not in divs
                append fact * div to temp
        divs := divs + temp
    return divs

Це пітон? У всякому разі, це не пітон 3.x точно.
GinKin

Це псевдокод, який повинен бути простим для перекладу на python.
user448810

3 роки пізніше, краще пізно, ніж ніколи :) ІМО, це найпростіший, найкоротший код для цього. У мене немає таблиці порівняння, але я можу розподілити фактори і обчислити дільники до мільйона за 1 секунди на своєму портативному ноутбуці i5.
Riyaz Mansoor

0

Ось моє рішення. Це здається німим, але працює добре ... і я намагався знайти всі належні дільники, тому цикл починався з i = 2.

import math as m 

def findfac(n):
    faclist = [1]
    for i in range(2, int(m.sqrt(n) + 2)):
        if n%i == 0:
            if i not in faclist:
                faclist.append(i)
                if n/i not in faclist:
                    faclist.append(n/i)
    return facts

помилка: повернення фактів => повернення факліку
P

0

Якщо ви хочете використовувати лише розуміння списку, і нічого іншого для вас не має значення!

from itertools import combinations
from functools import reduce

def get_devisors(n):
    f = [f for f,e in list(factorGenerator(n)) for i in range(e)]
    fc = [x for l in range(len(f)+1) for x in combinations(f, l)]
    devisors = [1 if c==() else reduce((lambda x, y: x * y), c) for c in set(fc)]
    return sorted(devisors)

0

Якщо на вашому ПК є багато пам’яті, груба одиночна лінія може бути достатньо швидкою з нумером:

N = 10000000; tst = np.arange(1, N); tst[np.mod(N, tst) == 0]
Out: 
array([      1,       2,       4,       5,       8,      10,      16,
            20,      25,      32,      40,      50,      64,      80,
           100,     125,     128,     160,     200,     250,     320,
           400,     500,     625,     640,     800,    1000,    1250,
          1600,    2000,    2500,    3125,    3200,    4000,    5000,
          6250,    8000,   10000,   12500,   15625,   16000,   20000,
         25000,   31250,   40000,   50000,   62500,   78125,   80000,
        100000,  125000,  156250,  200000,  250000,  312500,  400000,
        500000,  625000, 1000000, 1250000, 2000000, 2500000, 5000000])

На моєму повільному ПК займає менше 1 секунди.


0

Моє рішення за допомогою функції генератора:

def divisor(num):
    for x in range(1, num + 1):
        if num % x == 0:
            yield x
    while True:
        yield None

-1
return [x for x in range(n+1) if n/x==int(n/x)]

3
Запитуючий попросив кращий алгоритм, а не просто гарніший формат.
Ведрак

4
Потрібно використовувати діапазон (1, n + 1), щоб уникнути поділу на нуль. Крім того, вам потрібно використовувати float (n) для першого поділу, якщо ви використовуєте Python 2.7, тут 1/2 = 0
Jens Munk

-1

Для мене це прекрасно працює, а також чисто (Python 3)

def divisors(number):
    n = 1
    while(n<number):
        if(number%n==0):
            print(n)
        else:
            pass
        n += 1
    print(number)

Не дуже швидко, але повертає дільники по черзі так, як ви хотіли, також ви можете зробити list.append (n) та list.append (число), якщо ви дійсно хочете

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.