Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to condense sequence of half integers to string expressions with colon operators? (How to convert list to string)

Tags:

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?

like image 804
Argyll Avatar asked Oct 03 '19 20:10

Argyll


1 Answers

Here is a solution that

  • Adequately handles numbers that don't form a range. For example, [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]'.
  • Omits specifying step 1. For example, [9 3 4 5] would give [9 3:5].
  • Omits unnecessary brackets. For example, [8 6 4 2] would give '8:-2:2', and 5 would give '5'.
  • Allows empty input. So [] 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:

  • Proper range of 3 or more numbers, followed by at least a number not in that range. For example, if 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.
  • Proper range of 3 or more numbers, which ends the array. For example, if 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.
  • No proper range; there are more than 2 numbers remaining. For example, if 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.
  • No proper range; there are only 2 numbers remaining. For example, if 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).
  • No proper range; there is only 1 number remaining, that is, the current number ends the array. For example, 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.

like image 133
Luis Mendo Avatar answered Oct 14 '22 07:10

Luis Mendo