2016年3月7日 星期一

在Google Sheet中,使用函式RegExExtract()與陣列模式進行字串分割。

在Google Sheet中,雖然已經有函式Split()可以進行字串分割,可惜該函式無法與陣列模式搭配使用。這篇文章提出一個改用函式RegExExteact()並搭配陣列模式來分割字串的可能方案。

假設要被分割的字串內容類似"M|AA|BB|CC|DD"或"M|AA|BB|CC|DD|",分隔字元是"|"。可使用下列公式進行分割:
=ARRAYFORMULA( REGEXEXTRACT( TextToBeSeparated, "(?:(.*?)(\||$)){" & TRANSPOSE(ROW($1:$10)) & "}" ) )
其中,ROW($1:$10),表示要取出分割後的第1到第10個欄位。可視需要調整。例如:只想取出第3到第5個欄位的值,就改成傳入ROW($3:$5)。

核心想法,是使用正則表示式".*?"去吻合分隔字元之間的任意字元構成的字串。式中,"."代表任意字元,"*"代表重複0到任意多次,"?"代表取最短的重複次數(non-greedy)。

使用"\|"去吻合分隔字元,但考慮到最後一個欄位後面可能沒有分隔字元,直接就是資料結尾,所以同時使用"$"。構成"\||$"。

合併上述兩個構想,就形成正則表示式"(.*?)(\||$)"。

為了取得該模式重複出現第n次的時的值(也就是分割後第n欄位的值),需要指定重複次數n,這就形成了正則表示式"((.*?)(\||$)){n}",也就是"((.*?)(\||$)){1}"、"((.*?)(\||$)){2}"、"((.*?)(\||$)){3}"、...、"((.*?)(\||$)){10}"這樣一系列的正則表示式。

最後,由於函式RegExExtract()在搭配ArrayFormula()使用時,只會擷取第1個captured sub-match,所以要讓正則表示式中我們想要擷取的"(.*?)"成為1號群組,也就是說,要讓在它之前的所有群組,都是non-numbering群組,所以使用"(?:)"這種群組方式。於是最終的正則表示式就變成"(?:(.*?)(\||$)){n}",其中的n必須帶入數字1,2,3,...等數字。


註:函式RegExExtract()的特性:
  • 如果正則表示式中,沒有任何的captured group,它會擷取(傳回)第一個完全match的值。
  • 如果正則表示式中,有任何的captured group:
    • 如果函式RegExExtract()沒有放在ArrayFormula()裡面,它會擷取(傳回)第一個完全match裡的所有的captured sub-match的值。
    • 如果函式RegExExtract()有被放在ArrayFormula()裡面,它會擷取(傳回)第一個完全match裡的第一個captured sub-match的值。(2017/04/10:現在似乎並沒有這個行為。)