Как можно проанализировать фракционные числа с помощью пипарсинга?

StackOverflow https://stackoverflow.com/questions/3911824

Вопрос

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

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

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)))
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top