用多个字符分隔符标记字符串

时间:2018-06-24 03:28:06

标签: python regex tokenize

我正在尝试使用以下规则对表达式进行标记化:

  • 分隔符为'}}'和'{{'

  • 分隔符之间的字符串应保持完整(不包括被丢弃的单个空格(可以在解析器中完成)

  • 可以嵌入分隔符并保持顺序

  • '{'和'}'的单次出现应保持不变,并且不要用作分隔符(请参阅上一测试)。

  • 结果中不应有空字符串(可以在解析器中完成)

可以通过对解析中的(正确)结果进行后处理来完成这两个异常(用括号表示)。结果将被馈送到递归下降解析器。

这里有一些试验,没有通过我包含的单元测试。 struct Movie: Codable { var id: Int var vote_count: Int var title: String //etc.. } 函数是最接近匹配的函数,但仍然可以删除某些部分。我没有在下面的代码中使用find_all(它会保留空字符串),但是我运气不错。我希望正则表达式可以避免在我的代码中逐字符扫描字符串。

re.split()

更新

感谢Olivier提供有效的解决方案。我仍然希望,如果我能更好地了解正则表达式监视功能,那么正则表达式解决方案就可以工作。如果我使用下面的def tokenize_search(line): token_pat = re.compile(r'({{)([^{(?!{)]*|[^}(?!})]*)(}})') tokens = re.search(token_pat, line).groups() return list (tokens) def tokenize_findall(line): token_pat = re.compile(r'({{)([^{(?!{)]*|[^}(?!})]*)(}})') tokens = re.findall(token_pat, line) return tokens def check(a1, a2): print(a1 == a2) def main(): check(tokenize_search('{{}}'), ['{{', '}}']) check(tokenize_search('aaa {{}}'), ['{{', '}}']) check(tokenize_search('{{aaa}}'), ['{{', 'aaa', '}}']) check(tokenize_search('{{aa}} {{bbb}}'), ['{{', 'aa', '}}', '{{', 'bbb', '}}']) check(tokenize_search('{{aaa {{ bb }} }}'), ['{{', 'aaa ', '{{', ' bb ', '}}', '}}']) check(tokenize_search('{{aa {{ bbb {{ c }} }} }}'), ['{{', 'aa ', '{{', ' bbb ', '{{', ' c ', '}}', '}}', '}}']) check(tokenize_search('{{a{a}{{ b{b}b {{ c }} }} }}'), ['{{', 'a{a}', '{{', ' b{b}b ', '{{', ' c ', '}}', '}}', '}}']) 方法,它将通过测试,并且所做的全部工作就是将tokenize_finditer组之间的内容填充(除了我可能需要后处理的空间之外)代码更简单)。因此,我希望我可以在skipped的正则表达式中添加一个or子句,该子句应为:“或获取任何字符,后跟与'}}'或'{{'不匹配的任何字符”。不幸的是,我无法成功编写此匹配器。我看过正则表达式甚至可以进行递归匹配的示例,由于这不是递归匹配,因此听起来更可行。

'({{)|(}})'

3 个答案:

答案 0 :(得分:1)

这个问题不是一个parentheses matching problem,但是对于我来说建议不要尝试使用正则表达式解决这个问题已经足够了。

由于您要做的是使用给定的分隔符对字符串进行分区,因此我们可以基于partition函数编写解决方案,并进行一些调整以适合所有规则。 / p>

import re

def partition(s, sep):
    tokens = s.split(sep)

    # Intersperse the separator betweem found tokens
    partition = [sep] * (2 * len(tokens) - 1)
    partition[::2] = tokens

    # We remove empty and whitespace-only tokens
    return [tk for tk in partition if tk and not tk.isspace()]


def tokenize_search(line):
    # Only keep what is inside brackets
    line = re.search(r'{{.*}}', line).group() or ''

    return [tk for sub in partition(line, '{{') for tk in partition(sub, '}}')]

以上代码通过了所有测试。您将需要将该结果提供给解析器,以检查括号是否匹配。

答案 1 :(得分:1)

我相信OlivierMelançon的分区方法是必经之路。但是,正则表达式仍然有一些用途,例如检查所讨论的模式是否正确平衡或从较大的字符串中提取平衡(如第二个示例所示)。

这样做需要这样的递归正则表达式:

{{((?>(?:(?!{{|}}).)++|(?R))*+)}}

Demo

由于Python re模块不支持正则表达式递归,因此您将需要依赖备用regex模块来使用它。

要进一步处理匹配结果,您需要查看$1的内部部分,并一次更深一层,例如\w+|{{((?>(?:(?!(?:{{|}})).)++|(?R))*+)}},但这很麻烦。

答案 2 :(得分:0)

在Twitter上刚收到您的消息:)我知道我晚了2个月,但是如果您感兴趣的话,我有几个新想法。

我仔细查看了这些示例,发现您几乎可以完全匹配并捕获所有“ {{”或“}}”或“一对{{}}对中的令牌”。幸运的是,这很容易表达:

/({{|}}|(?:(?!{{|}})[^ ])+(?!({{(?2)*?}}|(?:(?!{{|}}).)*)*$))/g

On regex101 using your examples

“在{{}}对中间”是唯一棘手的部分。为此,我使用了否定的前瞻,以确保我们没有处于一定数量的(可能嵌套的){{}}对之后,然后是字符串末尾的位置。对于均衡输入,这将确保所有匹配的令牌都在{{}}对之内。

现在,您问“均衡输入”部分如何?如果输入无效,则例如“ aaa}}”的结果将产生["aaa", "}}"]。不理想。您可以单独验证输入;或者,如果您希望将其变成无法改变的怪物,则可以尝试以下操作:

/(?:^(?!({{(?1)*?}}|(?:(?!{{|}}).)*)*+$)(*COMMIT)(*F))?({{|}}|(?:(?!{{|}})[^ ])+(?!({{(?3)*?}}|(?:(?!{{|}}).)*)*+$))/g

Unleashed on regex101

这实际上只是为了展示。我同意建议解析器或其他一些更可维护的工具的其他建议。但是,如果您看过我的博客,那么您就会了解我对这些怪物很感兴趣:)