4

Simple question here. I have arrays:

a = [ 600 746 8556 90456 ]
b = [ 684 864 8600 90500 ]

and I want to get:

output = [ a(1):b(1) a(2):b(2) a(3):b(3) a(4):b(4) ]

(or either [ a(1):b(1); a(2):b(2); a(3):b(3); a(4):b(4) ], I don't care)

I cannot realize how to do this without using a loop, but I know it should be a way.

Any idea?

thanks in advance

Divakar
  • 218,885
  • 19
  • 262
  • 358
myradio
  • 1,703
  • 1
  • 15
  • 25
  • I have seen this one. Let's go on duplicate hunt! Here are plenty of methods by *Loren*: http://blogs.mathworks.com/loren/2008/10/13/vectorizing-the-notion-of-colon/ – knedlsepp Mar 10 '15 at 20:42
  • @knedlsepp Indeed, I believe I have seen similar questions before, but they are not very googleable. – Bas Swinckels Mar 10 '15 at 20:46
  • I will look for the one with the broadest answers and rename it to: "Vectorizing the notion of colon (:)". – knedlsepp Mar 10 '15 at 20:49
  • @knedlsepp Actually having appropriate titles help a lot in googling, so that's a good idea! – Divakar Mar 10 '15 at 20:51
  • @Divakar: I propose [this one](http://stackoverflow.com/questions/14341798/matlabs-colon-operator-values-between-two-vectors). It has the most views and already four linked ones. Feel free to add your `bsxfun`-solution there. – knedlsepp Mar 10 '15 at 21:10
  • Accepted answer on that question is virtually identical to my answer, except for using `colon`. – Bas Swinckels Mar 10 '15 at 21:15
  • 1
    @BasSwinckels: Isn't that a great way of approval to have found a neat solution. ;-) In terms of speed it might be outperformed by loops though. – knedlsepp Mar 10 '15 at 21:18
  • @knedlsepp I think I will just leave it here, at least for now. Good find that one!! – Divakar Mar 10 '15 at 21:35
  • Sorry for duplicating. I knew there were many chances it already existed but yes, it wasn't very googleable. cheers – myradio Mar 11 '15 at 01:09
  • @myradio It prompted new approaches though! :) – Divakar Mar 11 '15 at 04:56

2 Answers2

8

Approach #1

Vectorized approach with bsxfun's masking capability -

%// Get "extents" formed with each pair of "a" and "b" and max extent
ext = b-a
max_ext = max(ext)

%// Extend all a's to max possible extent
allvals = bsxfun(@plus,a,[0:max_ext]')  %//'

%// Get mask of valid extensions and use it to have the final output
mask  = bsxfun(@le,[0:max_ext]',ext)  %//'
out  = allvals(mask).'

Approach #2

Listing here is a cumsum based approach that has to be more memory efficient and faster than both the earlier listed bsxfun based approach and arrayfun based approach in the other answer. Here's the code -

%// Get "extents" formed with each pair of "a" and "b"
ext = b-a;

%// Ignore cases when "a" might be greater than "b"
a = a(ext>=0);
b = b(ext>=0);
ext = b-a;

if numel(ext)>1

    %// Strategically place "differentiated" values from array,a
    idx = ones(sum(ext+1),1);
    idx([1 cumsum(ext(1:end-1)+1)+1]) = [a(1) diff(a)-ext(1:end-1)];

    %// Perform cumulative summation to have the final output
    out = cumsum(idx)

else %// Leftover cases when there are one or no valid boundaries:(a->b)
    out = a:b
end

Sample run -

>> a
a =
     6    12    43
>> b
b =
     8    17    43
>> out
out =
     6     7     8    12    13    14    15    16    17    43
Community
  • 1
  • 1
Divakar
  • 218,885
  • 19
  • 262
  • 358
  • Well done avoiding very large numbers by using `b-a` – Luis Mendo Mar 10 '15 at 20:46
  • @LuisMendo I think that's the only way here. But, still in `allvals` I am `extending`, a lot of whom are descarded in the next step with the masking. – Divakar Mar 10 '15 at 20:53
  • 2
    Want to give another +1 for the second approach. In case a might be larger than b `a=a(ext>=0);b=b(ext>=0);` should be inserted as a second line. – Daniel Mar 10 '15 at 21:44
  • Great take on the `cumsum` version. To be equivalent to the `bsxfun` solution we need to strip away zero or negative runs though, as in: `a=[1,10]`, `b=[0,11]`. - Edit: Ok, Daniel already got the solution. – knedlsepp Mar 10 '15 at 21:46
  • @Daniel Thanks, edited with it, good suggestion on that!! – Divakar Mar 10 '15 at 22:03
  • As I'm currently comparing all the approaches, I have come across yet another corner case: If after `ext = b(ext>=0)-a;` it is empty, the first `cumsum` line fails. (Loren also has to check this explicitly in her `cumsum` approach) – knedlsepp Mar 10 '15 at 22:18
  • @knedlsepp That is when all `a` elements are greater than `b`, right? – Divakar Mar 10 '15 at 22:22
  • @Divakar: Yes, but I just saw it even fails for all but one: `a = [9 10 16 19]; b = [7 9 15 23];` – knedlsepp Mar 10 '15 at 22:24
  • @knedlsepp Yup! good catch that one! Edited to work with those corner cases. – Divakar Mar 10 '15 at 22:35
  • Great! If you are interested in terms of speed: Loren's answer is the tiniest bit faster than yours, then comes a factor of five to the `for` loop and yet another factor of `five` for the current `arrayfun` solution, (which becomes nearly as fast as the `for` loop if `@(start, stop) start:stop` is swapped for `@colon`). – knedlsepp Mar 10 '15 at 22:50
  • @knedlsepp Is the fastest one `Run-Length Decoding` based? That looks very close to the Approach #2 of this one, or I might have a complicated it a tiny bit here, but looks close. – Divakar Mar 10 '15 at 22:55
  • Yup. It's the `cumsum` one I mentioned earlier. I have given it some thought to directly use a `runLengthDecode` function, but I guess it is more similar to a RLE code than actually using one. – knedlsepp Mar 10 '15 at 22:59
7

One-liner using arrayfun, cell2mat and an anonymous function:

output = cell2mat(arrayfun(@(start, stop) start:stop, a, b, 'uni', 0))

Explanation: the function arrayfun iterates in parallel over the vectors a and b, and then calls the anonymous function for every pair of their elements. The anonymous function returns a vector of varying size instead of a scalar, so you need to use 'UniformOutput', false (which can be abbreviated to 'uni', 0) to make arrayfun return a cell array. Finally, you use cell2mat to squeeze the cell array together into a vector.

Quick test:

>> a = [10, 20, 40];
>> b = [13, 22, 45];
>> output = cell2mat(arrayfun(@(start, stop) start:stop, a, b, 'uni', 0))
output =
    10    11    12    13    20    21    22    40    41    42    43    44    45
Bas Swinckels
  • 18,095
  • 3
  • 45
  • 62
  • 1
    In case you're interested: As I'm currently testing all the solutions: I get a 5x speedup if I use `@colon` instead of `@(start, stop) start:stop`. Which makes this pretty much as fast as the `for` loop. The `cumsum` approach is about 5 times faster for large arrays. (Which isn't really that much, considering `arrayfun`'s bad reputation.) – knedlsepp Mar 10 '15 at 22:55
  • 1
    @knedlsepp I didn't know `@colon` even existed! – Luis Mendo Mar 10 '15 at 22:59
  • @LuisMendo: As this is basically the only use-case, I think you will still be able to live without it. ;-) – knedlsepp Mar 10 '15 at 23:05
  • @knedlsepp Nope. Now that I know of it existence, I'll try to find a way to include it in a near-future answer :-) – Luis Mendo Mar 10 '15 at 23:07