kjo
kjo

Reputation: 35331

How to assemble small n-d arrays into a larger n-d array?

The code below produces a cell array containing n (=210) 2x3x4-shaped n-d arrays.

n = prod(5:7);
makendarray = @(i) reshape(1:24, 2:4) + (i * 1000);
ndarrays = cellfun(makendarray, num2cell(1:n), 'un', 0);

For each i ∈ {1, … ,n}, the contents of the i-th n-d array are all integers whose initial digits match those of i. For example, for i = 123:

>> ndarrays{123}

ans(:,:,1) =

      123001      123003      123005
      123002      123004      123006


ans(:,:,2) =

      123007      123009      123011
      123008      123010      123012


ans(:,:,3) =

      123013      123015      123017
      123014      123016      123018


ans(:,:,4) =

      123019      123021      123023
      123020      123022      123024

I want to assemble all these n-d arrays into a larger [5x6x7x2x3x4 double] n-d array X in such a way that expressions of the form X(i,j,k,:,:,:) will correspond to one of the original smaller n-d arrays. This last requirement is the one that's giving me difficulty.

I have no problem producing the larger [5x6x7x2x3x4 double] n-d array from the smaller ones, but expressions of the form X(i,j,k,:,:,:) do not produce one of the original smaller n-d arrays.

Below is an example of one of the things I've tried; the last output should match below should match the output shown for ndarrays{123} above, but doesn't:

>> X = reshape(cell2mat(ndarrays), [5:7 2:4]);
>> [idx{1:3}] = ind2sub(5:7, 123);
>> squeeze(X(idx{:}, :, :, :))

ans(:,:,1) =

       62001       62003       62005
      167001      167003      167005


ans(:,:,2) =

       62007       62009       62011
      167007      167009      167011


ans(:,:,3) =

       62013       62015       62017
      167013      167015      167017


ans(:,:,4) =

       62019       62021       62023
      167019      167021      167023

EDIT: OK, by (pretty much blind) trial-and-error I found that the following does the trick:

tmp = cellfun(@(c) reshape(c, [1 24]), ndarrays, 'un', 0);
X = reshape(cat(1, tmp{:}), [5:7 2:4]);

IOW: linearize the subarrays before passing them to cat(1, ...). I'm surprised that it's necessary to explicitly perform this linearization step: it's what I expect MATLAB to do by default (in cell2mat, for example).

Be that as it may, is there a better (or at least clearer/easier to understand) way to achieve the same effect?

(BTW, for this problem, the initial shape of ndarray is given and not subject to change. IOW, solutions that require modifying the makesubarray function in the example do not fit the situation I'm dealing with.)

EDIT2: In reference to Luis Mendo's answer, here's the output (copy-pasted verbatim from my MATLAB workspace) of a small script (with echo on), showing the sizes of various items:

n = prod(5:7);
makendarray = @(i) reshape(1:24, 2:4) + (i * 1000);
ndarrays = cellfun(makendarray, num2cell(1:n), 'un', 0);
size(ndarrays)

ans =

     1   210

size(permute(ndarrays, [2 3 4 1]))

ans =

   210     1

size(cell2mat(permute(ndarrays, [2 3 4 1])))

ans =

   420     3     4

echo off;

Upvotes: 1

Views: 79

Answers (1)

Luis Mendo
Luis Mendo

Reputation: 112749

You can achieve it with a little bit of permute and reshape. The logic of this approach can be followed by observing size(X) at the end of each step (indicated in the comments):

X = cell2mat(permute(ndarrays(:), [2 3 4 1])); %// size [2 3 4 210]
X = permute(X, [4 5 6 1 2 3]); %// size [210  1  1  2  3  4]
X = reshape(X, [7 6 5 2 3 4]); %// size [7  6  5  2  3  4]
X = permute(X, [3 2 1 4 5 6]); %// size [5  6  7  2  3  4]

Upvotes: 1

Related Questions