一、问题背景:
最近在研究如何从复杂的SQL中解析来源表的问题,扩展到后面研究如何解析SQL的来源字段和字段别名问题,这两个问题都有一个核心问题要解决。
以下面的解析来源字段和字段别名为例,无论用什么解析方法,最后都需要按逗号去分割字段,而在这里面,因为有子查询和函数,里面也会有逗号,所以最后会干扰到逗号分割的准确性。
但是可以先用一个方法,把括号中的内容都整体识别出来,先用一些没有逗号的特定符号替换掉,再按逗号切割就可以避免这个问题了,最后再根据前面的替换规则,还原括号的内容即可。
但这个按括号识别的过程就有一个麻烦的问题了,就是当括号没有互相嵌套时,可以很容易用正则识别到括号中的内容,例如下面:
a 的括号都按顺序排列,用正则识别是完全精准的。
string的括号是相互嵌套的,用正则识别出来不准确。
二、问题简化
上面这个按括号识别的问题可以简化成一道小算法题:
假设有以下字符串,里面有多对括号互相嵌套,请将所有互相配对的括号内容截取出来,放到一个列表中,顺序不限
string = 'a(bb)(ggg(f(cc)f)hh(dd))(jj(ii)jj)(eee)a'
# 解析结果示例:
['(eee)', '(ii)', '(jj(ii)jj)', '(dd)', '(cc)',
'(f(cc)f)', '(ggg(f(cc)f)hh(dd))', '(bb)']
三、过程分析
string直观看并没有明显的规律,我们可以先把所有的左括号和所有的右括号的位置分别打印出来观察一下有没有一个固定的解析规律
可以发现以下的规律,当你从倒数第一个左括号(位置为34)开始看时,你会发现在它右边的第一个右括号(位置38)跟它必然是成对的,此时可以把这个配对(34, 38)取出暂存起来,并且从列表中移除,再继续找倒数的第一个左括号,也就是位置为27的,又可以找到在它右边的第一个右括号(位置30),又可以把它们配对暂存,按照这个规律找下去,直到2个列表都为空时,结束任务。
此时我们就完整地取出所有括号的配对关系了,再按照这个配对关系去截取字符串即可。
四、完整代码
只需要十几行:
lefts = [i.span()[0] for i in re.finditer('\(', string)]
rights = [i.span()[0] for i in re.finditer('\)', string)]
print(lefts)
print(rights)
lefts.sort(reverse=True)
Parentheses = []
while len(lefts) > 0:
for i in lefts:
for j in rights:
if j > i:
Parentheses.append((i, j))
lefts.remove(i)
rights.remove(j)
break
break
print(Parentheses)
ret = []
for i in Parentheses:
ret.append(string[i[0]: i[1]+1])
print(ret)
输出结果:
[1, 5, 9, 11, 19, 24, 27, 34] # 所有左括号位置
[4, 14, 16, 22, 23, 30, 33, 38] # 所有右括号位置
[(34, 38), (27, 30), (24, 33), (19, 22), (11, 14),
(9, 16), (5, 23), (1, 4)] # 配对的关系
['(eee)', '(ii)', '(jj(ii)jj)', '(dd)', '(cc)',
'(f(cc)f)', '(ggg(f(cc)f)hh(dd))', '(bb)'] # 按配对关系截取出的字符
这个逻辑是从倒数第一个左括号开始找,往右边找它的第一个配对的右括号。
其实反过来从第一个右括号开始找,往左边找它的第一个左括号也是可以的。