【问题标题】:Regular expression that takes <...> as one item in "foo bar <hello world> and so on" (Goal: Simple music/lilypond parsing)在“foo bar <hello world>等”中将<...>作为一项的正则表达式(目标:简单的音乐/lilypond解析)
【发布时间】:2013-02-10 18:32:50
【问题描述】:

我在 Python(3) 中使用 re 模块,想用以下格式替换 (re.sub(regex, replace, string)) 字符串

"foo <bar e word> f ga <foo b>" 

"#foo <bar e word> #f #ga <foo b>"

甚至

"#foo #<bar e word> #f #ga #<foo b>" 

但我无法将单个单词与 <...> 构造中的单词边界隔离开来。

帮助会很好!

附注 1

整个故事是一部音乐剧: 我有 Lilypond 格式的字符串(或者更好的是,非常简单的核心格式的一个子集,只是音符和持续时间),并希望将它们转换为 python 对 int(duration),list(of pitch strings)。性能并不重要,所以我可以来回转换它们,使用 python 列表迭代,拆分字符串并再次加入它们等。 但是对于上面的问题我没有找到答案。

源字符串

"c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1"

应该会导致

[
(4, ["c'"]),
(8, ["d"]),
(16, ["e'", "g'"]),
(4, ["fis'"]),
(0, ["a,,"]),
(0, ["g", "b'"]),
(1, ["c''"]),
]

基本格式是字符串+数字,像这样:e4 bes16

  • 列表项
  • 字符串可以包含多个,至少一个 [a-zA-Z] 字符
  • 字符串后跟零个或多个数字:e bes g4 c16
  • 字符串后跟零个或多个 ' 或 ,(不合并):e' bes, f'''2 g,,4
  • 字符串可以用字符串列表代替,列表限制符为:4个数字在>后面,不允许有空格

附注2

目标不是创建 Lilypond 解析器。它真的只是用于非常短的sn-ps,没有额外的功能,没有插入注释的扩展。如果这不起作用,我会选择另一种格式(简化),如 ABC。所以任何与 Lilypond 相关的事情(“通过 lilypond 运行它,让它在 Scheme 中给出音乐数据,解析它”)或其工具链肯定不是这个问题的答案。该软件包甚至没有安装。

【问题讨论】:

    标签: python regex lilypond


    【解决方案1】:

    我知道您不是在寻找通用解析器,但pyparsing 使这个过程变得非常简单。您的格式似乎与我作为最早的 pyparsing 示例之一编写的 chemical formula parser 非常相似。

    这是您使用 pyparsing 实现的问题:

    from pyparsing import (Suppress,Word,alphas,nums,Combine,Optional,Regex,Group,
                           OneOrMore)
    
    """
    List item
     -the string can consist of multiple, at least one, [a-zA-Z] chars
     -the string is followed by zero or more digits: e bes g4 c16
     -the string is followed by zero or more ' or , (not combined): 
      e' bes, f'''2 g,,4
     -the string can be substituted by a list of strings, list limiters are <>;
      the number comes behind the >, no space allowed
    """
    
    LT,GT = map(Suppress,"<>")
    
    integer = Word(nums).setParseAction(lambda t:int(t[0]))
    
    note = Combine(Word(alphas) + Optional(Word(',') | Word("'")))
    # or equivalent using Regex class
    # note = Regex(r"[a-zA-Z]+('+|,+)?")
    
    # define the list format of one or more notes within '<>'s
    note_list = Group(LT + OneOrMore(note) + GT)
    
    # each item is a note_list or a note, optionally followed by an integer; if
    # no integer is given, default to 0
    item = (note_list | Group(note)) + Optional(integer, default=0)
    
    # reformat the parsed data as a (number, note_or_note_list) tuple
    item.setParseAction(lambda t: (t[1],t[0].asList()) )
    
    source = "c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1"
    print OneOrMore(item).parseString(source)
    

    有了这个输出:

    [(4, ["c'"]), (8, ['d']), (16, ["e'", "g'"]), (4, ["fis'"]), (0, ['a,,']), 
     (0, ['g,', "b'"]), (1, ["c''"])]
    

    【讨论】:

    • 这个很好很干净。不过,Pyparsing 会增加一个额外的依赖项。对于一个非常小的 sn-p 辅助函数来说,这太多了。
    【解决方案2】:

    你的第一个问题可以这样回答:

    >>> import re
    >>> t = "foo <bar e word> f ga <foo b>"
    >>> t2 = re.sub(r"(^|\s+)(?![^<>]*?>)", " #", t).lstrip()
    >>> t2
    '#foo #<bar e word> #f #ga #<foo b>'
    

    我添加了lstrip() 以删除出现在此模式结果之前的单个空格。如果您想使用第一个选项,您可以简单地将 #&lt; 替换为 &lt;

    您的第二个问题可以通过以下方式解决,但您可能需要考虑 ['g,', "b'"] 等列表中的 ,。您的字符串中的逗号是否应该存在?可能有更快的方法。以下仅是概念证明。列表推导式可能会代替最后一个元素,尽管它会非常复杂。

    >>> s = "c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1"
    >>> q2 = re.compile(r"(?:<)\s*[^>]*\s*(?:>)\d*|(?<!<)[^\d\s<>]+\d+|(?<!<)[^\d\s<>]+")
    >>> s2 = q2.findall(s)
    >>> s3 = [re.sub(r"\s*[><]\s*", '', x) for x in s2]
    >>> s4 = [y.split() if ' ' in y else y for y in s3]
    >>> s4
    ["c'4", 'd8', ["e'", "g'16"], "fis'4", 'a,,', ['g,', "b'"], "c''1"]
    >>> q3 = re.compile(r"([^\d]+)(\d*)")
    >>> s = []
    >>> for item in s4:
        if type(item) == list:
                lis = []
                for elem in item:
                        lis.append(q3.search(elem).group(1))
                        if q3.search(elem).group(2) != '':
                                num = q3.search(elem).group(2)
                if q3.search(elem).group(2) != '':
                        s.append((num, lis))
                else:
                        s.append((0, lis))
        else:
                if q3.search(item).group(2) != '':
                        s.append((q3.search(item).group(2), [q3.search(item).group(1)]))
                else:
                        s.append((0, [q3.search(item).group(1)]))
    
    
    >>> s
    [('4', ["c'"]), ('8', ['d']), ('16', ["e'", "g'"]), ('4', ["fis'"]), (0, ['a,,']), (0, ['g,', "b'"]), ('1', ["c''"])]
    

    【讨论】:

    • 我用一个快速而肮脏的解决方案对你的第一个后记更新了这个答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多