Pythonで数のすべての要因を見つける最も効率的な方法は何ですか?
-
22-10-2019 - |
質問
誰かが私にPython(2.7)の数のすべての要因を見つける効率的な方法を説明できますか?
これを行うためにアルゴリズムを作成できますが、コードが不十分で、多数の結果を生み出すには時間がかかりすぎると思います。
解決
from functools import reduce
def factors(n):
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(n**0.5) + 1) if n % i == 0)))
これにより、すべての要因が非常に迅速に数字を返します n
.
なぜ上限として平方根ですか?
sqrt(x) * sqrt(x) = x
. 。したがって、2つの要因が同じ場合、それらは両方とも平方根です。 1つの要因を大きくする場合、他の要素を小さくする必要があります。これは、2つのうちの1つが常に sqrt(x)
, 、そのため、そのポイントまで検索するだけで、2つの一致する要因の1つを見つける必要があります。その後、使用できます x / fac1
取得するため fac2
.
reduce(list.__add__, ...)
の小さなリストを取得しています [fac1, fac2]
そして、それらを1つの長いリストにまとめます。
[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0
あなたが分割するときに残りがあれば一対の要因を返します n
小さい方ではゼロです(大きなものもチェックする必要はありません。分割するだけでそれを取得します n
小さいものによって。)
set(...)
外側では、完全な正方形でのみ発生する重複を取り除きます。にとって n = 4
, 、これは戻ります 2
2回、だから set
それらの1つを取り除きます。
他のヒント
@Agfが提示するソリューションは素晴らしいですが、任意のために〜50%の実行時間を達成することができます 奇数 パリティをチェックして番号。奇数の要因は常に奇妙なので、奇数に対処するときにこれらをチェックする必要はありません。
解決を始めたばかりです プロジェクトオイラー 自分で困惑します。いくつかの問題では、2つのネストされた2つの内部で除裂チェックが呼び出されます for
したがって、この関数のパフォーマンスが不可欠です。
この事実をAGFの優れたソリューションと組み合わせて、私はこの機能になりました。
from math import sqrt
def factors(n):
step = 2 if n%2 else 1
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(sqrt(n))+1, step) if n % i == 0)))
ただし、少数(〜<100)では、この変更からの余分なオーバーヘッドにより、関数が長くなる可能性があります。
速度を確認するためにいくつかのテストを実行しました。以下は使用されているコードです。異なるプロットを生成するために、私は変更しました X = range(1,100,1)
によると。
import timeit
from math import sqrt
from matplotlib.pyplot import plot, legend, show
def factors_1(n):
step = 2 if n%2 else 1
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(sqrt(n))+1, step) if n % i == 0)))
def factors_2(n):
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0)))
X = range(1,100000,1000)
Y = []
for i in X:
f_1 = timeit.timeit('factors_1({})'.format(i), setup='from __main__ import factors_1', number=10000)
f_2 = timeit.timeit('factors_2({})'.format(i), setup='from __main__ import factors_2', number=10000)
Y.append(f_1/f_2)
plot(X,Y, label='Running time with/without parity check')
legend()
show()
x =範囲(1,100,1)
ここには大きな違いはありませんが、数字が大きいと、利点は明らかです。
x = range(1,100000,1000)(奇数のみ)
x = range(2,100000,100)(偶数のみ)
x = range(1,100000,1001)(交互のパリティ)
AGFの答えは本当にかなりクールです。使用を避けるために書き直すことができるかどうかを見たかった reduce()
. 。これは私が思いついたものです:
import itertools
flatten_iter = itertools.chain.from_iterable
def factors(n):
return set(flatten_iter((i, n//i)
for i in range(1, int(n**0.5)+1) if n % i == 0))
また、トリッキーなジェネレーター関数を使用するバージョンも試しました。
def factors(n):
return set(x for tup in ([i, n//i]
for i in range(1, int(n**0.5)+1) if n % i == 0) for x in tup)
コンピューティングによってタイミングを合わせました:
start = 10000000
end = start + 40000
for n in range(start, end):
factors(n)
私はそれを一度走らせて、Pythonをコンパイルしてから、時間の下でそれを実行しました(1)コマンドを3回、最高の時間を維持しました。
- バージョンを削減:11.58秒
- Itertoolsバージョン:11.49秒
- トリッキーバージョン:11.12秒
itertoolsバージョンはタプルを構築し、flatten_iter()に渡すことに注意してください。代わりにリストを作成するためにコードを変更すると、少し遅くなります。
- Iterools(リスト)バージョン:11.62秒
Tricky Generator Functionsバージョンは、Pythonで可能な限り最速であると思います。しかし、それは私の測定に基づいて、redoceバージョンよりもはるかに高速ではなく、約4%高速です。
AGFの答えへの代替アプローチ:
def factors(n):
result = set()
for i in range(1, int(n ** 0.5) + 1):
div, mod = divmod(n, i)
if mod == 0:
result |= {i, div}
return result
私はこれらの素晴らしい答えのほとんどをTimeITで試してみて、それらの効率と私の単純な機能を比較しましたが、私はここにリストされているものよりも優れていることを常に見ています。私はそれを共有し、皆さんがどう思うかを見たと思いました。
def factors(n):
results = set()
for i in xrange(1, int(math.sqrt(n)) + 1):
if n % i == 0:
results.add(i)
results.add(int(n/i))
return results
書かれているように、テストするには数学をインポートする必要がありますが、math.sqrt(n)をn**。5に置き換える必要があります。重複はセットに関係なくセットに存在できないため、重複をチェックすることを気にしません。
AFG&Eryksunのソリューションのさらなる改善。次のコードは、実行時間を漸近的な複雑さを変更せずに、すべての要因のソートされたリストを返します。
def factors(n):
l1, l2 = [], []
for i in range(1, int(n ** 0.5) + 1):
q,r = n//i, n%i # Alter: divmod() fn can be used.
if r == 0:
l1.append(i)
l2.append(q) # q's obtained are decreasing.
if l1[-1] == l2[-1]: # To avoid duplication of the possible factor sqrt(n)
l1.pop()
l2.reverse()
return l1 + l2
アイデア:list.sort()関数を使用して、nlog(n)の複雑さを与えるソート付きリストを取得する代わりに。 l2でlist.reverse()を使用する方がはるかに高速です。これにより、O(n)の複雑さが必要です。 (これがPythonの作成方法です。)L2.Reverse()の後、L2をL1に追加して、順序のソートリストを取得できます。
注意、L1が含まれます 私- 増加しています。 L2が含まれます Q- 減少しています。それが上記のアイデアを使用する背後にある理由です。
これは、 @AGFのソリューションに代わるもので、同じアルゴリズムをよりパイソンスタイルで実装しています。
def factors(n):
return set(
factor for i in range(1, int(n**0.5) + 1) if n % i == 0
for factor in (i, n//i)
)
このソリューションは、輸入のないPython 2とPython 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))
Sympyには、業界強度のアルゴリズムが呼ばれています FactorInt:
>>> from sympy import factorint
>>> factorint(2**70 + 3**80)
{5: 2,
41: 1,
101: 1,
181: 1,
821: 1,
1597: 1,
5393: 1,
27188665321L: 1,
41030818561L: 1}
これには1分以内にかかりました。メソッドのカクテルの間に切り替えます。上記のリンクされたドキュメントを参照してください。
すべての主要な要因を考えると、他のすべての要因を簡単に構築できます。
受け入れられた回答が、上記の数を考慮するのに十分な長さ(つまり、永遠)を実行することを許可されたとしても、多数の場合、次の例で失敗することに注意してください。これはずさんなためです int(n**0.5)
. 。たとえば、いつ n = 10000000000000079**2
, 、 我々は持っています
>>> int(n**0.5)
10000000000000078L
以来 10000000000000079は素数です, 、受け入れられた回答のアルゴリズムは、この要素を見つけることはありません。それは単なるオフワンではないことに注意してください。より大きな数の場合、それはもっと多くオフになります。このため、この種のアルゴリズムの浮動小数点数を避けることをお勧めします。
これは、多数でうまく機能する、減らすことなく別の代替です。それは使用しています sum
リストを平らにします。
def factors(n):
return set(sum([[i, n//i] for i in xrange(1, int(n**0.5)+1) if not n%i], []))
より大きい数を必ずつかんでください sqrt(number_to_factor)
99のような珍しい数字の場合、3*3*11と floor sqrt(99)+1 == 10
.
import math
def factor(x):
if x == 0 or x == 1:
return None
res = []
for i in range(2,int(math.floor(math.sqrt(x)+1))):
while x % i == 0:
x /= i
res.append(i)
if x != 1: # Unusual numbers
res.append(x)
return res
プライム番号を使用してはるかに速く進む場合の例を次に示します。これらのリストは、インターネット上で簡単に見つけることができます。コードにコメントを追加しました。
# http://primes.utm.edu/lists/small/10000.txt
# First 10000 primes
_PRIMES = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013,
# Mising a lot of primes for the purpose of the example
)
from bisect import bisect_left as _bisect_left
from math import sqrt as _sqrt
def get_factors(n):
assert isinstance(n, int), "n must be an integer."
assert n > 0, "n must be greather than zero."
limit = pow(_PRIMES[-1], 2)
assert n <= limit, "n is greather then the limit of {0}".format(limit)
result = set((1, n))
root = int(_sqrt(n))
primes = [t for t in get_primes_smaller_than(root + 1) if not n % t]
result.update(primes) # Add all the primes factors less or equal to root square
for t in primes:
result.update(get_factors(n/t)) # Add all the factors associted for the primes by using the same process
return sorted(result)
def get_primes_smaller_than(n):
return _PRIMES[:_bisect_left(_PRIMES, n)]
すでにここに提示されているものよりも潜在的に効率的なアルゴリズムがあります(特に、小さなプライムファクトンがある場合 n
)。ここでのトリックは次のとおりです 制限を調整します 主要な要因が見つかるたびに、どの試験部門が必要なのか:
def factors(n):
'''
return prime factors and multiplicity of n
n = p0^e0 * p1^e1 * ... * pk^ek encoded as
res = [(p0, e0), (p1, e1), ..., (pk, ek)]
'''
res = []
# get rid of all the factors of 2 using bit shifts
mult = 0
while not n & 1:
mult += 1
n >>= 1
if mult != 0:
res.append((2, mult))
limit = round(sqrt(n))
test_prime = 3
while test_prime <= limit:
mult = 0
while n % test_prime == 0:
mult += 1
n //= test_prime
if mult != 0:
res.append((test_prime, mult))
if n == 1: # only useful if ek >= 3 (ek: multiplicity
break # of the last prime)
limit = round(sqrt(n)) # adjust the limit
test_prime += 2 # will often not be prime...
if n != 1:
res.append((n, 1))
return res
もちろん、これはまだ裁判部門であり、これ以上派手なものはありません。したがって、その効率はまだ非常に限られています(特に小さな除数のない大きな数の場合)。
これはPython3です。分裂 //
Python 2に適応するために必要な唯一のものである必要があります(追加 from __future__ import division
).
あなたの最大要因はあなたの番号以上ではないので、言いましょう
def factors(n):
factors = []
for i in range(1, n//2+1):
if n % i == 0:
factors.append (i)
factors.append(n)
return factors
Voilá!
数の要因を見つける最も簡単な方法:
def factors(x):
return [i for i in range(1,x+1) if x%i==0]
使用 set(...)
コードをわずかに遅くし、平方根をチェックするときにのみ本当に必要です。これが私のバージョンです:
def factors(num):
if (num == 1 or num == 0):
return []
f = [1]
sq = int(math.sqrt(num))
for i in range(2, sq):
if num % i == 0:
f.append(i)
f.append(num/i)
if sq > 1 and num % sq == 0:
f.append(sq)
if sq*sq != num:
f.append(num/sq)
return f
if sq*sq != num:
条件は、平方根が整数ではなく、平方根の床が要因である12のような数字に必要です。
このバージョンは番号自体を返さないことに注意してください。しかし、それはあなたがそれを望むなら簡単な修正です。出力もソートされていません。
すべての数字1〜200およびすべての数字1〜5000で10000回実行するタイミングを計りました。 Dansalmo's、Jason Schorn's、Oxrock's、Agf's、Steveha's、Eryksun's Solutionsなど、私がテストした他のすべてのバージョンよりも優れていますが、Oxrock'sははるかに近いです。
次のリストの理解と同じように単純なものを使用して、テスト1と見つけようとしている数をテストする必要はありません。
def factors(n):
return [x for x in range(2, n//2+1) if n%x == 0]
平方根の使用を参照して、10の係数を見つけたいとします。 sqrt(10) = 4
したがって range(1, int(sqrt(10))) = [1, 2, 3, 4]
そして、最大4までのテストは明らかに5を逃します。
私が何かを逃していない限り、私が提案するだろう、あなたがこの方法でそれをしなければならないなら、 int(ceil(sqrt(x)))
. 。もちろん、これは機能への多くの不必要な呼び出しを生成します。
読みやすさとスピードのために @oxrockのソリューションが最適だと思いますので、Python 3+のコード書き換えは次のとおりです。
def num_factors(n):
results = set()
for i in range(1, int(n**0.5) + 1):
if n % i == 0: results.update([i,int(n/i)])
return results
numpyがそうであっても誰もnumpyを使用していないというこの質問を見たとき、私はかなり驚きました より速い Pythonループよりも。 numpyで @AGFのソリューションを実装することにより、平均して判明しました 8倍高速。他のソリューションのいくつかをnumpyに実装した場合、驚くべき時代を迎えることができると信じています。
これが私の機能です:
import numpy as np
def b(n):
r = np.arange(1, int(n ** 0.5) + 1)
x = r[np.mod(n, r) == 0]
return set(np.concatenate((x, n / x), axis=None))
X軸の数は関数への入力ではないことに注意してください。関数への入力は、x軸マイナス1の数値2の2です。したがって、10は入力が2 ** 10-1 = 1023になります
import 'dart:math';
generateFactorsOfN(N){
//determine lowest bound divisor range
final lowerBoundCheck = sqrt(N).toInt();
var factors = Set<int>(); //stores factors
/**
* Lets take 16:
* 4 = sqrt(16)
* start from 1 ... 4 inclusive
* check mod 16 % 1 == 0? set[1, (16 / 1)]
* check mod 16 % 2 == 0? set[1, (16 / 1) , 2 , (16 / 2)]
* check mod 16 % 3 == 0? set[1, (16 / 1) , 2 , (16 / 2)] -> unchanged
* check mod 16 % 4 == 0? set[1, (16 / 1) , 2 , (16 / 2), 4, (16 / 4)]
*
* ******************* set is used to remove duplicate
* ******************* case 4 and (16 / 4) both equal to 4
* return factor set<int>.. this isn't ordered
*/
for(var divisor = 1; divisor <= lowerBoundCheck; divisor++){
if(N % divisor == 0){
factors.add(divisor);
factors.add(N ~/ divisor); // ~/ integer division
}
}
return factors;
}
import math
'''
I applied finding prime factorization to solve this. (Trial Division)
It's not complicated
'''
def generate_factors(n):
lower_bound_check = int(math.sqrt(n)) # determine lowest bound divisor range [16 = 4]
factors = set() # store factors
for divisors in range(1, lower_bound_check + 1): # loop [1 .. 4]
if n % divisors == 0:
factors.add(divisors) # lower bound divisor is found 16 [ 1, 2, 4]
factors.add(n // divisors) # get upper divisor from lower [ 16 / 1 = 16, 16 / 2 = 8, 16 / 4 = 4]
return factors # [1, 2, 4, 8 16]
print(generate_factors(12)) # {1, 2, 3, 4, 6, 12} -> pycharm output
Pierre Vriens hopefully this makes more sense. this is an O(nlogn) solution.
これはそれを行う最も簡単な方法だと思います:
x = 23
i = 1
while i <= x:
if x % i == 0:
print("factor: %s"% i)
i += 1