2010-10-12 29 views
4

我们刚刚开始踢轮胎pyparsing,并喜欢它,但我们一直无法得到它来帮助我们解析分数字符串以将它们转换为数字数据类型。如何使用pyparsing解析小数表达式?

例如,如果在数据库表中的列值包含字符串:

1 1/2

我们想一些方法将其转换成数字相当于蟒:

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)。提示?提示?及时的智慧? :)

回答

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) 

(注意使用解析动作,也做浮点转换和分数划分权在分析时的,我更喜欢这样做,而解析,当我知道东西是不是数字或分数或什么的,而不是稍后再回来并通过了一堆零散的字符串筛选,试图重新解析器已经做好了识别逻辑。)

下面是测试情况下,我的COM提出您的问题,由一个整数,分数和整数和分数,同时使用整数和实数:

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”,W HICH 匹配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只会做你告诉它的前瞻,否则它只是直接从左到右的解析。

+0

真棒,很好的答案,谢谢你花时间拼出来!我们得到了测试用例:1/2 [1.0],并且为什么我们一直得到1而不是0.5。它看起来像你勾画了我们的绊脚石。数据本身非常混乱,但看起来这至少可以为我们打下坚实​​的基础,并列举其他最常见的客户产品属性值表达式。 :) – Xavian 2010-10-12 19:28:29

2

这个配方可能会有所帮助:

看看周围39行:

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

感谢这个,这是一个有趣的食谱,可以帮助我们进行类似的分析问题。 (产品属性的实体检测“12伏直流电机”)不幸的是,当我们尝试使用这个代码时,它会抛出一个错误。在我们修正了错误之后,它似乎并没有像预期的那样工作,但我们将继续关注它,因为它是我们正在研究的类似问题如何使用pyparsing的一个例子。 :) 谢谢! – Xavian 2010-10-12 19:07:06

3

不正是你要找的内容,但是...

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

哇,这真的很好,很优雅,我不敢相信我们忽略了这一点。 :)不幸的是,我们正在处理的源数据非常混乱并且很难处理,所以如此紧张的事情不太可能奏效。有时我们会看到诸如“1 1/2”或“〜1 1/2”之类的东西,或者疯狂地称之为“1 1/8〜2 7/8”。我们需要一个基本的分析器来开始,然后重构覆盖我们应对的遗留数据中最常见的情况。 – Xavian 2010-10-12 19:13:41

1

这是一种与S.洛特一倍,但这里是它无论如何:

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))) 
+0

超级简洁。:)如果只有我们的数据更清洁,我们才能够使用这种方法。 :) – Xavian 2010-10-12 19:25:21

+0

+1为您考虑意想不到的地方的空白。 Pyparsing确实也隐含了空白跳过,因此您添加的测试可以与上面的pyparsing解析器一起工作,而无需其他更改。 – PaulMcG 2010-11-21 15:50:15