Tips & Tricks
Follow


Stephen23

TUTORIAL: Comma-Separated Lists and How to Use Them

Stephen23 on 23 Feb 2022 (Edited on 12 Apr 2024)
Latest activity Reply by Chen Lin on 12 Apr 2024

Introduction
Comma-separated lists are really very simple. You use them all the time. Here is one:
a,b,c,d
That is a comma-separated list containing four variables, the variables a, b, c, and d. Every time you write a list separated by commas then you are writing a comma-separated list. Most commonly you would write a comma-separated list as inputs when calling a function:
fun(a,b,c,d)
or as arguments to the concatenation operator or cell construction operator:
[a,b,c,d]
{a,b,c,d}
or as function outputs:
[a,b,c,d] = fun();
It is very important to understand that in general a comma-separated list is NOT one variable (but it could be). However, sometimes it is useful to create a comma-separated list from one variable (or define one variable from a comma-separated list), and MATLAB has several ways of doing this from various container array types:
1) from a field of a structure array using dot-indexing:
struct_array.field % all elements
struct_array(idx).field % selected elements
2) from a cell array using curly-braces:
cell_array{:} % all elements
cell_array{idx} % selected elements
3) from a string array using curly-braces:
string_array{:} % all elements
string_array{idx} % selected elements
Note that in all cases, the comma-separated list consists of the content of the container array, not subsets (or "slices") of the container array itself (use parentheses to "slice" any array). In other words, they will be equivalent to writing this comma-separated list of the container array content:
content1, content2, content3, .. , contentN
and will return as many content arrays as the original container array has elements (or that you select using indexing, in the requested order). A comma-separated list of one element is just one array, but in general there can be any number of separate arrays in the comma-separated list (zero, one, two, three, four, or more). Here is an example showing that a comma-separated list generated from the content of a cell array is the same as a comma-separated list written explicitly:
>> C = {1,0,Inf};
>> C{:}
ans =
1
ans =
0
ans =
Inf
>> 1,0,Inf
ans =
1
ans =
0
ans =
Inf
How to Use Comma-Separated Lists
Function Inputs: Remember that every time you call a function with multiple input arguments you are using a comma-separated list:
fun(a,b,c,d)
and this is exactly why they are useful: because you can specify the arguments for a function or operator without knowing anything about the arguments (even how many there are). Using the example cell array from above:
>> vertcat(C{:})
ans =
1
0
Inf
which, as we should know by now, is exactly equivalent to writing the same comma-separated list directly into the function call:
>> vertcat(1,0,Inf)
ans =
1
0
Inf
How can we use this? Commonly these are used to generate vectors of values from a structure or cell array, e.g. to concatenate the filenames which are in the output structure of dir:
S = dir(..);
F = {S.name}
which is simply equivalent to
F = {S(1).name, S(2).name, S(3).name, .. , S(end).name}
Or, consider a function with multiple optional input arguments:
opt = {'HeaderLines',2, 'Delimiter',',', 'CollectOutputs',true);
fid = fopen(..);
C = textscan(fid,'%f%f',opt{:});
fclose(fid);
Note how we can pass the optional arguments as a comma-separated list. Remember how a comma-separated list is equivalent to writing var1,var2,var3,..., then the above example is really just this:
C = textscan(fid,'%f%f', 'HeaderLines',2, 'Delimiter',',', 'CollectOutputs',true)
with the added advantage that we can specify all of the optional arguments elsewhere and handle them as one cell array (e.g. as a function input, or at the top of the file). Or we could select which options we want simply by using indexing on that cell array. Note that varargin and varargout can also be useful here.
Function Outputs: In much the same way that the input arguments can be specified, so can an arbitrary number of output arguments. This is commonly used for functions which return a variable number of output arguments, specifically ind2sub and gradient and ndgrid. For example we can easily get all outputs of ndgrid, for any number of inputs (in this example three inputs and three outputs, determined by the number of elements in the cell array):
C = {1:3,4:7,8:9};
[C{:}] = ndgrid(C{:});
which is thus equivalent to:
[C{1},C{2},C{3}] = ndgrid(C{1},C{2},C{3});
Further Topics:
MATLAB documentation:
Click on these links to jump to relevant comments below:
Dynamic Indexing (indexing into arrays with arbitrary numbers of dimensions)
Nested Structures (why you get an error trying to index into a comma-separated list)
Summary
Just remember that in general a comma-separated list is not one variable (although they can be), and that they are exactly what they say: a list (of arrays) separated with commas. You use them all the time without even realizing it, every time you write this:
fun(a,b,c,d)
Dyuman Joshi
Dyuman Joshi on 26 Apr 2023
How about a Tutorial section in the MATLAB FAQs page?
John D'Errico
John D'Errico on 26 Apr 2023
Good explanation. I wonder if Answers should have a counterpart, composed of purely tutorials like this?
Chen Lin
Chen Lin on 12 Apr 2024
Tips & Tricks channel is designed to be the repository for tutorials or technical articles. We can add links in Answers to point users to those content. I think @Dyuman Joshi's FAQ page is good idea. We can add a section called 'community tutorials' or 'tips and tricks'. Adding @Rena Berman
Stephen23
Stephen23 on 18 May 2022
A common misunderstanding of comma-separated lists involves nested structures. The expectation is that dot-indexing at arbitrary levels of the nesting should somehow be converted into just one comma-separated list, regardless of the sizes of the nested structures. That somehow MATLAB should "flatten" nested structures.
However this is not the case: every dot index is essentially independent from every other dot index, resulting in independent comma-separated lists. This is for the simple reason that every nested structure is actually a completely independent structure, one that just happens to be stored in another structure. (As an aside: the common attempt to use one piece of text to perform dynamic dot-indexing into multiple levels of nested structures simultaneously is another symptom of this misunderstanding).
In practice this means that nested structures can only be chained into "one" dot-indexing operation when all parent structures are scalar (there can be zero or more scalar parent structures). For example, here A is scalar:
A.students(1).grades = [7,8,9];
A.students(2).grades = [6,7,8];
A % parent is scalar
A = struct with fields:
students: [1×2 struct]
A.students % non-scalar
ans = 1×2 struct array with fields:
grades
[A.students.grades] % :)
ans = 1×6
7 8 9 6 7 8
whereas here B is non-scalar (so its dot-index returns mutlple separate structures, which cannot be further indexed):
B(1).students.grades = [1,2,3];
B(2).students.grades = [3,2,1];
B % parent is non-scalar
B = 1×2 struct array with fields:
students
B.students % this returns multiple separate structures!
ans = struct with fields:
grades: [1 2 3]
ans = struct with fields:
grades: [3 2 1]
[B.students.grades] % error!
Intermediate dot '.' indexing produced a comma-separated list with 2 values, but it must produce a single value when followed by subsequent indexing operations.
Stephen23
Stephen23 on 22 Feb 2024
"Can you explain (Like I'm Michael Scott) why it cannot be further indexed?"
The reason why is explained here:
If you prefer, it is for much the same reason that A,B,C,D.X does not dot-index into A, B, C, and D.
"Because one can see that they are same length (3) so output should intuitively be [1,2,3,3,2,1]."
Their lengths are not relevant to why this does not work.
The reason is does not work is because B(1).students is a completely different array than B(2).students. In MATLAB indexing into one array does not simultaneously index into another array.
Note that the cells of a cell array contain different arrays... and so do the fields of a structure array.
"Also, how one could get that output?"
Assuming that the STUDENTS fields are compatible structures and the GRADES fields are compatible arrays:
B(1).students.grades = [1,2,3,4];
B(2).students.grades = [3,2,1];
Method one using comma-separated lists:
tmp = [B.students]; % concatenate the separate arrays in STUDENTS into one array
out = [tmp.grades] % concatenate the separate arrays in GRADES into one array
out = 1×7
1 2 3 4 3 2 1
Method two using CELLFUN:
F = @(s)s.students.grades;
out = cell2mat(arrayfun(F,B,'uni',0))
out = 1×7
1 2 3 4 3 2 1
Method three using GETFIELD and ARRAYFUN:
F = @(n)getfield(B,{n},'students','grades');
out = cell2mat(arrayfun(F,1:numel(B),'uni',0))
out = 1×7
1 2 3 4 3 2 1
Method four using a FOR-loop is very simple and left as an exercise for the reader.
Sven Larsen
Sven Larsen on 22 Feb 2024
Thanks You @Stephen23, this post is extremely helpful! Can you explain (Like I'm Michael Scott) why it cannot be further indexed? Because one can see that they are same length (3) so output should intuitively be [1,2,3,3,2,1].
Also, how one could get that output? Apparantely its something like (i know this is incorrect, please correct me)
F = @(s) s.students(1).grades;
Z = arrayfun(F,B, 'Uni',0)
1×2 cell array
{[1 2 3]} {[3 2 1]}
Jan
Jan on 23 Feb 2022
An interesting application of comma-separated lists are dynamic indexing. You can use it to extract a specific dimension of a multi-dimensional array programatically:
X = rand(2, 3, 4, 5);
dim = 3;
tile = cell(1, ndims(X));
tile(:) = {':'};
tile{dim} = 1;
slice = X(tile{:}) % As X(:, :, 1, :)
slice =
slice(:,:,1,1) = 0.8627 0.2860 0.5269 0.6203 0.6879 0.5869 slice(:,:,1,2) = 0.6423 0.6845 0.6543 0.4469 0.5666 0.7449 slice(:,:,1,3) = 0.5700 0.2394 0.6216 0.6811 0.6777 0.9242 slice(:,:,1,4) = 0.0679 0.8715 0.2126 0.8711 0.1369 0.1320 slice(:,:,1,5) = 0.9052 0.0741 0.5456 0.3225 0.0649 0.8250
Stephen23
Stephen23 on 9 Feb 2024
@Jan & @Dyuman Joshi: I made Dyuman Joshi's suggested modification and reran the code.
Dyuman Joshi
Dyuman Joshi on 26 Apr 2023
@Jan, based on your comment on the last line of the code, did you mean to use
tile{dim} = 1;