Czy ktoś może mi wyjaśnić skuteczny sposób znajdowania wszystkich czynników liczby w Pythonie (2.7)?
Mogę stworzyć algorytm, który to zrobi, ale myślę, że jest on słabo zakodowany i uzyskanie wyniku dla dużej liczby zajmuje zbyt dużo czasu.
Czy ktoś może mi wyjaśnić skuteczny sposób znajdowania wszystkich czynników liczby w Pythonie (2.7)?
Mogę stworzyć algorytm, który to zrobi, ale myślę, że jest on słabo zakodowany i uzyskanie wyniku dla dużej liczby zajmuje zbyt dużo czasu.
primefac
? pypi.python.org/pypi/primefacOdpowiedzi:
To bardzo szybko zwróci wszystkie czynniki danej liczby
n
.Dlaczego pierwiastek kwadratowy jako górna granica?
sqrt(x) * sqrt(x) = x
. Więc jeśli te dwa czynniki są takie same, to oba są pierwiastkiem kwadratowym. Jeśli zwiększysz jeden czynnik, musisz zmniejszyć drugi czynnik. Oznacza to, że jeden z dwóch zawsze będzie mniejszy lub równysqrt(x)
, więc wystarczy przeszukać tylko do tego punktu, aby znaleźć jeden z dwóch dopasowanych czynników. Następnie możesz użyć,x / fac1
aby uzyskaćfac2
.To
reduce(list.__add__, ...)
zbieranie małych list[fac1, fac2]
i łączenie ich w jedną długą listę.Te
[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0
powroty parę czynników, jeśli pozostała po podzieleniun
przez mniejszy jest zero (nie trzeba sprawdzić większy zbyt, to po prostu robi się to poprzez podzielenien
przez mniejszy).Na
set(...)
zewnątrz pozbywa się duplikatów, co dzieje się tylko w przypadku doskonałych kwadratów. Bon = 4
to powróci2
dwa razy, więcset
pozbądź się jednego z nich.źródło
sqrt
- to prawdopodobnie zanim ludzie naprawdę myśleli o wsparciu Pythona 3. Myślę, że strona, z której ją otrzymałem, wypróbowała to__iadd__
i była szybsza . Wydaje mi się, że coś pamiętam ox**0.5
byciu szybszym niżsqrt(x)
w pewnym momencie - i dzięki temu jest to bardziej niezawodne.if not n % i
if n % i == 0
/
zwróci wartość zmiennoprzecinkową, nawet jeśli oba argumenty są liczbami całkowitymi i są one dokładnie podzielne, tj .4 / 2 == 2.0
nie2
.from functools import reduce
aby to zadziałało.Rozwiązanie przedstawione przez @agf jest świetne, ale można osiągnąć ~ 50% szybszy czas działania przy dowolnym dziwnym kursie liczby , sprawdzając parzystość. Ponieważ czynniki liczby nieparzystej zawsze są same w sobie nieparzyste, nie ma potrzeby sprawdzania ich w przypadku liczb nieparzystych.
Właśnie zacząłem samodzielnie rozwiązywać łamigłówki Projektu Euler . W niektórych przypadkach sprawdzanie dzielnika jest wywoływane w dwóch zagnieżdżonych
for
pętlach, a zatem wykonanie tej funkcji jest niezbędne.Łącząc ten fakt z doskonałym rozwiązaniem agf, otrzymałem taką funkcję:
Jednak w przypadku małych liczb (~ <100) dodatkowe obciążenie wynikające z tej zmiany może spowodować, że funkcja będzie działać dłużej.
Wykonałem kilka testów, żeby sprawdzić prędkość. Poniżej znajduje się użyty kod. Aby utworzyć różne wykresy, odpowiednio zmieniłem
X = range(1,100,1)
.X = zakres (1,100,1)
Nie ma tutaj znaczącej różnicy, ale przy większych liczbach przewaga jest oczywista:
X = zakres (1,100000,1000) (tylko liczby nieparzyste)
X = zakres (2,100000,100) (tylko liczby parzyste)
X = zakres (1,100000,1001) (zmienna parzystość)
źródło
odpowiedź agf jest naprawdę fajna. Chciałem zobaczyć, czy mogę go przepisać, aby uniknąć używania
reduce()
. Oto, co wymyśliłem:Wypróbowałem także wersję, która wykorzystuje skomplikowane funkcje generatora:
Zmierzyłem czas, obliczając:
Uruchomiłem go raz, aby Python mógł go skompilować, a następnie uruchomiłem go pod poleceniem time (1) trzy razy i zachowałem najlepszy czas.
Zauważ, że wersja itertools buduje krotkę i przekazuje ją do flatten_iter (). Jeśli zmienię kod, aby zamiast tego zbudować listę, nieco zwalnia:
Uważam, że podstępna wersja funkcji generatora jest najszybsza z możliwych w Pythonie. Ale tak naprawdę nie jest dużo szybszy niż wersja zredukowana, około 4% szybciej na podstawie moich pomiarów.
źródło
for tup in
):factors = lambda n: {f for i in range(1, int(n**0.5)+1) if n % i == 0 for f in [i, n//i]}
Alternatywne podejście do odpowiedzi agf:
źródło
reduce()
jest znacznie szybszy, więc prawie wszystko pozareduce()
częścią zrobiłem tak samo, jak agf. Ze względu na czytelność byłoby miło zobaczyć wywołanie funkcji takie jakis_even(n)
zamiast wyrażenian % 2 == 0
.Oto alternatywa dla rozwiązania @ agf, które implementuje ten sam algorytm w bardziej pythonowym stylu:
To rozwiązanie działa zarówno w Pythonie 2, jak i Pythonie 3 bez importu i jest znacznie bardziej czytelne. Nie testowałem wydajności tego podejścia, ale asymptotycznie powinno być tak samo, a jeśli wydajność jest poważnym problemem, żadne rozwiązanie nie jest optymalne.
źródło
W SymPy istnieje branżowy algorytm zwany factorint :
Zajęło to mniej niż minutę. Przełącza się między mieszanką metod. Zobacz dokumentację, do której link znajduje się powyżej.
Biorąc pod uwagę wszystkie czynniki pierwsze, wszystkie inne czynniki można łatwo zbudować.
Zauważ, że nawet jeśli zaakceptowana odpowiedź mogła trwać wystarczająco długo (tj. Wieczność), aby uwzględnić powyższą liczbę, w przypadku niektórych dużych liczb zakończy się niepowodzeniem, jak w poniższym przykładzie. Wynika to z niechlujstwa
int(n**0.5)
. Na przykład, kiedyn = 10000000000000079**2
mamyPonieważ 10000000000000079 jest liczbą pierwszą , algorytm zaakceptowanej odpowiedzi nigdy nie znajdzie tego czynnika. Zwróć uwagę, że nie jest to tylko pojedynek; dla większych liczb będzie wyłączony o więcej. Z tego powodu lepiej unikać liczb zmiennoprzecinkowych w tego rodzaju algorytmach.
źródło
sympy.divisors
to nie jest dużo szybsze, szczególnie w przypadku liczb z kilkoma dzielnikami. Masz jakieś testy?sympy.divisors
dla 100 000 i wolniejsza dla czegokolwiek wyższego (kiedy prędkość ma znaczenie). (I oczywiściesympy.divisors
działa na liczbach takich jak10000000000000079**2
.)Dla n do 10 ** 16 (może nawet trochę więcej), oto szybkie, czyste rozwiązanie Pythona 3.6,
źródło
Dalsze ulepszenie rozwiązania afg i eryksun. Poniższy fragment kodu zwraca posortowaną listę wszystkich czynników bez zmiany asymptotycznej złożoności czasu wykonywania:
Pomysł: Zamiast używać funkcji list.sort () do uzyskania posortowanej listy, która daje złożoność nlog (n); Znacznie szybciej jest używać list.reverse () na l2, co wymaga złożoności O (n). (Tak powstaje Python.) Po l2.reverse (), l2 może zostać dołączone do l1, aby otrzymać posortowaną listę czynników.
Zauważ, że l1 zawiera i -s, które rosną. l2 zawiera q -s, które maleją. To jest powód wykorzystania powyższej idei.
źródło
list.reverse
jest O (n), a nie O (1), a nie to, że zmienia ogólną złożoność.l1 + l2.reversed()
zamiast odwracać listę w miejscu.Wypróbowałem większość z tych wspaniałych odpowiedzi z czasem, aby porównać ich skuteczność z moją prostą funkcją, a jednak ciągle widzę, że moje wyniki przewyższają te wymienione tutaj. Pomyślałem, że podzielę się tym i zobaczę, co wszyscy myślicie.
Jak jest napisane, będziesz musiał zaimportować matematykę do testu, ale zastąpienie math.sqrt (n) przez n **. 5 powinno działać równie dobrze. Nie przejmuję się marnowaniem czasu na szukanie duplikatów, ponieważ duplikaty nie mogą istnieć w zestawie.
źródło
xrange(1, int(math.sqrt(n)) + 1)
jest oceniany raz.Oto kolejna alternatywa bez redukcji, która działa dobrze z dużymi liczbami. Używa
sum
do spłaszczenia listy.źródło
sum
anireduce(list.__add__)
do spłaszczania listy.Pamiętaj, aby złapać liczbę większą niż w
sqrt(number_to_factor)
przypadku nietypowych liczb, takich jak 99, która ma 3 * 3 * 11 ifloor sqrt(99)+1 == 10
.źródło
x=8
oczekiwanego[1, 2, 4, 8]
[2, 2, 2]
Najprostszy sposób na znalezienie czynników liczby:
źródło
Oto przykład, jeśli chcesz użyć liczb pierwszych, aby przejść znacznie szybciej. Listy te można łatwo znaleźć w Internecie. Dodałem komentarze w kodzie.
źródło
potencjalnie bardziej wydajny algorytm niż te już tutaj przedstawione (zwłaszcza jeśli są w nim małe fakty pierwsze
n
). sztuczka polega na dostosowaniu limitu , do którego wymagany jest podział próbny za każdym razem, gdy zostaną znalezione czynniki pierwsze:jest to oczywiście nadal podział próbny i nic bardziej wyszukanego. a zatem nadal bardzo ograniczona jego wydajność (szczególnie w przypadku dużych liczb bez małych dzielników).
to jest python3; podział
//
powinien być jedyną rzeczą, którą musisz dostosować do Pythona 2 (dodajfrom __future__ import division
).źródło
Użycie
set(...)
sprawia, że kod jest nieco wolniejszy i jest naprawdę konieczne tylko wtedy, gdy sprawdzasz pierwiastek kwadratowy. Oto moja wersja:if sq*sq != num:
Warunkiem jest to konieczne do liczby, jak 12, przy czym pierwiastek nie jest liczbą całkowitą, a podłoga pierwiastka jest czynnikiem.Zwróć uwagę, że ta wersja nie zwraca samego numeru, ale jest to łatwe rozwiązanie, jeśli chcesz. Dane wyjściowe również nie są sortowane.
Zmierzyłem czas 10000 razy na wszystkich numerach 1-200 i 100 razy na wszystkich numerach 1-5000. Przewyższa wszystkie inne testowane przeze mnie wersje, w tym rozwiązania dansalmo, Jasona Schorna, Oxrocka, agf, steveha i eryksun, choć zdecydowanie najbliższe jest rozwiązanie Oxrock.
źródło
Twój maksymalny współczynnik to nie więcej niż liczba, więc powiedzmy
voilá!
źródło
źródło
Użyj czegoś tak prostego, jak poniższa lista, zwracając uwagę, że nie musimy testować 1 i liczby, którą próbujemy znaleźć:
W odniesieniu do pierwiastka kwadratowego, powiedzmy, że chcemy znaleźć współczynniki 10. Część całkowita funkcji
sqrt(10) = 4
powodurange(1, int(sqrt(10))) = [1, 2, 3, 4]
i testowaniu do 4 wyraźnie brakuje 5.Chyba że brakuje mi czegoś, co sugerowałbym, jeśli musisz to zrobić w ten sposób, używając
int(ceil(sqrt(x)))
. Oczywiście powoduje to wiele niepotrzebnych wywołań funkcji.źródło
Myślę, że dla czytelności i szybkości rozwiązanie @ oxrock jest najlepsze, więc oto kod przepisany dla Pythona 3+:
źródło
Byłem dość zaskoczony, gdy zobaczyłem to pytanie, że nikt nie używał numpy, nawet jeśli numpy jest znacznie szybszy niż pętle Pythona. Wdrażając rozwiązanie @ agf z numpy i okazało się średnio 8x szybciej . Wierzę, że gdybyś wdrożył niektóre inne rozwiązania w numpy, możesz uzyskać niesamowite czasy.
Oto moja funkcja:
Zauważ, że liczby na osi X nie są danymi wejściowymi dla funkcji. Dane wejściowe funkcji to 2 do liczby na osi x minus 1. Zatem gdzie dziesięć to wartość wejściowa to 2 ** 10-1 = 1023
źródło
źródło
Myślę, że jest to najprostszy sposób:
źródło