在Python中使用多个正则表达式匹配进行“计数”

2 投票
6 回答
963 浏览
提问于 2025-04-16 11:54

假设我有以下的多行字符串:

# 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 个回答

1

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 
1

正则表达式是用来匹配字符串的。它们并不是用来在匹配的过程中操作变量的。你可能不喜欢逐行遍历并自己计数的这个方法,但这其实是个简单直接的解决方案。

3

« 好吧,当然可以,但如果“变量操作”是在 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))) 

撰写回答