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