Как можно проанализировать выражения с дробными числами, используя pyparsing? - PullRequest
5 голосов
/ 12 октября 2010

Мы только начали разбирать шины, и нам это пока нравится, но мы не смогли заставить его разобрать строки с дробными числами, чтобы превратить их в числовые типы данных.

Например, если значение столбца в таблице базы данных содержало строку:

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 ]

6 голосов
/ 12 октября 2010

Поскольку вы цитируете некоторые тесты, похоже, что вы хотя бы попытались решить проблему. Я предполагаю, что вы уже определили одно число, которое может быть целым или действительным - не имеет значения, вы все равно конвертируете все в плавающее число - и часть из двух чисел, возможно, что-то вроде этого:

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 выполняет только то, что вам говорят, иначе это просто прямой анализ слева направо.

3 голосов
/ 12 октября 2010

Не совсем то, что вы ищете, но ...

>>> import fractions
>>> txt= "1 1/2"
>>> sum( map( fractions.Fraction, txt.split() ) )
Fraction(3, 2)
>>> float(_)
1.5
2 голосов
/ 12 октября 2010

Этот рецепт может быть полезным:

Посмотрите вокруг строки 39:

mixed = Combine(numeral + fraction, adjacent=False, joinString=' ')
1 голос
/ 12 октября 2010

Это что-то вроде двойника с С. Лоттом, но все равно вот оно:

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)))
...