10

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.

Robert Seifert
  • 25,078
  • 11
  • 68
  • 113

3 Answers3

13

You can find all factors of a number n by dividing it by a vector containing the integers 1 through n, then finding where the remainder after division by 1 is exactly zero (i.e., the integer results):

>> n = 60;
>> find(rem(n./(1:n), 1) == 0)

ans =

     1     2     3     4     5     6    10    12    15    20    30    60
gnovice
  • 125,304
  • 15
  • 256
  • 359
  • I thought about something like this, but never heard about `rem` before. That's very elegant! – Robert Seifert Jan 09 '14 at 19:19
  • 1
    No need for division. `rem` alone is enough (see my answer) – Luis Mendo Jan 09 '14 at 20:28
  • 1
    this brute-force method is slow for larger integers, not to mention that it can easily throw out-of-memory errors (for instance `n=1e9` would require about 8GB of memory). – Amro Jan 10 '14 at 14:54
  • 1
    @Amro: Memory is certainly a concern for larger integers, but can be mitigated with a few tricks (some of which you mention in your answer). I was simply giving the quick-and-dirty answer that would probably be applicable for most cases, and which was eluding the OP. :) – gnovice Jan 10 '14 at 15:11
  • 1
    It's really hard to decide which is the "best" answer, for small numbers your/Luis' approach is very constructive, but for fairly big numbers amro's is unbeatable, which is very well thought. – Robert Seifert Jan 10 '14 at 16:36
  • @thewaywewalk Totally understandable. No worries. :) – gnovice Jan 10 '14 at 17:17
11

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)]);
Robert Seifert
  • 25,078
  • 11
  • 68
  • 113
Amro
  • 123,847
  • 25
  • 243
  • 454
  • nice thought! Are you aware that you could halve the time by use of `mod` instead of `rem`? (That's why I never heard of `rem`, I always used `mod`, which is in most cases practically the same) - PS: I'd rather put your conclusion on the top, I don't know how much people care about the other 5 approaches ;) – Robert Seifert Jan 10 '14 at 16:38
  • @thewaywewalk: I actually don't see a difference in time between [`rem`](http://www.mathworks.com/help/matlab/ref/rem.html) and [`mod`](http://www.mathworks.com/help/matlab/ref/mod.html), as you said they are practically the same except for the listed special cases.. I'm running the latest MATLAB version of Windows 64-bit. – Amro Jan 10 '14 at 17:53
  • I tested it again with your timing function, I either get the same time for both or (in most of the cases) one of them is up to 5 times faster. However, doesn't really matter. – Robert Seifert Jan 10 '14 at 18:05
10

An improvement over @gnovice's answer is to skip the division operation: rem alone is enough:

n = 60;
find(rem(n, 1:n)==0)
Community
  • 1
  • 1
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147