project euler 23 MATLAB - matlab

I'm slowly working my way though problem 23 in project Euler but I;ve run into a snag. Problem #23 involves trying to find the sum of all numbers that cannot be creat by two abundant numbers.
First here's my code:
function [divisors] = SOEdivisors(num)
%SOEDIVISORS This function finds the proper divisors of a number using the sieve
%of eratosthenes
%check for primality
if isprime(num) == 1
divisors = [1];
%if not prime find divisors
else
divisors = [0 2:num/2]; %hard code a zero at one.
for i = 2:num/2
if divisors(i) %if divisors i ~= 0
%if the remainder is not zero it is not a divisor
if rem(num, divisors(i)) ~= 0
%remove that number and all its multiples from the list
divisors(i:i:fix(num/2)) = 0;
end
end
end
%add 1 back and remove all zeros
divisors(1) = 1;
divisors = divisors(divisors ~= 0);
end
end
This function finds abundant numbers
function [abundantvecfinal] = abundantnum(limitnum)
%ABUNDANTNUM creates a vector of abundant numbers up to a limit.
abundantvec = [];
%abundant number count
count = 1;
%test for abundance
for i = 1:limitnum
%find divisors
divisors = SOEdivisors(i);
%if sum of divisors is greater than number it is abundant, add it to
%vector
if i < sum(divisors)
abundantvec(count) = i;
count = count + 1;
end
end
abundantvecfinal = abundantvec;
end
And this is the main script
%This finds the sum of all numbers that cannot be written as the sum of two
%abundant numbers and under 28123
%get abundant numbers
abundant = abundantnum(28153);
%total non abundant numbers
total = 0;
%sums
sums = [];
%count moves through the newsums vector allowing for a new space for each
%new sum
count = 1;
%get complete list of possible sums under using abundant numbers under
%28123 then store them in a vector
for i = 1:length(abundant)
for x = 1:length(abundant)
%make sure it is not the same number being added to itself
if i ~= x
sums(count) = abundant(i) + abundant(x);
count = count + 1;
end
end
end
%setdiff function compares two vectors and removes all similar elements
total = sum(setdiff(1:28153, sums));
disp(total)
The first problem is it gives me the wrong answer. I know that I'm getting the correct proper divisors and the correct abundant numbers so the problem probably lies in the main script. And it seems as though it almost certainly lies in the creation of the abundant sums. I was hoping someone might be able to find an error I havent been able to.
Beyond that, the code is slow due to the multiple for loops, so I'm also looking for ways to do problems like this more efficiently.
Thanks!

Well, I don't have enough reputation to just comment. Why are you ruling out adding the same number to itself? The problem statement gives the example 12+12=24.
I also don't see a reason that x should ever be less than i. You don't need to sum the same two numbers twice.

Related

Find all numbers which are equal to the sum of the factorial of their digits

How can Find all numbers (e.g. 145= 1! + 4! + 5! = 1 + 24 + 120 = 145.)
which are equal to the sum of the factorial of their digits, by MATLAB?
I want to chop off digits, add the factorial of the digits together and compare it with the original number. If factorial summation be equal to original number, this numbers is on of the solution and must be keep. I can't code my idea, How can I code it? Is this true?
Thanks
The main reason that I post this answer is that I can't leave the use of eval in the previous answer without a decent alternative
Here is a small function to check this for any given (integer) n:
isFact = #(n) n==sum(factorial(int2str(n)-'0'));
Explanation:
int2str(n)-'0': "chop off digits"
sum(factorial(...)): "add the factorial of the digits together"
n==...: "compare it with the original number"
You can now plug it in a loop to find all the numbers between 1 to maxInt:
maxInt = 100000; % just for the example
solution = false(1,maxInt); % preallocating memory
for k = 1:maxInt
solution(k) = isFact(k);
end
find(solution) % find all the TRUE indices
The result:
ans =
1 2 145 40585
The loop above was written to be simple. If you look for further efficiency and flexibility (like not checking all the numbers between 1 to maxInt and checking array in any shape), you can change it to:
% generating a set of random numbers with no repetitions:
Vec2Check = unique(randi(1000,1,1000)); % you can change that to any array
for k = 1:numel(Vec2Check)
if isFact(Vec2Check(k))
Vec2Check(k) = Vec2Check(k)+0.1;
end
end
solution = Vec2Check(Vec2Check>round(Vec2Check))-0.1
The addition of 0.1 serves as a 'flag' that marks the numbers that isFact returns true for them. We then extract them by comparing the vector to it's rounded vertsion.
You can even go with a one-line solution:
solution = nonzeros(arrayfun(#(n) n.*(n==sum(factorial(int2str(n)-'0'))),Vec2Check))
The following snippet finds the numbers up to 1000 satisfying this condition.
numbers = [];
for i=1:1000
number_char = int2str(i);
sum = 0;
for j=1:length(number_char)
sum = sum+ factorial(eval(number_char(j)));
end
if (sum == i)
numbers(end+1) = i;
end
end
disp(numbers)
This should yield:
1 2 145
Note that if (log10(n)+1)*9! is less than n, then there is no number satisfying the condition larger than n.

How to add random values into an empty vector repeatedly without knowing when it would stop? How to count average number of steps?

Imagine the process of forming a vector v by starting with the empty vector and then repeatedly putting a randomly chosen number from 1 to 20 on the end of v. How could you use Matlab to investigate on average how many steps it takes before v contains all numbers from 1 to 20? You can define/use as many functions or scripts as you want in your answer.
v=[];
v=zeros(1,20);
for a = 1:length(v)
v(a)=randi(20);
end
since v is now only a 1x20 vector, if there are two numbers equal, it definitely
does not have all 20 numbers from 1 to 20
for i = 1:length(v)
for j = i+1:length(v)
if v(i)==v(j)
v=[v randi(20)];
i=i+1;
break;
end
end
end
for k = 1:length(v)
for n = 1:20
if v(k)==n
v=v;
elseif v(k)~=n
a=randi(20);
v=[v a];
end
if a~=n
v=[v randi(20)];
k=k+1;
break;
end
end
end
disp('number of steps: ')
i*k
First of all, the loop generating the vector must be infinite. You can break out of the loop if your condition is met. This is how you can count how many steps you need. You cannot use a loop over 20 steps if you know you'll need more than that. I like using while true and break.
Next, your method of determining if all elements are present is a method of O(n2). This can be done in O(n log n) sorting the elements. This is what unique does. It works by sorting, which, in the general case, is O(n log n) (think QuickSort). So, drawing n elements and after each checking to see if you've got them all is an operation O(n2 log n). This is expensive!
But we're talking about a finite set of integers here. Integers can be sorted in O(n) (look up histogram sort or radix sort). But we can do even better, because we don't even need to physically create the vector or sort its values. We can instead simply keep track of the elements we have seen in an array of length 20: In the loop, generate the next vector element, set the corresponding value in your 20-element array, and when all elements of this array are set, you have seen all values at least once. This is when you break.
My implementation of these two methods is below. The unique method takes 11s to do 10,000 repetitions of this process, and the other one only 0.37s. After 10,000 repetitions, I saw that you need about 72 steps on average to see all 20 integers.
function test
k = 10000;
tic;
n1 = 0;
for ii=1:k
n1 = n1 + method1;
end
n1 = n1 / k;
toc
disp(n1)
tic;
n2 = 0;
for ii=1:k
n2 = n2 + method2;
end
n2 = n2 / k;
toc
disp(n2)
end
function n = method1
k = 20;
v = [];
n = 1;
while true
v(end+1) = randi(k);
if numel(unique(v))==k
break;
end
n = n + 1;
end
end
function n = method2
k = 20;
h = zeros(20,1);
n = 1;
while true
h(randi(k)) = 1;
if all(h)
break;
end
n = n + 1;
end
end
Note on the timings: I use tic/toc here, but it is usually better to use timeit instead. The time difference is large enough for this to not matter all that much. But do make sure that the code that uses tic/toc is inside a function, and not copy-pasted to the command line. Timings are not representative when using tic/toc on the command line because the JIT compiler will not be used.
I'm not sure if I understand your question correctly, but maybe have a look at the unique() function.
if
length(unique(v)) == 20
then you have all values from 1:20 in your vector
v = []
counter = 0;
while length(unique(v)) ~= 20
a = randi(20);
v=[v a];
counter = counter +1
end
the value counter should give you the number of iterations needed until v contains all values.
If you want to get the average amount of iterations by trial and error just make a look around this code and test it 10000 times and average the results form counter.

Count values from range in Matlab

I want to count the number of values in the array. I have a code which works:
Range = [1:10^3];% [1:10^6];
N = 10^2;% 10^8
Data = randi([Range(1),Range(end)],N,1);
Counts = nan(numel(Range),1);
for iRange = 1:numel(Range)
Counts(iRange) = sum(Data==Range(iRange));
end
Could you help me to make this code faster?
I feel that it should be via unique or hist, but I could not find a solution.
N = histcounts(Data,Range)
gives me 999 numbers instead of 1000.
As Ander Biguri stated at a comment, histcounts is what you seek.
The function counts the number of values of X (Data in your example), are found at every bin between two edges, where bins defined as such:
The value X(i) is in the kth bin if edges(k) ≤ X(i) < edges(k+1).
While the last bin also includes the right edges.
This means:
For N values, you need N+1 edges.
Each bin should start at the value you want it to include (1 between 1:2, 2 between 2:3, etc).
In your example:
Counts = histcounts(Data,Range(1):(Range(end)+1))';
I wanted to point out an issue with this code:
Counts = nan(numel(Range),1);
for iRange = 1:numel(Range)
Counts(iRange) = sum(Data==Range(iRange));
end
It shows a single loop, but == and sum work over all elements in the array, making this really expensive compared to a loop that doesn't do so, especially if N is large:
Counts = zeros(numel(Range),1);
for elem = Data(:).'
Counts(elem) = Counts(elem) + 1;
end

Mean value of each column of a matrix

I have a 64 X 64 matrix that I need to find the column-wise mean values for.
However, instead of dividing by the total number of elements in each column (i.e. 64), I need to divide by the total number of non-zeros in the matrix.
I managed to get it to work for a single column as shown below. For reference, the function that generates my matrix is titled fmu2(i,j).
q = 0;
for i = 1:64
if fmu2(i,1) ~= 0;
q = q + 1;
end
end
for i = 1:64
mv = (1/q).*sum(fmu2(i,1));
end
This works for generating the "mean" value of the first column. However, I'm having trouble looping this procedure so that I will get the mean for each column. I tried doing a nested for loop, but it just calculated the mean for the entire 64 X 64 matrix instead of one column at a time. Here's what I tried:
q = 0;
for i = 1:64
for j = 1:64
if fmu2(i,j) ~= 0;
q = q +1;
end
end
end
for i = 1:64
for j = 1:64
mv = (1/q).*sum(fmu2(i,j));
end
end
Like I said, this just gave me one value for the entire matrix instead of 64 individual "means" for each column. Any help would be appreciated.
For one thing, do not call the function that generates your matrix in each iteration of a loop. This is extremely inefficient and will cause major problems if your function is complex enough to have side effects. Store the return value in a variable once, and refer to that variable from then on.
Secondly, you do not need any loops here at all. The total number of nonzeros is given by the nnz function (short for number of non-zeros). The sum function accepts an optional dimension argument, so you can just tell it to sum along the columns instead of along the rows or the whole matrix.
m = fmu2(i,1)
averages = sum(m, 1) / nnz(m)
averages will be a 64-element array with an average for each column, since sum(m, 1) is a 64 element sum along each column and nnz(m) is a scalar.
One of the great things about MATLAB is that it provides vectorized implementations of just about everything. If you do it right, you should almost never have to use an explicit loop to do any mathematical operations at all.
If you want the column-wise mean of non-zero elements you can do the following
m = randi([0,5], 5, 5); % some data
avg = sum(m,1) ./ sum(m~=0,1);
This is a column-wise sum of values, divided by the column-wise number of elements not equal to 0. The result is a row vector where each element is the average of the corresponding column in m.
Note this is very flexible, you could use any condition in place of ~=0.

how do i do a random swap but impose a limit of how far that number can move from its original position?

A set of 20 numbers have been stored inside a vector d, for example:
d = [ 5 6 7 8 9 ....]
I use
i = randperm(length(d));
d = d(i);
to randomly shuffle the numbers inside the matrix.
However, I need to find a way to limit the shuffle to ensure that the number does not move more then "5" places from its original position?
Meaning that if originally d(2) = 6, the final position of 6 should only move to d(1) to d(2+5).
Note, d(1) because the numbers cannot move to a negative position.
Any help on this would be appreciated! also, if there is a more efficient way with the shuffling please kindly let me know!
My solution would be to create a random permutation and swap bad indices as long as no index violates the distance rule.
d=data
delta=5;
i= randperm(length(d));
v=badPosition(i);
while(v~=0)
%lower bound for position
a=max(1,i(v)-5);
%upper bound for position
A=min(numel(i),i(v)+5);
spos=randi([a,A]);
h=i(v);
i(v)=i(spos);
i(spos)=h;
v=badPosition(i);
end
d=d(i)
function pos=badPosition(indices)
delta=5;
allPos=(find(indices>(1:numel(indices))+delta|indices<(1:numel(indices))-delta));
if numel(allPos)>0
pos=allPos(randi(numel(allPos)));
else
pos=0;
end
end
badPosition is a function which returns 0 if all indices are okay or one index which violates the distance rule. If multiple violations exists, a random index is chosen.
This solution will scale better:
n=2000;
I = 1:n;
k = 5;
for ii = 1+k:n-k
t = I(ii);
s = t + randi(2*k,1)-k;
if abs(I(s) - ii) <= k
I(ii) = I(s);
I(s) = t;
end
end
Then do data(I). This doesn't properly account for the edge cases but I think you can fairly easily adjust for that.
I tried it for n=100000 and it ran in less than a second.
However the shuffling is not uniformly distributed.