如何使用pyparsing解析分数表达式?
我们刚开始尝试使用pyparsing这个工具,目前感觉还不错,但我们一直没能让它帮我们解析分数形式的数字字符串,把它们转换成数字类型。
举个例子,如果数据库表中的某个列值是这样的字符串:
1 1/2
我们希望能把它转换成Python中的数字形式:
1.5
我们想要一个解析器,它不在乎分数中的数字是整数还是小数。比如说:
1.0 1.0/2.0
...也应该能转换成:
1.5
简单来说,我们希望解析器能做到以下几点:
"1 1/2" = 1 + 0.5 = 1.5
下面的示例代码似乎离我们想要的结果很近...
http://pyparsing.wikispaces.com/file/view/parsePythonValue.py
...但还是不够接近,无法取得进展。我们所有的测试都发现,处理分数的功能只返回了表达式的第一部分(1)。有没有什么建议?提示?及时的智慧? :)
4 个回答
这个方法可能会对你有帮助:
可以看看大约在第39行的内容:
mixed = Combine(numeral + fraction, adjacent=False, joinString=' ')
这可能不是你想要的答案,但...
>>> import fractions
>>> txt= "1 1/2"
>>> sum( map( fractions.Fraction, txt.split() ) )
Fraction(3, 2)
>>> float(_)
1.5
既然你提到了一些测试,说明你至少对这个问题有了一些尝试。我猜你已经定义了一个单一的数字,这个数字可以是整数也可以是小数——反正你最后都会把它们转换成浮点数——还有一个由两个数字组成的分数,可能像这样:
from pyparsing import Regex, Optional
number = Regex(r"\d+(\.\d*)?").setParseAction(lambda t: float(t[0]))
fraction = number("numerator") + "/" + number("denominator")
fraction.setParseAction(lambda t: t.numerator / t.denominator)
(注意这里使用了解析动作,它们在解析时就进行浮点转换和分数计算。我更喜欢在解析的时候就处理这些,因为我知道某个东西是数字、分数或其他,而不是等到后面再去处理一堆零散的字符串,试图重建解析器已经完成的识别逻辑。)
这里是我为你的问题准备的测试用例,包含一个整数、一个分数,以及一个整数和分数的组合,使用了整数和小数:
tests = """\
1
1.0
1/2
1.0/2.0
1 1/2
1.0 1/2
1.0 1.0/2.0""".splitlines()
for t in tests:
print t, fractExpr.parseString(t)
最后一步是如何定义一个分数表达式,它可以是一个单一的数字、一个分数,或者一个单一的数字和一个分数。
由于pyparsing是从左到右解析的,它不会像正则表达式那样进行回溯。所以这个表达式可能效果不好:
fractExpr = Optional(number) + Optional(fraction)
为了将可能来自数字和分数部分的数值相加,添加这个解析动作:
fractExpr.setParseAction(lambda t: sum(t))
我们的测试输出:
1 [1.0]
1.0 [1.0]
1/2 [1.0]
1.0/2.0 [1.0]
1 1/2 [1.5]
1.0 1/2 [1.5]
1.0 1.0/2.0 [1.5]
对于测试用例1/2
,它只包含一个分数,前面的分子匹配了Optional(number)
这个部分,但这让我们只剩下"/2",而这并不匹配Optional(fraction)
——幸运的是,由于第二个部分是可选的,这个“通过”了,但它实际上并没有做到我们想要的。
我们需要让fractExpr更聪明一点,先检查是否有单独的分数,因为单独的数字和分数的前面分子之间可能会产生混淆。最简单的方法是让fractExpr这样读取:
fractExpr = fraction | number + Optional(fraction)
现在有了这个改变,我们的测试结果更好了:
1 [1.0]
1.0 [1.0]
1/2 [0.5]
1.0/2.0 [0.5]
1 1/2 [1.5]
1.0 1/2 [1.5]
1.0 1.0/2.0 [1.5]
在使用pyparsing时,有几个经典的陷阱,而这就是其中之一。只要记住,pyparsing只会执行你告诉它的前瞻,否则它就是简单的从左到右解析。