Как можно проанализировать фракционные числа с помощью пипарсинга?
Вопрос
Мы только начали пинать шины пипарс и нравились до сих пор, но мы не смогли заставить его помочь нам разобрать фракционные номера строки, чтобы превратить их в числовые типы данных.
Например, если значение столбца в таблице базы данных содержало строку:
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). Советы? Подсказки? Своевременная мудрость? :)
Решение
Поскольку вы цитируете некоторые тесты, похоже, что вы, по крайней мере, сделали удар по проблеме. Я предполагаю, что вы уже определили один номер, который может быть целочисленным или реальным - не имеет значения, вы все равно преобразуете все в плавание - и часть из двух чисел, вероятно, что -то вроде этого:
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)
Последним шагом является то, как определить фракционное выражение, которое может быть одним числом, дробью или одним числом и дробкой.
Поскольку пипарсинг остается правой, оно не делает такой же, как и REGEXEN. Так что это выражение не будет работать так хорошо:
fractExpr = Optional(number) + Optional(fraction)
Подводя итог вместе численные значения, которые могут исходить из числа и части фракции, добавьте это действие PANSE:
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]
Есть несколько классических ловушек с пипарсом, и это один из них. Просто помните, что пипарс, только то, что вы говорите, иначе это просто прямолинейный анализ.
Другие советы
Не то, что вы ищете, но ...
>>> import fractions
>>> txt= "1 1/2"
>>> sum( map( fractions.Fraction, txt.split() ) )
Fraction(3, 2)
>>> float(_)
1.5
Этот рецепт может быть полезен:
Осмотрите строку 39:
mixed = Combine(numeral + fraction, adjacent=False, joinString=' ')
Это вроде вдвое с С. Лоттом, но вот в любом случае:
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)))