在Python中使用多个正则表达式匹配进行“计数”
假设我有以下的多行字符串:
# Section
## Subsection
## Subsection
# Section
## Subsection
### Subsubsection
### Subsubsection
# Section
## Subsection
我想把它变成:
# 1 Section
## 1.1 Subsection
## 1.2 Subsection
# 2 Section
## 2.1 Subsection
### 2.1.1 Subsubsection
### 2.1.2 Subsubsection
# 3 Section
## 3.1 Subsection
在Python中,使用re
模块,是否可以对这个字符串进行替换操作,具体要求是:
- 根据每行开头的
#
的数量进行匹配 - 记录之前匹配到的相同数量的
#
的次数 - 在合适的地方把这个计数器插入到行中
...假设这些“计数器”总是大于零?
这个问题让我对正则表达式的知识感到挑战。我知道我可以遍历每一行,增加或插入一些变量,但这不是我想要的解决方案。我只是好奇,是否有这种功能仅仅存在于正则表达式中,因为我知道某种计数功能已经存在(例如,要进行的替换次数)。
6 个回答
Pyparsing把这些扫描、匹配和替换的任务都打包成了一个自己的解析框架。下面是一个带注释的解决方案,针对你提到的问题:
from pyparsing import LineStart, Word, restOfLine
source = """\
# Section
## Subsection
## Subsection
# Section
## Subsection #
### Subsubsection
### Subsubsection
# Section
## Subsection
"""
# define a pyparsing expression to match a header line starting with some
# number of '#'s (i.e., a "word" composed of '#'s), followed by the rest
# of the line
sectionHeader = LineStart() + Word("#")("level") + restOfLine
# define a callback to keep track of the nesting and numbering
numberstack = [0]
def insertDottedNumber(tokens):
level = len(tokens.level)
if level > len(numberstack):
numberstack.extend([1]*(level-len(numberstack)))
else:
del numberstack[level:]
numberstack[level-1] += 1
dottedNum = '.'.join(map(str,numberstack))
# return the updated string containing the original level and rest
# of the line, with the dotted number inserted
return "%s %s %s" % (tokens.level, dottedNum, tokens[1])
# attach parse-time action callback to the sectionHeader expression
sectionHeader.setParseAction(insertDottedNumber)
# use sectionHeader expression to transform the input source string
newsource = sectionHeader.transformString(source)
print newsource
输出你想要的结果:
# 1 Section
## 1.1 Subsection
## 1.2 Subsection
# 2 Section
## 2.1 Subsection #
### 2.1.1 Subsubsection
### 2.1.2 Subsubsection
# 3 Section
## 3.1 Subsection
正则表达式是用来匹配字符串的。它们并不是用来在匹配的过程中操作变量的。你可能不喜欢逐行遍历并自己计数的这个方法,但这其实是个简单直接的解决方案。
« 好吧,当然可以,但如果“变量操作”是在 re.sub 的回调函数中进行的,那这样可以吗?我想我问题的简化版是:“能否根据之前的匹配结果,使用正则表达式进行不同的替换?” »
听起来我们需要一个生成器函数作为回调;不幸的是,re.sub() 不接受生成器函数作为回调。
所以我们必须使用一些技巧:
import re
pat = re.compile('^(#+)',re.MULTILINE)
ch = '''# Section
## Subsection
## Subsection
# Section
## Subsection
### Subsubsection
### Subsubsection
## Subsection
### Subsubsection
### Subsubsection
#### Sub4section
#### Sub4section
#### Sub4section
#### Sub4section
##### Sub5section
#### Sub4section
##### Sub5section
##### Sub5section
### Subsubsection
### Subsubsection
#### Sub4section
#### Sub4section
## Subsection
### Subsubsection
### Subsubsection
### Subsubsection
#### Sub4section
##### Sub5section
##### Sub5section
### Subsubsection
#### Sub4section
## Subsection
### Subsubsection
### Subsubsection
# Section
## Subsection
## Subsection
# Section
## Subsection
### Subsubsection
#### Sub4section
#### Sub4section
#### Sub4section
##### Sub5section
#### Sub4section
### Subsubsection
## Subsection
### Subsubsection
# Section
## Subsection
'''
def cbk(match, nb = [0] ):
if len(match.group())==len(nb):
nb[-1] += 1
elif len(match.group())>len(nb):
nb.append(1)
else:
nb[:] = nb[0:len(match.group())]
nb[-1] += 1
return match.group()+' '+('.'.join(map(str,nb)))
ch = pat.sub(cbk,ch)
print ch
.
« 默认参数值是在函数定义执行时被计算的。这意味着这个表达式只在函数定义时计算一次,然后在每次调用时都使用这个“预先计算”的值。这一点特别重要,当默认参数是可变对象时,比如列表或字典:如果函数修改了这个对象(例如,往列表中添加一个项目),那么默认值实际上是被修改了。这通常不是我们想要的结果。 »
http://docs.python.org/reference/compound_stmts.html#function
但在这里,这正是我的本意。
结果:
# 1 Section
## 1.1 Subsection
## 1.2 Subsection
# 2 Section
## 2.1 Subsection
### 2.1.1 Subsubsection
### 2.1.2 Subsubsection
## 2.2 Subsection
### 2.2.1 Subsubsection
### 2.2.2 Subsubsection
#### 2.2.2.1 Sub4section
#### 2.2.2.2 Sub4section
#### 2.2.2.3 Sub4section
#### 2.2.2.4 Sub4section
##### 2.2.2.4.1 Sub5section
#### 2.2.2.5 Sub4section
##### 2.2.2.5.1 Sub5section
##### 2.2.2.5.2 Sub5section
### 2.2.3 Subsubsection
### 2.2.4 Subsubsection
#### 2.2.4.1 Sub4section
#### 2.2.4.2 Sub4section
## 2.3 Subsection
### 2.3.1 Subsubsection
### 2.3.2 Subsubsection
### 2.3.3 Subsubsection
#### 2.3.3.1 Sub4section
##### 2.3.3.1.1 Sub5section
##### 2.3.3.1.2 Sub5section
### 2.3.4 Subsubsection
#### 2.3.4.1 Sub4section
## 2.4 Subsection
### 2.4.1 Subsubsection
### 2.4.2 Subsubsection
# 3 Section
## 3.1 Subsection
## 3.2 Subsection
# 4 Section
## 4.1 Subsection
### 4.1.1 Subsubsection
#### 4.1.1.1 Sub4section
#### 4.1.1.2 Sub4section
#### 4.1.1.3 Sub4section
##### 4.1.1.3.1 Sub5section
#### 4.1.1.4 Sub4section
### 4.1.2 Subsubsection
## 4.2 Subsection
### 4.2.1 Subsubsection
# 5 Section
## 5.1 Subsection
编辑 1:我把 else nb[:] = nb[0:len(match.group())] 改成了 else: 仅此而已
编辑 2:代码可以简化为
def cbk(match, nb = [0] ):
if len(match.group())>len(nb):
nb.append(1)
else:
nb[:] = nb[0:len(match.group())]
nb[-1] += 1
return match.group()+' '+('.'.join(map(str,nb)))