web-dev-qa-db-ja.com

Pythonで数値のすべての要因を見つける最も効率的な方法は何ですか?

誰かがPython(2.7)の数値のすべての要因を見つける効率的な方法を説明できますか?

これを行うためのアルゴリズムを作成できますが、コーディングが不十分であり、多数の結果を生成するには時間がかかりすぎると思います。

126
Adnan
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つの長いリストに結合します。

nを小さい方で除算したときの余りがゼロの場合、[i, n/i] for i in range(1, int(sqrt(n)) + 1) if n % i == 0は因子のペアを返します(大きい方をチェックする必要はありません。nを小さい方で除算するだけで取得できます)。

外側のset(...)は重複を取り除きつつあり、これは完全な正方形に対してのみ発生します。 n = 4の場合、これは2を2回返すため、setはそのうちの1つを取り除きます。

242
agf

@agfが提示するソリューションは素晴らしいものですが、パリティをチェックすることにより、任意のodd数の実行時間を最大50%高速化できます。奇数の要素は常に奇数であるため、奇数を処理する際にこれらをチェックする必要はありません。

Project Euler 自分でパズルを解き始めました。いくつかの問題では、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 = range(1,100,1) X = range(1,100,1)

ここでは大きな違いはありませんが、数字が大きいほど利点は明らかです。

X = range(1,100000,1000)(奇数のみ) X = range(1,100000,1000) (only odd numbers)

X = range(2,100000,100)(偶数のみ) X = range(2,100000,100) (only even numbers)

X = range(1,100000,1001)(代替パリティ) X = range(1,100000,1001) (alternating parity)

45
Steinar Lima

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をコンパイルするために一度実行した後、time(1)コマンドで3回実行し、最適な時間を保ちました。

  • バージョンを削減:11.58秒
  • itertoolsバージョン:11.49秒
  • トリッキーなバージョン:11.12秒

ItertoolsバージョンはTupleを構築し、flatten_iter()に渡すことに注意してください。代わりにリストを作成するようにコードを変更すると、少し遅くなります。

  • iterools(リスト)バージョン:11.62秒

トリッキーなジェネレーター関数バージョンは、Pythonで可能な限り高速であると思います。しかし、実際には削減バージョンよりもはるかに高速ではなく、測定値に基づいて約4%高速です。

27
steveha

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
11
Eryk Sun

Nが最大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))
6
Bruno Astrolino

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)の複雑さを与えるソート済みリストを取得する代わりに。 O(n)の複雑さをとるl2でlist.reverse()を使用する方がはるかに高速です。 (これがpythonの作成方法です。)l2.reverse()の後、l2をl1に追加して、因子のソート済みリストを取得できます。

通知、l1には -s増加しています。 l2に含まれる q-s減少しています。これが、上記のアイデアを使用した理由です。

6
Pranjal Mittal

これらのすばらしい答えのほとんどを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に置き換えることも同様に機能します。とにかく重複がセットに存在することはできないので、重複のチェックに時間を浪費しません。

6
oxrock

以下は、同じアルゴリズムをよりPython的なスタイルで実装する@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の両方で機能し、はるかに読みやすくなっています。このアプローチのパフォーマンスはテストしていませんが、漸近的には同じである必要があります。パフォーマンスが深刻な問題である場合、どちらのソリューションも最適ではありません。

5
Julian

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は素数 であるため、受け入れられた回答のアルゴリズムはこの要素を見つけられません。それは単なるオフバイワンではないことに注意してください。数字が大きくなると、さらに多くなります。このため、この種のアルゴリズムでは浮動小数点数を避ける方が良いでしょう。

5
Evgeni Sergeev

次に、reduceを使用しない別の代替方法を示します。これは、大きな数値で良好に機能します。 sumを使用してリストをフラット化します。

def factors(n):
    return set(sum([[i, n//i] for i in xrange(1, int(n**0.5)+1) if not n%i], []))
5
dansalmo

3 * 3 * 11とsqrt(number_to_factor)を持つ99のような異常な数値の場合は、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
4
mbowden

すでにここに提示されているものよりも潜在的に効率的なアルゴリズム(特に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部門のみです。

2

数の因子を見つける最も簡単な方法:

def factors(x):
    return [i for i in range(1,x+1) if x%i==0]
2
GooDeeJaY

あなたの最大係数はあなたの数よりも大きくないので、たとえば

def factors(n):
    factors = []
    for i in range(1, n//2+1):
        if n % i == 0:
            factors.append (i)
    factors.append(n)

    return factors

ほら!

2
Polina Novikova

素数を使用してさらに高速にしたい場合の例を次に示します。これらのリストは、インターネットで簡単に見つけることができます。コードにコメントを追加しました。

# 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)]
2
Pierre Thibault

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で10000回、すべての番号1-5000で100回実行する時間を計りました。これは、ダンサルモ、ジェイソンスクホーン、オックスロック、agf、ステベハ、エリクスンのソリューションを含む、私がテストした他のすべてのバージョンよりも優れていますが、オックスロックの方がはるかに近いです。

1
HamsterBoo

次のリスト内包表記のような単純なものを使用します。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)))を使用してこの方法でそれを行う必要がある場合。もちろん、これは多くの不必要な関数呼び出しを生成します。

1
Jason Schorn
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;
}
0
Tangang Atanga

読みやすさと速度の面で、@ 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
0
Nic Scozzaro
 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. 
0
Tangang Atanga

Numpyがpythonループよりもway fastであるにもかかわらず、誰もnumpyを使用していないというこの質問を見たとき、私はかなり驚きました。 @agfのソリューションをnumpyで実装すると、平均で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軸の数字は関数への入力ではないことに注意してください。関数への入力は2で、x軸の数値から1を引いた値です。したがって、10の場合、入力は2 ** 10-1 = 1023になります。

Performance test results of using numpy instead of for loops.