【问题标题】:Complex parsing of a string in PythonPython中字符串的复杂解析
【发布时间】:2016-06-15 19:45:01
【问题描述】:

我想解析格式如下的字符串:

[{text1}]{quantity}[{text2}]

这条规则意味着在开头有一些文本可以选择存在或不存在,然后是 {quantity},其语法我将在下面描述,然后是更多可选文本。

{quantity} 可以有多种形式,其中 {n} 是任何正整数

{n}
{n}PCS
{n}PC
{n}PCS.
{n}PC.
Lot of {n}

此外,它应该接受这个附加规则:

{n} {text2} 

在此规则中,{n} 后跟一个空格,然后是 {text2}

在出现PC或PCS的情况下

  • 它后面可能跟也可能不跟一个点
  • 不区分大小写
  • 可以选择在 {n} 和 PCS 之间出现空格
  • 以下内容全部被剥离:PC 或 PCS、可选的点和可选的空格

所需的输出被归一化为两个变量:

  • {n} 为整数
  • [{text1}] [{text2}],即第一个 {text1}(如果存在),然后是空格,然后是 {text2}(如果存在),连接到一个字符串。仅当有两个文本段时才使用空格分隔文本段。

如果 {quantity} 包含除正整数以外的任何内容,则 {n} 仅包含整数,并且 {quantity} 的其余部分(例如“PCS.”)从 {n} 和结果文本中删除字符串。

在文本部分,可能会出现更多的整数。找到的 {quantity} 以外的任何内容都应视为文本的一部分,而不是解释为另一个数量。

我是一名前 C/C++ 程序员。如果我必须用这些语言来解决这个问题,我可能会在 lex 和 yacc 中使用规则,否则我将不得不编写很多讨厌的代码来手动解析它。

我想学习一种在 Python 中高效编码的简洁方法,可能使用某种形式的规则来轻松支持更多案例。我想我可以在 Python 中使用 lex 和 yacc,但我想知道是否有更简单的方法。我是 Python 新手;我什至不知道从哪里开始。

我并不是要求任何人为一个完整的解决方案编写代码,而是我需要一两种方法,也许还有一些示例代码来展示如何做到这一点。

【问题讨论】:

  • 首先要弄清楚的问题是您的语言是否与上下文无关。这将决定您是否可以使用正则表达式或类似工具。如果你不能,那么老实说,yacc 是这项工作的默认工具。 yacc 可能有一个 python 特定的包,但原来的包也一样 :-)
  • PLY 是 Python 的 lex-yacc,但 pyparsing 可能更容易走出大门。
  • 我认为它是上下文无关的,从某种意义上说,会有单独的一行项目被单独分析。所以你是在为无上下文数据行建议正则表达式?

标签: python string parsing


【解决方案1】:

Pyparsing 让您通过使用 '+' 和 '|' 将较小的解析器拼接在一起来构建解析器运营商(除其他外)。您还可以将名称附加到解析器中的各个元素,以便之后更容易获取值。

from pyparsing import (pyparsing_common, CaselessKeyword, Optional, ungroup, restOfLine, 
    oneOf, SkipTo)

int_qty = pyparsing_common.integer

# compose an expression for the quantity, in its various forms
"""
{n}
{n}PCS
{n}PC
{n}PCS.
{n}PC.
Lot of {n}
"""
LOT = CaselessKeyword("lot")
OF = CaselessKeyword("of")
pieces = oneOf("PC PCS PC. PCS.", caseless=True)
qty_expr = Optional(LOT + OF).suppress() + int_qty("qty") + Optional(pieces).suppress()

# compose expression for entire line
line_expr = SkipTo(qty_expr)("text1") + qty_expr + restOfLine("text2")

tests = """
    Send me 1000 widgets pronto!
    Deliver a Lot of 50 barrels of maple syrup by Monday, June 10.
    My shipment was short by 25 pcs.
    """

line_expr.runTests(tests)

打印:

Send me 1000 widgets pronto!
['Send me', 1000, ' widgets pronto!']
- qty: 1000
- text1: ['Send me']
- text2:  widgets pronto!


Deliver a Lot of 50 barrels of maple syrup by Monday, June 10.
['Deliver a ', 50, ' barrels of maple syrup by Monday, June 10.']
- qty: 50
- text1: ['Deliver a ']
- text2:  barrels of maple syrup by Monday, June 10.


My shipment was short by 25 pcs.
['My shipment was short by', 25, '']
- qty: 25
- text1: ['My shipment was short by']
- text2: 

编辑: Pyparsing 支持两种形式的匹配备选方案:MatchFirst,它在第一个匹配的备选方案上停止(使用“|”运算符定义),以及 Or,它评估所有备选方案并选择最长的匹配项(使用“^”运算符定义) .因此,如果您需要数量表达式的优先级,那么您明确定义它:

qty_pcs_expr = int_qty("qty") + White().suppress() + pieces.suppress()
qty_expr = Optional(LOT + OF).suppress() + int_qty("qty") + FollowedBy(White())

# compose expression for entire line
line_expr = (SkipTo(qty_pcs_expr)("text1") + qty_pcs_expr + restOfLine("text2") |
             SkipTo(qty_expr)("text1") + qty_expr + restOfLine("text2"))

以下是新测试:

tests = """
    Send me 1000 widgets pronto!
    Deliver a Lot of 50 barrels of maple syrup by Monday, June 10.
    My shipment was short by 25 pcs.
    2. I expect 22 pcs delivered in the morning
    On May 15 please deliver 1000 PCS.
    """

给予:

2. I expect 22 pcs delivered in the morning
['2. I expect ', 22, ' delivered in the morning']
- qty: 22
- text1: ['2. I expect ']
- text2:  delivered in the morning


On May 15 please deliver 1000 PCS.
['On May 15 please deliver ', 1000, '']
- qty: 1000
- text1: ['On May 15 please deliver ']
- text2: 

【讨论】:

  • 想知道:不符合我的规则的字符串会发生什么?有什么方法可以抛出异常吗?
  • 多么棒的声明式 API。比我开发的正则表达式解决方案更具可读性。
  • @MarkColan - 是的,如果输入字符串不匹配,pyparsing 将引发 ParseException。
  • 我有一个需要手动编辑一些记录的问题。仅当后面有空格时,才应将整数视为数量。在你的例子中,一个单独的整数(即不是 PCS 规则)被视为数量,即使它后面没有空格。如何解决?另外,有没有办法优先考虑 {quantity} 规则,例如始终选择“100 PCs”或“100PCS”形式(如果存在),即使它位于末尾?
【解决方案2】:

我不知道你是否想使用re,但这是一个我认为有效的正则表达式。您可以更改str 值来测试它。匹配返回一个具有三个值 [{text1}]{quantity}[{text2}] 的元组。如果缺少 text1 和 text2,则元组中的第一项和最后一项将为空。

import re

str = "aSOETIHSIBSROG1PCS.ecsrGIR"

matchObj = re.search(r'([a-zA-Z]+|)(\dPCS?\.?|Lot of \d)([a-zA-Z]+|)',str).groups()
print matchObj.groups()

#Output
('aSOETIHSIBSROG', '1PCS.', 'ecsrGIR')

【讨论】:

    【解决方案3】:

    这是一个规则处理器,它使用正则表达式来匹配您的两种情况。我创建了一个自定义匹配结果类来保存从输入字符串中提取的相关值。规则处理器连续尝试以下规则:

    • rule1 - 尝试匹配 {n} 后跟 pc、pc.、pcs 或 pcs 之一。
    • rule2 - 尝试匹配以“lot of”开头的 {n}
    • rule3 - 匹配 {n} 后跟 {text2}

    运行时,结果

    abc 23 PCS. def
    amount=23 qtype=PCS. text1="abc" text2="def" rule=1
    abc 23pc def
    amount=23 qtype=pc text1="abc" text2="def" rule=1
    abc 24pc.def
    amount=24 qtype=pc. text1="abc" text2="def" rule=1
    abc 24 pcs def
    amount=24 qtype=pcs text1="abc" text2="def" rule=1
    abc lot of 24 def
    amount=24 qtype=lot of text1="abc" text2="def" rule=2
    3 abcs
    amount=3 qtype=None text1="" text2="abcs" rule=3
    
    重新进口 班级比赛: def __init__(self, amount, qtype, text1, text2, rule): self.amount = int(数量) self.qtype = qtype self.text1 = text1 self.text2 = text2 self.rule = 规则 def __str__(self): return 'amount={} qtype={} text1="{}" text2="{}" rule={}'.format( self.amount, self.qtype, self.text1, self.text2, self.rule) #{n} 个人电脑个人电脑。个人电脑 def 规则 1(s): m = re.search("\s*(?P\d+)\s*(?PPCS?\.?)\s*", s, re.IGNORECASE) 如果米: 返回匹配(m.group('amount'),m.group('qtype'), text1=s[:m.start()], text2=s[m.end():], 规则=1) 返回无 #很多 {n} def 规则 2(s): m = re.search("\s*lot of\s*(?P\d+)\s*", s, re.IGNORECASE) 如果米: return Match(m.group('amount'), 'lot of', text1=s[:m.start()], text2=s[m.end():], 规则=2) 返回无 #{n} {文本2} def 规则 3(s): m = re.search("\s*(?P\d+)\s*",s) 如果米: 返回匹配(m.group('数量'),无, text1=s[:m.start()], text2=s[m.end():], 规则=3) 返回无 规则 = [规则 1,规则 2,规则 3] 定义进程: 对于规则中的规则: m = 规则 如果 m:返回 m 返回无 测试 = [ "abc 23 PCS. def", "abc 23pc def", "abc 24pc.def", "abc 24 pcs def", "abc 很多 24 def", “3个ABC” ] 对于测试中的 t: m = 进程(t) 打印(t) 打印(米)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-20
      • 1970-01-01
      • 2013-01-06
      • 1970-01-01
      • 2012-05-24
      • 1970-01-01
      • 2013-01-10
      • 1970-01-01
      相关资源
      最近更新 更多