Is there a built-in function in Matlab that condenses sequence of half integers to expressions with colon operators?
For example, [1:4,5:.5:7]
gives
1, 2, 3, 4, 5, 5.5, 6, 6.5, 7
Given a double array such as [1, 2, 3, 4, 5, 5.5, 6, 6.5, 7]
, is there a convenient way to convert it back to [1:4,5:.5:7]
— or equally valid, [1:5,5.5:.5:7]
— as a string?
Here is a solution that
[1 3 5 9]
will be output as '[1:2:5 9]'
. Similarly, [1 3 5 9 11]
would give '[1:2:5 9 11]'
.1
. For example, [9 3 4 5]
would give [9 3:5]
.[8 6 4 2]
would give '8:-2:2'
, and 5
would give '5'
.[]
would give '[]'
.x = [2 4.5 7 9.5 9 8 7 6 5 15 7.5 7 6.5 6 9 11]; % example input
sep = ' '; % define separator; it could also be comma
str = ''; % initiallize output
k = 1; % first number not processed yet
while k<=numel(x)
m = find(diff([diff(x(k:end)) inf]), 1) + 1; % may be empty
if m>2 % if non-empty and at least 2: range found (at least 3 numbers)
ini = x(k);
ste = x(k+1)-x(k);
fin = x(k+m-1);
if ste~=1
str = [str num2str(ini) ':' num2str(ste) ':' num2str(fin)];
else
str = [str num2str(ini) ':' num2str(fin)];
end
k = k+m; % m numbers have been processed
else % no range: include just one number
str = [str num2str(x(k))];
k = k+1; % 1 number has been processed
end
str = [str sep]; % add separator
end
str = strip(str,sep); % this removes trailing space/comma, if it exists. For pre-2016b, use `strtrim`
if any(str==sep) || isempty(str)
str = ['[' str ']']; % brackets are required
end
Examples / tests:
[2 4.5 7 9.5 9 8 7 6 5 15 7.5 7 6.5 6 9 11]
gives '[2:2.5:9.5 9:-1:5 15 7.5:-0.5:6 9 11]'
[1.5 16 -0.5 -7 -9 -11]
gives '[1.5 16 -0.5 -7:-2:-11]'
[4 2 0 -2 5 12 19]
gives '[4:-2:-2 5:7:19]'
[-2 0 2.5 5.5]
gives '[-2 0 2.5 5.5]'
[2 3 4 10 7 8]
gives '[2:4 10 7 8]'
[6 4.5 3]
gives '6:-1.5:3'
[3 4 5 6]
gives '3:6'
42
gives '42'
[]
gives '[]'
The code consists of a loop that searches for the maximum-length range starting at the current position, and then mover forward. The trickiest part is the line
m = find(diff([diff(x(k:end)) inf]), 1) + 1; % may be empty
This tries to find the maximum length m
of numbers that form a range, starting from the current position k
. diff(x(k:end))
computes consecutive differences, and the outer diff
detects changes in those difference. The first such change, computed with find(..., 1)
, indicates the first number that doesn't belong to the range. There are five cases, the second of which explains why the inf
is needed:
x(k:end)
is [3 5 7 15]
we have diff(x(k:end))
equal to [2 2 8]
, diff([diff(x(k:end)) inf])
equal to [0 6 inf]
, find(..., 1)
gives 2
, and m
is 3
.x(k:end)
is [3 5 7]
we have diff(x(k:end))
equal to [2 2]
, diff([diff(x(k:end)) inf])
equal to [0 inf]
, find(..., 1)
gives 2
, and m
is 3
. This is why inf
is needed; without it the result would be m=[]
, which would be incorrectly interpreted as "no range" by the if
branch.x(k:end)
is [3 6 7]
we have diff(x(k:end))
equal to [3 1]
, diff([diff(x(k:end)) inf])
equal to [-2 inf]
, find(..., 1)
gives 1
, and m
is 2
. This means that there is a range of two numbers; but it is not a proper range, so the if
branch will disregard it, and execution will proceed with the else
part.x(k:end)
is [3 6]
we have diff(x(k:end))
equal to 3
, diff([diff(x(k:end)) inf])
equal to [inf]
, find(..., 1)
gives 1
, and m
is 2
. Again, there is a range of two numbers, but it is not a proper range. Note that without the included inf
we would have m=[]
instead of the "correct" 2
, but that would also be valid to trigger the else
part (in which the actual value of m
is not used).if x(k:end)
is just 3
we have diff(x(k:end))
equal to []
, diff([diff(x(k:end)) inf])
equal to []
, find(..., 1)
gives []
, and m
is []
. Although m
"should" be 1
, []
is just as valid to trigger the else
part.Note that, since the input contains integers or half integers, there are no floating-point accuracy issues, as those numbers are represented exactly up to ±2^52
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With