I'm trying to write a function in Octave to convert roman numerals into decimal numbers.
What I have so far has two major problems:
1) It will only work for roman numerals up to 6 letters in length
2) The answer is only correct if each letter is smaller than the previous. ie it will not compute (IV) correctly as 4, but incorrectly as 6.
I need to somehow fix both of these problems. I have a strong suspicion that my approach to the problem is wrong and that there is an altogether more efficient way of doing this.
Anyway, if the problem interests you and/or you know of a good way to do this, any help is much appreciated.
function roman2num(s)
decimal = [1000, 500, 100, 50, 10, 5, 1];
roman = ["M", "D", "C", "L", "X", "V", "I"];
num = 0;
for i = 1:7
if strcmp(s(1), roman(i))
num += decimal(i);
break
end
end
if length(s) >= 2
for i = 1:7
if strcmp(s(2), roman(i))
num += decimal(i);
break
end
end
end
if length(s) >= 3
for i = 1:7
if strcmp(s(3), roman(i))
num += decimal(i);
break
end
end
end
if length(s) >= 4
for i = 1:7
if strcmp(s(4), roman(i))
num += decimal(i);
break
end
end
end
if length(s) >= 5
for i = 1:7
if strcmp(s(5), roman(i))
num += decimal(i);
break
end
end
end
if length(s) >= 6
for i = 1:7
if strcmp(s(6), roman(i))
num += decimal(i);
break
end
end
end
num
end
As far as I can tell the only rule you need to worry about is determining if the letter constitutes an "add" or "subtract" operation. For some letter, we only subtract it if the first non-equal letter to the right represents a greater value.
For example in 'IIV' The first non-equal letter to the right of both I's is V so we subtract 2 and add 5 for the V since it has no letters to the right.
Implementing this rule in MATLAB is fairly straightforward.
function num = roman2num(s)
decimal = [1000, 500, 100, 50, 10, 5, 1];
roman = ['M', 'D', 'C', 'L', 'X', 'V', 'I'];
tokens = arrayfun(#(x) decimal(find(roman==x,1)), char(s));
num = tokens(end);
for idx = 1:numel(tokens)-1
val = tokens(idx);
ridx = find(tokens(idx+1:end) ~= val, 1);
if ~isempty(ridx) && tokens(ridx + idx) > val
num = num - val;
else
num = num + val;
end
end
I tested using all the numerals between 1 and 3333.
Related
I wrote some code to display the prime numbers between 2 and a user-chosen number, following the pseudocode on wikipedia. I am not sure why this does not work, as I have the increments correct following Erastothenes' Sieve. Please help me.
I have tried changing the bounds but this did not work.
There are no errors, but it returns the wrong output. If I enter 10, it returns 2, 3, 4, 5, 6, 7, 8, 9, 10.
n=input("Enter an upper limit: ");
nums= 2:n;
p=2;
for i = p:sqrt(n)
for j = (i^2):i:sqrt(n)
nums(j) = 0;
end
end
for k = 1:n-1
if nums(k) ~= 0
disp(nums(k))
end
end
You can use the primes function in MATLAB for this
N = 10; % upper limit
p = primes(N); % List of all primes up to (and including) N
With one step less automation, you could use another in-built isprime
p = 1:N; % List of all numbers up to N
p( ~isprime( p ) ) = []; % Remove non-primes
Finally without using built-ins, we can address your code!
I assume you're referring to this pseudocode for the Sieve of Eratosthenes on Wikipedia.
Input: an integer n > 1.
Let A be an array of Boolean values, indexed by integers 2 to n,
initially all set to true.
for i = 2, 3, 4, ..., not exceeding √n:
if A[i] is true:
for j = i2, i2+i, i2+2i, i2+3i, ..., not exceeding n:
A[j] := false.
Output: all i such that A[i] is true.
I'll follow it step by step, pointing out differences to your code:
n = 10;
A = [false; true(n-1,1)]; % array of true Booleans, first element (1) is not prime
% But I've included a first element to make indexing easier.
% In your code, you were using index 'i' which was incorrect, as your array started at 2.
% Two options: (1) take my approach and pad the array
% (2) take your approach and using indices i-1 and j-1
for ii = 2:sqrt(n)
if A(ii) == true % YOU WERE MISSING THIS STEP!
for jj = ii^2:ii:n % YOU ONLY LOOPED UNTIL SQRT(n)!
A(jj) = false;
end
end
end
p = find(A);
disp(p)
This outputs the expected values.
Note that, at the end of the manual looping method, A is equivalent to isprime(1:n), mirroring my earlier suggestions.
There is two mistakes in your code:
The multiple should be check until n and not sqrt(n)
Since your nums vector start with 2 and not 1, if you want to
access the right value you need to use nums(j-1) = 0
So:
n=100
nums= 2:n;
p=2;
for i = p:sqrt(n)
for j = (i^2):i:n
nums(j-1) = 0;
end
end
for k = 1:n-1
if nums(k) ~= 0
disp(nums(k))
end
end
Noticed that you can skip one for loop using a modulo, it's probably not faster than the previous solution since this code create a logical index that include each prime that already been found.
n = 100
nums= 2:n;
for i = 2:sqrt(n)
nums(mod(nums,i)==0 & nums != i) = [];
end
nums.'
I simply delete the value in nums that can be divided by x but not x.
I have a simple problem, I'm trying to replace values in a 1x60000 array.
Here's my code, where Z is the 1x60000 array:
for i = 1:length(Z)
if Z(i) == 140
Z(i) = 1;
elseif Z(i) == 83
Z(i) = 2;
elseif Z(i) == 52
Z(i) = 3;
elseif Z(i) == 36
Z(i) = 4;
elseif Z(i) == 28
Z(i) = 5;
elseif Z(i) == 23
Z(i) = 6;
elseif Z(i) == 125
Z(i) = -1;
else
Z = Z(i);
end
end
The largest value in the array is 140. However, when I run the code I receive this error:
Index exceeds matrix dimensions.
Any help would be appreciated.
Your issue is the Z = Z(i) line, you're assigning a single value to an array then trying to index that single value next loop. If you want to leave Z(i) unchanged, simply don't use the else condition.
This whole code could be a lot shorter (and less loopy) using some logical indexing and ismember:
% Row 1 values to be replaced in Z by row 2 values
replacements = [140, 83, 52, 36, 28, 23, 125;
1, 2, 3, 4, 5, 6, -1];
% Get the indices where Z is one of the values to be changed
[~, idx] = ismember(Z, replacements(1,:));
% Use indexing to replace all the values at once
Z(idx~=0) = replacements(2, idx(idx~=0));
The line which certainly creates the error
Z = Z(i);
as you don't have any index on the left part.
I have written simple code in Matlab for binary Search. It's working properly if the searched item is in the array but goes into an infinite recursion loop if not.
I'm not sure where the problem lies.
function [] = BinarySearch(A,beg,last,item)
mid=floor((last+beg)/2);
if (beg)<=last
if item==A(mid)
fprintf('Item found at position %d \n',mid);
else
if(item<A(mid))
BinarySearch(A,beg,mid,item)
else
BinarySearch(A,mid,last,item)
end
end
else
fprintf('Item not found\n');
end
Imagine the really simple case that you only have 2 items in your list
A = [1 3]
and you call your BinarySearch on an item which would lie in the middle of the list. Look at the comments below, which follow how your function behaves...
BinarySearch(A, 1, 2, 2)
% mid = 1
% item ~= A(1): go to else
% item > A(1): go to else
% BinarySearch(A, 1, 2, 2)
% ... rinse and repeat
If your item was too small
BinarySearch(A, 1, 2, 0)
% mid = 1
% item ~= A(1): go to else
% item < A(1)
% BinarySearch(A, 1, 1, 0)
% mid = 1
% beg <= last (this is still true)
% item ~= A(1): go to else
% item < A(1)
% BinarySearch(A, 1, 1, 0)
% ... rinse and repeat
Similarly for an item which is larger than any in the list,
BinarySearch(A, 1, 2, 5)
% leads to BinarySearch(A, 1, 2, 5)
% ... repeat!!
You keep re-checking the same region, because your left (beg) and right (last) indices are allowed to both stay the same.
Let's re-implement the function, returning an actual value instead of just printing out the position to the console too. The comments directly relate to the numbered steps in the Wikipedia article for binary search, which looks similar in structure to what you've attempted:
function idx = BinarySearch(A, L, R, item)
%% BINARYSEARCH search for an item in the array A. Assumes that A is sorted ascending
% 1. Should be initially called using idx = BinarySearch(A, 1, n, item)
% where n is the number of elements in A, numel(A)
% 2. if L > R, the search terminates as unsuccessful
if L > R
idx = 0;
else
% 3. set m (position of middle element) to the floor of (L+R)/2
m = floor((L+R)/2);
% 4. if Am < item, set L to m+1 and go to 2.
if A(m) < item
L = m + 1; % YOU MISSED THIS STEP, CAUSING OVERLAPPING SEARCH REGIONS
idx = BinarySearch(A, L, R, item);
% 5. if Am > item, set R to m-1 and go to 2.
elseif A(m) > item
R = m - 1; % THE OTHER HALF OF THE STEP YOU MISSED
idx = BinarySearch(A, L, R, item);
% 6. Now Am = item, search is done, return index
else
idx = m;
end
end
end
Tests with A as before:
BinarySearch(A, 1, 2, 2); % returns 0: not found
BinarySearch(A, 1, 2, 0); % returns 0: not found
BinarySearch(A, 1, 2, 5); % returns 0: not found
BinarySearch(A, 1, 2, 3); % returns 2: 3 is at index 2
BinarySearch(A, 1, 2, 1); % returns 1: 1 is at index 1
Note that it may not be most efficient to implement this recursively. I'll leave it as an exercise, but this could be easily implemented using a while loop instead. The same logical structure would be used.
Try to set breakpoints to examine values where you think a problem might be.
You can use the while loop and the break command to avoid going into infinite loop.
For example try something like this:
function [index] = binarySearch(A, n, num)
left = 1;
right = n;
flag = 0;
while left <= right
mid = ceil((left + right) / 2);
if A(mid) == num
index = mid;
flag = 1;
break;
else if A(mid) > num
right = mid - 1;
else
left = mid + 1;
end
end
end
if flag == 0;
index = -1;
end
end
This question already has answers here:
Why is 24.0000 not equal to 24.0000 in MATLAB?
(6 answers)
Closed 5 years ago.
The following if statement is only working properly if my digitToFind variable is 5, otherwise it gets ignored.
if(digitToFind == R)
digitToFindFreq = digitToFindFreq + 1;
end
The program is meant to count the number of digits in a given integer, and find the frequency of one specific number chosen by the user.
Example: 123445; number of digits is 6, frequency of 4 is 2.
digitToFindFreq = 0;
numOfDigits = 0;
integerInput = input('Enter an integer: ');
while(integerInput ~= round(integerInput))
fprintf('Invalid input. Try again!\n');
integerInput = input('Enter an integer: ');
end
digitToFind = input('Enter a digit number to find (0 to 9): ');
while(digitToFind < 0 || digitToFind > 9 || digitToFind ~= round(digitToFind))
fprintf('Invalid input. Try again!\n');
digitToFind = input('Enter a digit number to find (0 to 9): ');
end
if(integerInput == 0 && digitToFind ~= 0)
numOfDigits = 1;
digitToFindFreq = 0;
elseif(integerInput == 0 && digitToFind == 0)
numOfDigits = 1;
digitToFindFreq = 1;
end
while(integerInput >= 1)
integerInput = integerInput/10;
X = integerInput - fix(integerInput);
R = 10*X;
if(digitToFind == R)
digitToFindFreq = digitToFindFreq + 1;
end
integerInput = integerInput - X;
numOfDigits = numOfDigits + 1;
end
fprintf('\nNumber of digits: %d, Digit to find frequency: %d\n',numOfDigits,digitToFindFreq);
I have never had an issue like this before. It must be something small that I am missing because otherwise the program works properly.
This is probably an issue with floating point numbers, when you are dividing and multiplying by 10, and subtracting remainders, your value is not remaining an integer. Therefore a direct == test may fail.
1.0000000000000001 == 1 % False
You should replace your test with this:
if(abs(digitToFind - R) < 0.1) % the digit must be close to digitToFind
digitToFindFreq = digitToFindFreq + 1;
end
Or use strings:
integerInput = num2str(integerInput);
digitToFind = num2str(digitToFind);
while length(integerInput) > 0
R = integerInput(end);
integerInput = integerInput(1:end-1);
if strcmp(digitToFind, R)
digitToFindFreq = digitToFindFreq + 1;
end
integerInput = integerInput - X;
numOfDigits = numOfDigits + 1;
end
Really though, use neither of those methods. You are not leveraging Matlab's in built indexing to do all of the hard work for you. I've re-written your whole code, so you can also see how to do your validation checks with strings instead of integers. Note with the below method, I've changed the input type so that num2str isn't needed and your integers can be much¹ bigger.
% include your error checking here, but based on strings
integerInput = '.';
digitToFind = '.';
% Check all characters in integerInput are digits 0-9, so integer
while ~(all(integerInput >= '0') && all(integerInput <= '9'))
% Include the 's' qualifier to convert input directly to string, allows
% for much longer integers than using num2str() later on
integerInput = input('Enter an integer: ', 's');
end
% Check digitToFind is 0-9, and only 1 character
while ~(all(digitToFind >= '0') && all(digitToFind <= '9')) || length(digitToFind) ~= 1
digitToFind = input('Enter a digit number to find (0 to 9): ', 's');
end
% use logical indexing to find vector of 1s and 0s in the positions where
% integerInput(i) = digitToFind. Then count the number of non-zero elements
% using nnz (in built function). No need for your edge-case 0 checks either.
numOfDigits = length(integerInput);
digitToFindFreq = nnz(integerInput == digitToFind);
¹ A note on maximum input sizes
intmax('uint64') = 18446744073709551615 is the largest (unsigned) integer Matlab can handle. Note that realmax = 1.7977e+308 is significantly larger! For your application, I would assume it's desirable to allow inputs greater than ~20 digits, so representing them as integers is going to have issues. This may be another reason you are having issues with your tests, as anything over the intmax limit will have to be stored as a floating point (real) number, not an integer!
By using the 's' flag with the input command, the input number isn't evaluated as an integer, and instead it's converted directly to a string. The string variable's maximum length is only dependent on your computer's memory so could be enormous! For instance, I can create a 900,000,000 element string (1.8 Gigabytes) before I run out of memory.
Edit:
In the above, to check for digits being in the range 0-9, I'm using the fact that their ASCII values are consecutive, as that's what's being compared. You could also use the in-built isstrprop
while ~(all(isstrprop(integerInput, 'digit')))
...
q = 2;
k= 2^q;
x1 = [0.0975000000000000, 0.980987500000000, -0.924672950312500, -0.710040130079246];
for i = 1 : length(x1)
[idx_centers,location] = kmeans(x1',q);
end
temp = idx_centers;
for i = 1 : length(x1)
if temp(i)== 2
idx_centers(i) = 0;
end
BinaryCode_KMeans(i) = idx_centers(i); % output is say [0,0,1,1];
end
strng = num2str(BinaryCode_KMeans);
DecX = bin2dec(strng);
In the above code snippet, I want to express the binary string to its decimal equivalent where the binary string is obtained from kmeans clustering. The decimal equivalent should either be 1,2,3, or 4 i.e., k = 2^q when q=2.
But sometimes after conversion, the decimal equivalent is 12 because for a 4 bit binary code we get decimal numbers in 1 to 16 or 0 -- 15. the number of elements in x1 can vary and can be less than or greater than k. What should I do so that I can always get the decimal equivalent of the binary code within k for any value of q?
First of, there is no need to run kmeans multiple times, it will calculate the cluster centers using a single run. Note that, the code below tries to find a mapping between the clustering results and n the number of samples. There are three ways in the code below to encode this information.
clear
clc
q = 2;
k= 2^q;
n = 4;
x1 = rand(n,1);
fprintf('x1 = [ '); fprintf('%d ', x1); fprintf(']\n');
[idx_centers, location] = kmeans(x1, q);
fprintf('idx_centers = [ '); fprintf('%d ', idx_centers); fprintf(']\n');
for i = 1:q
idx_centers(idx_centers == i) = i-1;
end
fprintf('idx_centers = [ '); fprintf('%d ', idx_centers); fprintf(']\n');
string = num2str(idx_centers');
% Original decimal value
DecX = bin2dec(string);
fprintf('0 to (2^n) - 1: %d\n', DecX);
% Reduced space decimal value
% Ignoring the 0/1 order as [ 1 1 0 0 ]
% would be the same as [ 0 0 1 1 ]
if DecX >= (2^n)/2
complement = bitget(bitcmp(int64(DecX)),n:-1:1);
DecX = bin2dec(num2str(complement));
end
fprintf('0 to ((2^n)/2) - 1: %d\n', DecX);
% Minimal Decimal value based on the number of samples
% in the 0's cluster which is in the range of 0 to n-1
fprintf('0 to n - 1: %d\n', numel(find(idx_centers == 0)));
Hint: If you change the q to more than 2, the code will not work because bin2dec only accepts zeros and ones. In case of having more than 2 clusters, you need to elaborate the code and use multidimensional arrays to store the pairwise clustering results.