Search code examples
matlabmathfactorizationnumber-theory

Factorization of an integer


While answering another, I stumbled over the question how I actually could find all factors of an integer number without the Symbolic Math Toolbox.

For example:

factor(60)

returns:

 2     2     3     5

unique(factor(60))

would therefore return all prime-factors, "1" missing.

 2     3     5

And I'm looking for a function which would return all factors (1 and the number itself are not important, but they would be nice)

Intended output for x = 60:

 1     2     3     4     5     6    10    12    15    20    30    60     

I came up with that rather bulky solution, apart from that it probably could be vectorized, isn't there any elegant solution?

x = 60;

P = perms(factor(x));
[n,m] = size(P);
Q = zeros(n,m);
for ii = 1:n
    for jj = 1:m
        Q(ii,jj) = prod(P(ii,1:jj));
    end
end

factors = unique(Q(:))'

Also I think, this solution will fail for certain big numbers, because perms requires a vector length < 11.


Solution

  • Here is a comparison of six different implementations for finding factors of an integer:

    function [t,v] = testFactors()
        % integer to factor
        %{45, 60, 2059, 3135, 223092870, 3491888400};
        n = 2*2*2*2*3*3*3*5*5*7*11*13*17*19;
    
        % functions to compare
        fcns = {
            @() factors1(n);
            @() factors2(n);
            @() factors3(n);
            @() factors4(n);
            %@() factors5(n);
            @() factors6(n);
        };
    
        % timeit
        t = cellfun(@timeit, fcns);
    
        % check results
        v = cellfun(@feval, fcns, 'UniformOutput',false);
        assert(isequal(v{:}));
    end
    
    function f = factors1(n)
        % vectorized implementation of factors2()
        f = find(rem(n, 1:floor(sqrt(n))) == 0);
        f = unique([1, n, f, fix(n./f)]);
    end
    
    function f = factors2(n)
        % factors come in pairs, the smaller of which is no bigger than sqrt(n)
        f = [1, n];
        for k=2:floor(sqrt(n))
            if rem(n,k) == 0
                f(end+1) = k;
                f(end+1) = fix(n/k);
            end
        end
        f = unique(f);
    end
    
    function f = factors3(n)
        % Get prime factors, and compute products of all possible subsets of size>1
        pf = factor(n);
        f = arrayfun(@(k) prod(nchoosek(pf,k),2), 2:numel(pf), ...
            'UniformOutput',false);
        f = unique([1; pf(:); vertcat(f{:})])'; %'
    end
    
    function f = factors4(n)
        % http://rosettacode.org/wiki/Factors_of_an_integer#MATLAB_.2F_Octave
        pf = factor(n);                    % prime decomposition
        K = dec2bin(0:2^length(pf)-1)-'0'; % all possible permutations
        f = ones(1,2^length(pf));
        for k=1:size(K)
          f(k) = prod(pf(~K(k,:)));        % compute products 
        end; 
        f = unique(f);                     % eliminate duplicates
    end
    
    function f = factors5(n)
        % @LuisMendo: brute-force implementation
        f = find(rem(n, 1:n) == 0);
    end
    
    function f = factors6(n)
        % Symbolic Math Toolbox
        f = double(evalin(symengine, sprintf('numlib::divisors(%d)',n)));
    end
    

    The results:

    >> [t,v] = testFactors();
    >> t
    t =
        0.0019        % factors1()
        0.0055        % factors2()
        0.0102        % factors3()
        0.0756        % factors4()
        0.1314        % factors6()
    
    >> numel(v{1})
    ans =
            1920
    

    Although the first vectorized version is the fastest, the equivalent loop-based implementation (factors2) is not far behind, thanks to automatic JIT optimization.

    Note that I had to disable the brute-force implementation (factors5()) because it throws an out-of-memory error (storing the vector 1:3491888400 in double-precision requires over 26GB of memory!). This method is obviously not feasible for large integers, neither space- or time-wise.

    Conclusion: use the following vectorized implementation :)

    n = 3491888400;
    f = find(rem(n, 1:floor(sqrt(n))) == 0);
    f = unique([1, n, f, fix(n./f)]);