Разбор строки заданного формата для построения бинарного дерева решений - PullRequest
0 голосов
/ 23 мая 2010

Я пытаюсь разобрать строку с круглыми скобками вида ((Question)(Left_Node)(right_node)).

Вопрос, например, будет иметь вид «если размер сегмента <1,5, то выберите левый узел, в противном случае правый». Вопрос может быть словарём с ключом и значением. Левый и правый узлы представляют собой полное левое или правое половинное дерево, которое будет проходить рекурсивно, пока не будет достигнут конечный узел. Таким образом, мы построим двоичное дерево решений. </p>

Ответы [ 2 ]

0 голосов
/ 05 марта 2011

Этот тип синтаксиса действительно является целью pyparsing. Базовый формат достаточно прост, а в pyparsing он выглядит так:

decision = '(' + '('+question+')' + '('+action+')' + '('+action+')' + ')'

Но как только вы начинаете добавлять арифметические выражения, а также логические операции и поддержку операторов 'и' и 'или', все становится сложнее. Кроме того, это рекурсивная грамматика, поскольку само действие может быть вложенным решением.

Pyparsing имеет встроенную поддержку, которая упрощает определение арифметических и логических выражений, включая приоритет операций и группировку в скобках, а также рекурсивные выражения. Вот грамматика pyparsing в различных частях. Сначала приведем некоторые основные элементы синтаксического анализа:

LPAR,RPAR = map(Suppress,"()")
number = Regex(r"[+-]?\d+(\.\d*)?").setParseAction(lambda tokens:float(tokens[0]))
varname = Word(alphas+'_', alphanums+'_')

Действие разбора, приложенное к числовому выражению, автоматически преобразует проанализированное число в значение с плавающей точкой во время разбора. Класс Word принимает две строки: строку, содержащую все допустимые начальные символы, и строку всех допустимых символов тела. Это определение varname поддерживает имена переменных, похожие на идентификаторы Python.

Pyparsing имеет метод operatorPrecedence, который принимает выражение для определения базового операнда, и список кортежей для определения каждого уровня операторов: выражение для операторов, целое число, является ли оператор унарным, двоичным или троичным и будь то левый или правый ассоциативный. operatorPrecedence заботится о рекурсивном определении арифметических выражений, вложенных в скобки. Это выражение определяет основную математику с 4 функциями:

operand = number | varname
arithExpr = operatorPrecedence(operand,
    [
    (oneOf("+ -"), 1, opAssoc.RIGHT),
    (oneOf("* /"), 2, opAssoc.LEFT),
    (oneOf("+ -"), 2, opAssoc.LEFT),
    ])

Теперь вот определение логического условия (которое мы в конечном итоге будем использовать для определения вопроса о решении):

TRUE = CaselessKeyword("TRUE") | Keyword("T")
FALSE = CaselessKeyword("FALSE") | Keyword("F")
comparisonOp = oneOf("< > <= >= = !=")
boolLiteral = TRUE | FALSE
arithComparison = arithExpr + comparisonOp + arithExpr
boolOperand = boolLiteral | arithComparison
booleanExpr = operatorPrecedence(boolOperand,
    [
    ('not', 1, opAssoc.RIGHT),
    ('and', 2, opAssoc.LEFT),
    ('or', 2, opAssoc.LEFT),
    ])

Ваше определение действий было немного схематичным, поэтому я составил несколько возможных утверждений: оператор присваивания, оператор print, и, поскольку это речевое приложение, оператор say, который очень похож на print.

rhs = booleanExpr | arithExpr
assignment = varname("assign_var") + '=' + Group(rhs)("assign_value")
print_cmd = Keyword("print") + Group(delimitedList(rhs  | quotedString))
say_cmd = Keyword("say") + Group(delimitedList(rhs | quotedString))
cmd = print_cmd | say_cmd | assignment

В дополнение к определениям выражений вы заметите, что за некоторыми выражениями следует строка в кавычках, как будто выражение является функцией, вызываемой с этой строкой. Фактически, этот «вызов» фактически возвращает копию выражения, и соответствующие токены помечаются этим именем. Имена этих результатов очень полезны после разбора при выделении отдельных подходящих элементов (аналогично именованным группам в регулярных выражениях).

Наконец, чтобы объединить эти части в ваше выражение решения, вот выражения вопроса и действия:

IF = CaselessKeyword("IF")
question = LPAR + IF + Group(booleanExpr)("condition") + RPAR
action = LPAR + cmd + RPAR | Group(decision)

Обратите внимание, что определение действия может включать решение, но действие используется для определения решения. Чтобы разорвать эту зависимость от курицы и яйца, в этом разделе мы предваряем определение decision, но с выражением-заполнителем с использованием класса pyparsing Forward:

decision = Forward()

Затем после определения question и action мы используем оператор «<<» для «вставки» фактического определения выражения в существующую переменную <code>decision:

decision << ( LPAR +
                question +
                Group(action)("ifAction") +
                Optional(Group(action)("elseAction")) +
              RPAR)

Опять же, я взял на себя смелость с вашим определенным форматом, думая, что необязательный параметр else может быть полезен. Если вы не хотите, чтобы это было необязательным, просто удалите оболочку Optional вокруг Group(action)("elseAction").

Это определяет грамматику, теперь вот несколько тестов. Использование функции dump () для результатов, возвращаемых parseString, является хорошим способом распечатать токены и любые связанные с ними имена.

tests = """\
    ((if TRUE)(a=99))

    ((if TRUE)(a = (a=99)))

    ((if a<100)(a = a + 1))

    ((if a<100)(a = a + 1)(a = a-1))

    (
     (if a<100)
     (print a, "is too small") 
     ( (if a>100) (print a,"is too big") (print a, "is just right") )
    )

    (
     (if a<100)
     (say a, "is too small!") 
     ( (if a>100) (say a,"is too big!") (say a, "is just right!") )
    )
    """

for d in decision.searchString(tests):
    print d.dump()
    print

Вот вывод:

['IF', ['TRUE'], ['a', '=', [99.0]]]
- condition: ['TRUE']
- ifAction: ['a', '=', [99.0]]
  - assign_value: [99.0]
  - assign_var: a

['IF', ['TRUE'], ['a', '=', ['a', '=', 99.0]]]
- condition: ['TRUE']
- ifAction: ['a', '=', ['a', '=', 99.0]]
  - assign_value: ['a', '=', 99.0]
  - assign_var: a

['IF', ['a', '<', 100.0], ['a', '=', [['a', '+', 1.0]]]]
- condition: ['a', '<', 100.0]
- ifAction: ['a', '=', [['a', '+', 1.0]]]
  - assign_value: [['a', '+', 1.0]]
  - assign_var: a

['IF', ['a', '<', 100.0], ['a', '=', [['a', '+', 1.0]]], 
    ['a', '=', [['a', '-', 1.0]]]]
- condition: ['a', '<', 100.0]
- elseAction: ['a', '=', [['a', '-', 1.0]]]
  - assign_value: [['a', '-', 1.0]]
  - assign_var: a
- ifAction: ['a', '=', [['a', '+', 1.0]]]
  - assign_value: [['a', '+', 1.0]]
  - assign_var: a

['IF', ['a', '<', 100.0], ['print', ['a', '"is too small"']], 
    [['IF', ['a', '>', 100.0], ['print', ['a', '"is too big"']], 
    ['print', ['a', '"is just right"']]]]]
- condition: ['a', '<', 100.0]
- elseAction: [['IF', ['a', '>', 100.0], ['print', ['a', '"is too big"']], 
                ['print', ['a', '"is just right"']]]]
- ifAction: ['print', ['a', '"is too small"']]

['IF', ['a', '<', 100.0], ['say', ['a', '"is too small!"']], 
    [['IF', ['a', '>', 100.0], ['say', ['a', '"is too big!"']], 
        ['say', ['a', '"is just right!"']]]]]
- condition: ['a', '<', 100.0]
- elseAction: [['IF', ['a', '>', 100.0], ['say', ['a', '"is too big!"']], 
                ['say', ['a', '"is just right!"']]]]
- ifAction: ['say', ['a', '"is too small!"']]

Вот ссылка на полную программу разбора - http://pastebin.com/DnaNrx7j.

Это только первый этап, анализирующий ввод. Следующим шагом является оценка выражения путем обработки возвращенных токенов. Пример pyparsing wiki SimpleBool.py (http://pyparsing.wikispaces.com/file/view/simpleBool.py) включает в себя пример присоединения действий разбора для преобразования проанализированных токенов в экземпляры вызываемого класса, которые упрощают оценку результатов.

Надеюсь, это поможет.

0 голосов
/ 24 мая 2010

Если вы можете принять решение о деталях формата ввода, то используйте необработанный исходный код Python. Например, вы можете хранить свое дерево в словаре узлов Python:

tree = {
    "root": ("a", "b")
    "a": ("c", "d"),
    "b": ("e", "f"),
    "c": (None, None), #No children, a leaf.
    "d": (None, None),
    "e": (None, None),
    "f": (None, None),
}

Теперь вы можете анализировать это дерево просто с помощью анализатора python.

from tree_data import tree #We're done parsing!

root_node = tree["root"]
left_child = tree[root_node[0]]

Ну, если формат ввода уже указан, то мы не сможем вам помочь, если вы не предоставите информацию об этом формате.

...