在这里插入图片描述

什么是逗号分割列表

这玩意一般都不知道是什么,Comma-separated list,CSL, 虽然,用Matlab的时候天天会用到。这到底是个什么玩意?或者,更进一步,这到底是不是个玩意?

每次调用一个参数大于一个的函数时,我们就是用了一个CSL,a.k.a.,逗号分割列表。

>> zeros(2, 2)

ans =

     0     0
     0     0

每次我们显式定义一个数组/矩阵/元胞数组时,我们就写了一个或者多个逗号分割列表。

>> [1, 2, 3, 3; 4, 5, 6, 7]

ans =

     1     2     3     3
     4     5     6     7

>> {1,2,3}

ans =

  1×3 cell 数组

    {[1]}    {[2]}    {[3]}

我们还可以在命令行下面直接这样写:

>> 1,2, '53', "23"

ans =

     1


ans =

     2


ans =

    '53'


ans = 

    "23"

这样观察下来,从字面上,我们可以知道,逗号分割列表的定义就是,字面上的,用逗号分开的多个值,这些值不需要是一样的类型。

那么,这个玩意有什么用呢?在考察有什么用之前,我们先考察一下,从哪儿能变出逗号分割列表。

逗号分割列表与其他数据结构的关系

我们重点考虑结构数组、元胞数组和字符数组与逗号分割列表的关系。

结构数组

我们最常用到的结构数组就是对目录下的文件进行处理的时候,通常我们会用dir函数配合通配符*来得到目录下文件/子目录的列表。

>> s = dir(".")

s = 

  包含以下字段的 18×1 struct 数组:

    name
    folder
    date
    bytes
    isdir
    datenum

这个时候,我们就会得到一个结构数组,这里,是一个18x1的数组,数组的每个元素就是一个包含六个field字段的结构体。

这个时候,我们采用索引的语法来访问,就会得到逗号分割列表。

s.name

就得到所有名字构成的逗号分割列表。就相当于运行:

s(1).name, s(2).name, s(3).name, ..., s(end).name

同样,还能够用所有的索引访问语法来获得部分元素的相应字段的逗号分割列表。就比如说,我们要获得所有的非目录文件名:

idx = [s.isdir];
s(~idx).name

这也可以直接写成

s(~[s.isdir]).name

值得注意的是,这里,用了两次结构数组字段访问的语法,得到相应的逗号分割数组。

这里还有一个特殊的语法需要注意,[s.isdir],这里有一个原则:

逗号分割列表等效约定:每次有一个值是CSL,逗号分割列表时,就等效与把对应的展开形式直接写在这里。

那么,这里就相当于是:

[s(1).isdir, s(2).isdir, ..., s(end).isdir]

是一个最为平凡的数组定义语句。

这里有一个思考题,我们知道前面得到的s的字段name是一个字符数组,也就是'dir.m'这样的特殊数组,

  1. [s.name]会是什么?
  2. {s.name}会是什么?

元胞数组

思考题的第一问很简单,我们的字符数组,是一个行向量,其实,['dir.m']等效是['d', 'i', 'r', '.', 'm'],那么[s.name]就会形成一个所有的名字拼在一起的字符数组。

思考题的第二道,应用上面的等效约定,这就是元胞数组的构造。

那么{s.name}就等效于{s(1).name, s(2).name, ..., s(end).name},构成一个一行的元胞数组。

相应的,对元胞数组进行{}索引,也得到符号分割列表。

>> cs = {1, 2; 3, 4}

cs =

  2×2 cell 数组

    {[1]}    {[2]}
    {[3]}    {[4]}

>> cs{:}

ans =

     1


ans =

     3


ans =

     2


ans =

     4

同样, {}也支持多种索引访问语法形式cs{idx},同样,也会得到相应个数的逗号分割列表。

逗号分割列表等效原则对这里依然适用,所以,下面两个表达式字面上等效,可以相互替换。

  1. A{:}
  2. A{1}, A{2}, A{3}, ..., A{end}

字符数组和字符串数组

自从R2016b 中的 MATLAB 中引入string之后,文本的表达方式就有两种:

  1. 字符数组
  2. 字符串

字符数组是一个字符的行向量,表达一行文本,所以利用[]来定义更长数组的语法天然就是合法的,因此,字符串连接也可以用逗号分割列表天然的完成(逗号可以省略)。

>> 'abc'

ans =

    'abc'

>> ['abc', 'cde']

ans =

    'abccde'

所以,如果要表达一个字符串数组的数组时,如果构造一个二维字符串数组,则自动提出了所有行都相等的限制……那么要表达多个长度不一样的字符串的数组怎么办呢?

在R2016b以前,只能采用字符数组的元胞数组来完成,也就是:


>> {'abc', 'efg'}

ans =

  1×2 cell 数组

    {'abc'}    {'efg'}

>> {'abc', 'efg'; 'a', 'b'}

ans =

  2×2 cell 数组

    {'abc'}    {'efg'}
    {'a'  }    {'b'  }

还是挺不自然的。

因此就有了字符串,则跟其他程序设计语言中的字符串一样,用双引号,是一个正经的对象。

从这以后,能够有一种叫字符串数组的东西:

>> strs = ["abc", "d", "efghi"]

strs = 

  1×3 string 数组

    "abc"    "d"    "efghi"

这个东西,跟{"abc", "d", "efghi"}在功能上就有点类似。

所以,对这个数组进行索引,可以采用{idx}的语法,这里面idx也是一切合法的索引访问语法,包括{:},那么,得到的是什么呢?居然是字符数组的逗号分割列表……

>> strs{:}

ans =

    'abc'


ans =

    'd'


ans =

    'efghi'

好吧,最终还是闭环了……

逗号分割列表的作用

这个逗号分割列表跟Common Lisp中的values一样,主要用于处理函数的输入和输出。

函数输入

通过逗号分割列表等效,我们就能够动态构造函数的输入变量。构造一个比输入数组倍数大小的数组:

function B = matrixBigger(A, n)
sz = arrayfun(@(szi) n * szi, size(A), 'UniformOutput', false);
B = zeros(sz{:});
end

对于那些参数可变的函数,更有特别的效果:

>> C = {1, 0, Inf, rand(10,1)}

C =

  1×4 cell 数组

    {[1]}    {[0]}    {[Inf]}    {10×1 double}

>> vertcat(C{:})

ans =

    1.0000
         0
       Inf
    0.7816
    0.3939
    0.4578
    0.5519
    0.6699
    0.7861
    0.4083
    0.6945
    0.2809
    0.1369

根据逗号分割列表等效原则,这就等于是vertcat([1], [0], [Inf], rand(10, 1))

函数输出

函数输出也同样可以应用逗号分割列表等效原则,[a, b, c] = func(e, d, f),就能够等效为
[C{1}, C{2}, C{3}] = func(d{1}, d{2}, d{3}),可以写成

[C{:}] = func(d{:})

这个对于这个ind2sub函数可能会有点用。

>> help ind2sub
ind2sub - 将线性索引转换为下标
    此 MATLAB 函数 返回数组 row 和 col,其中包含与大小为 sz 的矩阵的线性索引 ind 对应
    的等效行和列下标。此处,sz 是包含两个元素的向量,其中 sz(1) 指定行数,sz(2) 指定列
    数。
>> sz = [2, 4]

sz =

     2     4

>> [idx{1:2}] = ind2sub(sz, 1:8)

idx =

  1×2 cell 数组

    {[1 2 1 2 1 2 1 2]}    {[1 1 2 2 3 3 4 4]}

可以看到idx{1} - idx{2}就对应1,2,3,..., 8对应的二维数组下标。

这个函数对于更加复杂的gradientndgrid这些函数也挺好用的。

为了更好地提供多个输出值,Matlab还有一个函数deal专门来干这个事情。

>> help deal
 deal - 将输入分发到输出
    此 MATLAB 函数 复制输入参数 A1,...,An,并将它们作为输出参数 B1,...,Bn 返回。它与
    B1 = A1、…、Bn = An 相同。在此语法中,输入和输出参数的数目必须相同。

    语法
      [B1,...,Bn] = deal(A1,...,An)
      [B1,...,Bn] = deal(A)

这样就可以在匿名函数中很容易定义多输出函数:

>> f = @(n)deal(n, n*n, n*n*n)

f =

  包含以下值的 function_handle:

    @(n)deal(n,n*n,n*n*n)

>> [results{1:3}] = f(10)

results =

  1×3 cell 数组

    {[10]}    {[100]}    {[1000]}

通过deal函数,还能够很容易的设定结构数组的某个字段的值:

>> s = dir("*.m")

s = 

  包含以下字段的 13×1 struct 数组:

    name
    folder
    date
    bytes
    isdir
    datenum

>> names = cellfun(@(fn) upper(fn), {s.name}, 'UniformOutput', false);
>> [s.uname] = deal(names{:})

s = 

  包含以下字段的 13×1 struct 数组:

    name
    folder
    date
    bytes
    isdir
    datenum
    uname

很容易地给数组的每个结构体增加一个字段uname是对应的名称的大写字符数组。

总结

  1. 逗号分割列表是一个字面上很简单的概念,就是a, b, c, d这个形式,允许不同类型的对象列在一起
  2. 元胞数组和字符串数组跟逗号分割列表之间的转换是操作函数输入输出的主要应用。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部