【问题标题】:Advanced multiple search replace高级多重搜索替换
【发布时间】:2019-11-29 02:09:05
【问题描述】:

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

假设有文件 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

“;”应该是分隔符。可以是其他任何东西。 脚本应该搜索and A。然后继续搜索B。然后用C1 替换那个B。 然后继续到下一个出现的A。搜索下一个B并将这个B替换为C2。等等。 当脚本将 B 替换为 C3 时,它应该停止,因为没有进一步的规则。

最终文件应如下所示:

B
B
A
  C1
B
B
A
  C2
B
A
  C3

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

【问题讨论】:

  • 请展示您到目前为止所做的尝试,并说明您遇到的具体问题。
  • 我编辑了您的问题以使文件更清晰。但是现在看到输入文件中的一些Bs前面有空格,而这些也恰好是被替换的字母。空间应该在那里吗?此外,在您的示例中,每个被替换的B 都是在搜索并找到的A 之后立即。这一定是这种情况吗?如果A 之后的下一个B 不是紧随其后怎么办?正如前面的评论所说,请展示你自己在这个问题上的一些工作,最好是尝试一些代码。
  • 要搜索和替换的项目是否总是单个字符?如果搜索的项目是一行的部分而不是整行怎么办?当你说“我不能使用任何标准的搜索和替换工具”时,是不是意味着你不能使用 Python 的内置字符串运算符或方法,例如 inreplace

标签: python search replace


【解决方案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())

解释:

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

优点:

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

缺点:

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

输出(注意我添加了一个额外的匹配来检查它是否在规则完成时停止):

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

【讨论】:

    【解决方案2】:

    你可以使用正则表达式来实现类似的东西。 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-24
      • 2021-02-18
      • 1970-01-01
      相关资源
      最近更新 更多