Excel动态数组公式应用——拆分字符串

Excel动态数组公式应用——拆分字符串

如果不着急要拆分字符串,可以等Excel推出TEXTSPLIT函数(现在这个函数在Insider Beta)。如果现在就想要这个功能,你的Excel必须是正版的Microsoft Office 365。不想看过程的话,可以直接跳到“具体操作”部分。


目前拆分字符串的方法

相比于其他编程语言,拆分字符串在Excel一直是一个不太容易的操作。例如,我要拆分一下字符串(分隔符为 , ):

在没有动态数组公式以前,有以下方式,但都有不同程度的缺点:

  • FIND查找分隔符+MID提取子字符串
    • 优点:纯公式,如果输入值改变,输出值自动更新。
    • 缺点:如果有若干个分隔符,公式将会很难看懂。如果有未知数量的分隔符,就不能用FIND+MID去实现这个功能。
  • VBA定义新公式
    • 优点:纯公式,如果输入值改变,输出值自动更新。
    • 缺点:VBA无法输出动态的数组,所以每个子字符串要单独提取。
  • 文本分列向导 功能:
    • 优点:所有字符串,就算不知道有多少个,都能一次性提取。
    • 缺点:不是公式。使用这个功能拆分子字符串后,输出结果是硬编码。如果输入值改变了,要重新运行该功能。

Google Sheets 的 SPLIT 函数

我们看看友商是怎么解决这个难题的:Google Sheets,国外Excel的免费替代品,有SPLIT函数,十分好用。

可以看到,Google Sheets和常用的编程语言一样,用SPLIT就可以拆分字符串,所有子字符串,都能拆分到不同的单元格。

可惜的是,Excel没有类似于SPLIT函数,不过可以自己创造一个,并将函数储存到名称管理器,方便以后运用。


实现方法1

总的来说,第一个方法就是把一个字符串拆分成字符数组,然后遍历数组里面的每一个字符,根据字符是否等于分隔符,重新把字符数组重新合并成想要的的子字符串。

用Excel现有的动态数组公式要分几步:

  1. 把一个字符串分成单个字符的数组。这里要用到Excel里面两个动态数组公式: MAP SEQUENCE 。如果输入是 [a,bc,def] 的话,输出就是 [a][,][b][c][,][d][e][f] 。(这里,每一个 [ ] 就是一个单元格。)
  2. 遍历第一步的字符数组, 根据分隔符累计字符串。这里要用到动态数组公式 SCAN 。 例如, [a][,][b][c][,][d][e][f] ,就会变成 [a][a,][b][bc][bc,][d][de][def] 。(可以看到,每遇到一个 , ,就会重新累积字符。
  3. 把带 , 结尾的字符和最后一个字符保留下来,清空其他单元格。例如,上一步的结果就会变成 [][a][][][bc][][][def]
  4. FILTER 来提取非空的子字符串。例如,上一步的结果就会变成 [a][bc][def]

我们可以把这几步合并成一个 LAMBDA 函数,并储存起来。


实现方法2

实现拆分字符串还有另外一种思路:直接找出分隔符的位置,然后根据分割符的位置,提取每一个想要的子字符串。

也需要几步方法实现:

  1. 查找分隔符。可以用 FIND + MAP + UNIQUE 找出分隔符位置。例如输入值是 a,bc,def ,分隔符位置就是 2,5,9 (注意,这里最后一个位置9是故意加进去的,这会使得之后的操作更方便。)
  2. 知道分隔符位置后,就可以知道子字符串的起始位置。同样一个例子,子字符串起始位置是 1,3,6
  3. 知道子字符串起始位置和分隔符位置后,就可以用 MID 提取子字符串了。

最后把这几步合并成一个 LAMBDA ,又保存起来。

这是具体的实现方法:

这里,第1步所用的函数 MAP + FIND 的时间复杂度是 \Theta(n^2) ,但是我在合并的时候,运用了动态规划的算法思维,运用了 SCAN + FIND ,所以最终的时间复杂度和方法1是一致的,都是 \Theta(n) 。虽然方法2在实现中要处理很多细节,但是总的结果是比方法1更简洁。


具体操作

  1. 在 Excel for Windows 中,选择“ 公式 ”>“ 名称管理器”。 在 Excel for Mac 中,选择“ 公式 ”>“ 定义名称 ”。
  2. 选择“ 新建 ”,然后在“ 新建名称 ”对话框中输入信息:
  3. 在名称中,输入 TEXTSPLIT 。再引用中,拷贝粘贴以下公式,然后按确认。
=LAMBDA(value,delimiter,
  LAMBDA(dlm_loc_arr,
    TRANSPOSE(MAP(
      MAP(SEQUENCE(ROWS(dlm_loc_arr)),LAMBDA(i,IF(i=1,1,LEN(delimiter)+INDEX(dlm_loc_arr,i-1)))),
      dlm_loc_arr,
      LAMBDA(start_num,dlm_loc,MID(value,start_num,dlm_loc-start_num))
    LAMBDA(nxt_dlm_loc,