如何使用pyparsing解析分数表达式?

时间:2010-10-12 05:02:20

标签: python parsing dsl pyparsing fractions

到目前为止,我们刚刚开始尝试使用轮胎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 个答案:

答案 0 :(得分:6)

由于你引用了一些测试,听起来你至少已经对这个问题进行了尝试。我假设你已经定义了一个数字,它可以是整数或实数 - 无关紧要,你将所有东西都转换成浮点数 - 只有两个数字的一​​小部分,可能是这样的:

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是从左到右,它不会像regexen那样进行同样的回溯。所以这个表达不会那么好用:

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只执行你告诉它的前瞻,否则它只是直接从左到右的解析。

答案 1 :(得分:3)

不完全是你想要的,但是......

>>> import fractions
>>> txt= "1 1/2"
>>> sum( map( fractions.Fraction, txt.split() ) )
Fraction(3, 2)
>>> float(_)
1.5

答案 2 :(得分:2)

这个食谱可能会有所帮助:

环顾第39行:

mixed = Combine(numeral + fraction, adjacent=False, joinString=' ')

答案 3 :(得分:1)

对于S. Lott来说这是一个双重的,但无论如何它仍然存在:

from fractions import Fraction
print sum(Fraction(part) for part in '1 1/2'.split())

处理浮动'整数'会很复杂,但是:

from fractions import Fraction
clean = '1.0 1.0/2.0'.replace('.0 ',' ').replace('.0/', '/').rstrip('0.').split()
print(clean)
print(sum(Fraction(part) for part in clean))

和其他海报的例子一样,加上一个带/用空格的例子:

from fractions import Fraction

tests = """\
1
1.0
1/2
1.0/2.0
1 1/2
1.0 1/2
1.0 1.0/2.0
1.0 1.0 / 2.0
""".splitlines()

for t in tests:
    clean = t.replace('.0 ',' ').replace('.0/', '/').rstrip('0.').split()
    value = sum(Fraction(part) for part in clean)
    print('%s -> %s, %s = %f' % (t, clean, value, float(value)))