高级多重搜索替换

时间:2019-07-20 09:16:00

标签: python search replace

问题: 我想以一种高级方式批量替换文件中的模式,所以我不能使用任何标准的搜索和替换工具:

假设有文件1:

B
B
A
  B
B
B
A
  B
B
A
  B

我想用其他东西代替B。但是只有每个B在A之后。

这里是文件2,其中包含“规则”以及如何搜索和替换:

A;B;C1
A;B;C2
A;B;C3

“;”应该是分隔线。可以是其他任何东西。 脚本应搜索和A。然后继续搜索B。并将该B替换为C1。 之后,继续执行下一个出现的A。搜索下一个B,并将其替换为C2。等等。 当脚本用C3替换B后,它应该停止,因为没有其他规则了。

最终文件应如下所示:

B
B
A
  C1
B
B
A
  C2
B
A
  C3

我想为此使用python,但是如果有更简单的方法,它不是强制性的。

2 个答案:

答案 0 :(得分:1)

您可以使用正则表达式实现类似的功能。 re.finditer返回比赛的开始/结束位置,re.sub接受参数应进行多少次替换。您可以从这里开始:

import re

data = '''B
B
A
  B
B
B
A
  B
B
A
  B'''

rules = [
    (r'A.*?(B)', r'C1'),
    (r'A.*?(B)', r'C2'),
    (r'A.*?(B)', r'C3'),
]

startpos = 0
while rules:
    rule = rules.pop(0)
    for g in re.finditer(rule[0], data[startpos:], flags=re.DOTALL):
        data = data[:startpos + g.start(1)] + re.sub(g.group(1), rule[1], data[startpos + g.start(1):], count=1)
        startpos += g.start(1)
        break

print(data)

打印:

B
B
A
  C1
B
B
A
  C2
B
A
  C3

答案 1 :(得分:1)

我开始编写基于正则表达式的解决方案,但是@Andrej首先到达那里!因此,我向您展示了一种不使用正则表达式的更“幼稚”的方法。

#!/usr/bin/env python3
import sys


def read_rules(fpath="/tmp/test.rules", sep=";"):
    rules = []
    with open(fpath) as f:
        for line in f:
            rules.append(line.strip().split(sep))

    return rules


def parse_data(rules, fpath="/tmp/test.data"):
    cur_rule = rules[0]
    rule_idx = 0
    data = []
    state = None

    with open(fpath) as f:
        for line in f:
            line = line.strip('\n')
            if not cur_rule:
                data.append(line)
                continue

            # We match start
            if cur_rule[0] in line and not state:
                # End matches in the same line and start < end
                # This case is not in your data
                if (
                    cur_rule[1] in line
                    and line.index(cur_rule[0]) < line.index(cur_rule[1])
                ):
                    new_line = line.replace(cur_rule[1], cur_rule[2], 1)
                    data.append(new_line)
                    rule_idx += 1

                    # We reached the end of rules
                    if len(rules) == rule_idx:
                        cur_rule = None
                    else:
                        cur_rule = rules[rule_idx]
                else:
                    # Set state to looking for end
                    state = 1
                    data.append(line)

                continue

            # Now, if here we are looking for end...
            if state == 1:
                # Nope... not found... move on
                if cur_rule[1] not in line:
                    data.append(line)
                    continue

                # replace
                data.append(
                    line.replace(cur_rule[1], cur_rule[2], 1)
                )

                # Reset state
                state = None

                rule_idx += 1

                # We reached the end of rules
                if len(rules) == rule_idx:
                    cur_rule = None
                else:
                    cur_rule = rules[rule_idx]
                continue

            # Here, no line matched
            data.append(line)


    return data


def main():
    rules = read_rules()
    print(rules)
    data = parse_data(rules)
    print("\n".join(data))


if __name__ == "__main__":
    sys.exit(main())

说明:

  • 这是一种逐行算法,可有效处理大型数据集
  • 基于“状态”:我们寻找“开始”(第一个字符)或“结束”(要匹配的第二个字符)
  • 如果找到开始:
    • 如果我们在同一行中结束,请执行替换并前进至下一条规则
    • 如果我们没有在同一行中结束,请更改状态并移至下一行
  • 如果我们处于状态= 1(查找“结束”),并且在当前行中找到它,则执行替换并移至下一条规则
  • 在任何时候我们都会推进规则,如果到达规则末尾,请将cur_rule设置为None。超过该点的所有行都将从输入复制到输出,而无需任何处理

优点:

  • 对于大量输入,这应该更快。输出也可以优化为“即时”,而不是存储在内存中
  • 我认为更容易理解

缺点:

  • 它不能处理所有情况,这就是为什么我称其为“天真”。一个示例是,如果您在同一行中有2个匹配项,或者在同一行中(按此顺序-先结束)匹配“结束”和“开始”。可以根据需要对这种情况进行调整,但可能会变得复杂,并且正则表达式解决方案变得更具吸引力

输出(请注意,我添加了一个额外的匹配项以检查规则完成后是否停止):

B
B
A
  C1
B
B
A
  C2
B
A
  C3
A
  B