最近在处理数据的时候,碰到个有点头疼的情况。手上拿到一堆数据,格式不统一,都塞在一个元胞数组(就是那个cell array)里头。有的格子里是字符串,有的里面是数字,还有的是个小矩阵,乱七八糟的。
我最初的想法,就是老老实实写个循环,一个格子一个格子地去处理。比如我想知道每个格子里头,如果是字符串,那它的长度是多少。写起来大概就是这样:先弄个空列表准备装着结果,然后用for
循环从第一个格子走到一个,在循环里面先判断这个格子是不是字符串,如果是,就用length
函数取长度,再存到结果列表里;如果不是,可能就记个0或者别的啥标记。
写是能写出来,但总感觉有点啰嗦,特别是当这种处理逻辑要在好几个地方用的时候,每次都写这么一段循环,代码看起来就有点臃肿,不清爽。
摸索新方法
我就琢磨着,MATLAB这么强大的工具,应该有更简便的方法?就去翻翻文档,也顺便在网上扒拉一下,看看大家是怎么处理这种批量操作元胞数组的问题的。这时候,cellfun
这个函数就跳到我眼前。
看介绍说,这玩意儿能把一个函数应用到元胞数组的每个元素上。听起来正是我想要的!
开始动手试
我决定先拿个简单的例子试试水。
- 搞个元胞数组: 我先随手创建一个,比如
C = {'你好', '世界', '这是一个测试', '哈哈'}
。 - 想干 我想获取每个字符串的长度。
- 用
cellfun
试试: 按照说明,我试着写lengths = cellfun(@length, C)
。@length
就是告诉cellfun
,对每个格子都用length
这个函数处理一下。
执行一下,lengths
这个变量里头,果然就得到一个包含每个字符串长度的向量,比如 [2 2 6 2]
这种。确实比我自己写循环简单多!
处理更复杂点的情况
尝到甜头后,我就想用它来解决我最初那个更复杂的问题。我的元胞数组里不光有字符串,还有数字啥的。直接用@length
肯定不行,遇到数字会报错。
我想起来cellfun
好像可以接受自定义的函数。那我就可以自己写个小逻辑:
- 先判断传进来的这个格子是啥类型。
- 如果是字符串,就返回它的长度。
- 如果不是字符串,就返回个固定的值,比如 0 或者 NaN(Not a Number)。
在MATLAB里,可以直接用匿名函数(就是那个带符号,后面跟括号和表达式的)来实现这个小逻辑。我试着写类似这样的:
results = cellfun(@(x) my_custom_logic(x), my_mixed_cell_array)
这里的 my_custom_logic(x)
就是我上面说的那个判断类型、计算长度的逻辑,可以单独写成一个小函数,或者直接用 MALTAB 内置的 ischar
之类的函数在匿名函数里头写判断。
比如,一个简单的匿名函数可以写成: @(x) ischar(x)length(x)
。如果x是字符,ischar(x)
返回1,乘上长度就是长度;如果不是字符,ischar(x)
返回0,乘上任何东西都是0。这只是个简化例子,实际逻辑可能需要更完善。
遇到的小坎坷和解决方案
在使用过程中,也碰到过一个小问题。有时候,我那个自定义函数处理完之后,返回的结果类型不统一。比如,有的格子处理完返回数字,有的返回字符串,或者有的返回空数组。
这时候直接用cellfun
,它会尝试把所有结果统一成一个普通的数值数组,如果做不到,就会报错。后来发现,cellfun
有个参数叫'UniformOutput'
。默认情况下,它希望输出是统一格式的(值为true
)。
如果我知道输出结果类型不统一,或者不确定,就可以把这个参数设成false
:
results = cellfun(@(x) my_complex_logic(x), my_cell_array, 'UniformOutput', false)
这样设置后,cellfun
就不强求输出格式统一,它会把每个格子的处理结果再放回到一个新的元胞数组里。这样虽然结果还是个元胞数组,但至少不会报错,后续我可以再根据需要去处理这个结果元胞数组。
实践后的感受
总的来说,cellfun
这玩意儿用熟之后,确实挺方便的。特别是在需要对元胞数组里的每个元素执行相同操作时,代码能简洁不少。
- 优点:代码更短,逻辑看起来更清晰,少写很多循环的模板代码。
- 注意点:要稍微注意一下函数的输入输出,特别是当处理逻辑复杂,输出类型可能不统一时,记得用
'UniformOutput', false
。
现在我处理类似数据时,会优先考虑能不能用cellfun
来搞定。虽然它本质上可能也是循环,但写起来舒服多。分享给大家,希望对处理元胞数组感到头疼的朋友有点帮助。