Как разобрать строку, начинающуюся с конца С++

Добавлен еще один пример.

У меня есть математическое выражение, такое как cos(pi*cos(pi * sin(pi*y))), и я хочу его решить.

Я думаю, что лучший способ разобрать его — начать с конца строки.

Итак, в выражении выше:

  1. я = грех (пи * у)
  2. я = потому что (пи * я)
  3. я = потому что (пи * я)

Я собираюсь добавить еще одно выражение в качестве примера: cos(pi*(avg(x,x)*y))

Это должно оцениваться так:

  1. я = среднее (х, х)
  2. i = i*y
  3. я = потому что (пи * я)

Что вы думаете об этом? Не могли бы вы помочь мне реализовать код?

заранее спасибо


person Elena    schedule 17.12.2015    source источник
comment
Что вы имеете в виду под словом решить? Численно, символически?   -  person CroCo    schedule 17.12.2015
comment
Я думаю, вы должны использовать рекурсию для анализа такого выражения   -  person Olga Akhmetova    schedule 17.12.2015
comment
Под решением я имею в виду получить целое число, которое является результатом оценки, я должен присвоить переменным x и y значение, я знаю.   -  person Elena    schedule 17.12.2015
comment
Почему бы не разобрать строку от начала до конца, чтобы составить список необходимых вычислений, а затем обработать список от конца к началу (или просто создать стек вместо списка)?   -  person shapiro yaacov    schedule 17.12.2015
comment
@OlgaAkhmetova да, я думаю, как и вы, но я не знаю, как начать разбор с конца строки.   -  person Elena    schedule 17.12.2015
comment
Я думаю, что @OlgaAkhmetova имеет в виду наличие рекурсивной функции, которая анализирует требуемое действие, дает в качестве входных данных остальную часть строки и возвращает результат int. Затем выполните свое действие и вернитесь   -  person shapiro yaacov    schedule 17.12.2015
comment
@Elena В случае сомнений переверните строку в памяти (для этого есть простые алгоритмы) и начните анализировать с начала перевернутой строки. Но я не уверен, является ли направление вашей реальной проблемой.   -  person Jonas Schäfer    schedule 17.12.2015
comment
@Елена, почему ты хочешь начать с конца? Это какое-то требование?   -  person Olga Akhmetova    schedule 17.12.2015
comment
@JonasWielicki Как вы думаете, в чем проблема?   -  person Elena    schedule 17.12.2015
comment
@OlgaAkhmetova нет, это не требование. Я думал, что это будет проще.   -  person Elena    schedule 17.12.2015
comment
@Елена, у тебя есть правила относительно строк? Хотя это не тривиальная работа.   -  person CroCo    schedule 17.12.2015
comment
@CroCo У меня нет никаких правил.   -  person Elena    schedule 17.12.2015
comment
@Elena Елена, я не уверен, чего вы хотите добиться, эффективно меняя строку. Для таких выражений, как cos(pi*atan(y)) + sin(pi*y), я не вижу преимущества в переворачивании строки. (Хотя в вашем исходном примере, по общему признанию, есть преимущество, потому что в перевернутой строке операции оказываются в том порядке, в котором они необходимы, и, выполняя их в этом порядке, вам нужна только одна временная переменная.)   -  person Jonas Schäfer    schedule 17.12.2015
comment
@Elena, если вы ожидаете, что пользователь будет вводить случайные строки, у вас большие проблемы, поскольку cos(pi) можно записать как cos[pi], или cos(Pi), или cos(PI), или cos(3.14). Моя точка зрения состоит в том, чтобы, по крайней мере, начать с некоторых правил.   -  person CroCo    schedule 17.12.2015
comment
@CroCo выражение исходит от генератора случайных чисел, поэтому существует только одно возможное обозначение.   -  person Elena    schedule 17.12.2015


Ответы (3)


У меня есть математическое выражение, такое как cos(pi*cos(pi * sin(pi*y))), и я хочу его решить.

Нет, вы хотите оценить его. Решение говорит вам об условиях, при которых что-то верно. Оценка этого просто дает вам результирующее значение.

Я думаю, что лучший способ разобрать его — начать с конца строки.

Традиционный способ разбора подобных выражений — использование рекурсивного спуска. Он более общий и его гораздо проще реализовать. Поток управления выглядит примерно так:

  • cos ( A ...

    • где A = pi * cos ( B ...

      • где B = pi * sin ( C ...

        • где C = pi * y

          теперь вы можете вычислить pi * y и вернуть значение C

        ... и теперь у вас есть C, вы можете оценить pi * sin(C) и вернуть значение B

      ... и теперь у вас есть значение B, вы можете оценить pi * cos(B), возвращая значение как A

    ... и теперь у вас есть значение A, вы можете оценить cos(A), и все готово.

Точно так же работает выражение C cos(M_PI * cos(M_PI * sin(M_PI * y))) (предполагая, что π является обычной, но нестандартной константой).

Он вычисляется примерно справа налево (фактически изнутри наружу), но по-прежнему читается слева направо. Мы только что пометили временные значения для ясности.

Этот поток управления часто просто превращается в дерево, подобное

[cos of _]
        |
    [pi * _]
          |
     [cos of _]
             |
         [pi * _]
               |
          [sin of _]
                  |
              [pi * y]

Но, очевидно, вы можете просто оценить результат один раз, если вам не нужно сохранить дерево на потом. (Обратите внимание, что это кривое дерево по-прежнему остается деревом, оно просто вырождается из-за того, как вложено ваше выражение).

...Что вы думаете об этом?

Проблема с вашим решением заключается в том, что оно ломается для разных структур вложенности, например.

cos( sin((pi * x) + y) + sin(y + (pi * x)) )

нельзя просто оценивать справа налево.

Не могли бы вы помочь мне реализовать код?

Отделите обработку строк (токенизацию) от синтаксического анализа и оценки. Гораздо проще рассуждать независимо о вашей обработке строк и вашей математике.

person Useless    schedule 17.12.2015
comment
Поскольку у ОП, похоже, очень ограниченные знания о синтаксических анализаторах в целом, было бы интересно указать на доступные генераторы синтаксических анализаторов. Я использовал antlr для Java, и все было в порядке. Я попробовал Boost.Spirit однажды (несколько лет назад), но сообщения об ошибках заставили меня прекратить его использовать. Есть ли у вас предложения по C++? - person Jens; 17.12.2015
comment
Честно говоря, для таких простых выражений кажется, что их реализация напрямую - это путь - по крайней мере, в качестве учебного упражнения. Системы лексера/парсера, которые я использовал ранее, вероятно, имеют более сложную кривую обучения, чем C++, необходимый для этого, и ошибки и документацию, вероятно, труднее понять, если вы не знаете основ. Если у кого-то есть предложение для начинающих, оно будет хорошо принято. - person Useless; 17.12.2015
comment
@ Бесполезно, спасибо за ваш ответ, но у меня все еще есть некоторые сомнения по этому поводу. Не могли бы вы помочь мне, хотя бы немного, реализовать код? Большое спасибо. - person Elena; 17.12.2015
comment
Начните с написания токенизатора: ваша строка должна быть преобразована в последовательность, подобную ["cos", "(", "pi", "*", "sin", ...]. Задайте специальный вопрос, если у вас есть проблемы с этим, это достаточно большой шаг сам по себе. Затем напишите синтаксический анализатор/анализатор, работающий с этой последовательностью. Он должен распознавать cos как функцию одного аргумента, * как оператор инфиксного умножения, ( как начало вложенного подвыражения и т. д. и т. д. Ключ, как всегда, состоит в том, чтобы разбить проблему на более мелкие проблемы и решить их. их по отдельности. - person Useless; 17.12.2015
comment
@Useless, теперь у меня есть токенизация, но я не знаю, как продолжить. - person Elena; 18.12.2015
comment
Хорошая работа! Теперь у вас есть только несколько возможностей для решения: например. cos означает оценку следующего подвыражения и применение cos к результату ( означает рекурсивную оценку вложенного подвыражения и т. д. Однако это может лучше подходить для нового вопроса, показывая ввод (который является результатом, который вы получили от ваш токенизатор) и желаемый результат. - person Useless; 18.12.2015

ОТРЕДАКТИРОВАНО: улучшение базового случая.

Это просто какой-то псевдокод, но идея должна работать:

int ParseAndCalculate(string input)
{
    if (input.DoesConvertToSomeIntegerWork())
        return input.ConvertToSomeInteger();

    string actionRequired = GetActionFromString(input, "(");
    int tempIndex = input.LocationOfFirst("(");
    int tempResult = ParseAndCalculate(input, tempIndex, input.Length -1);
    tempResult = PerformRequiredAction(tempResult, actionRequired);

    return tempResult;
}

Не уверен, что вы должны вернуть в базовом случае. Может быть, выяснить, что там на самом деле стоит?

person shapiro yaacov    schedule 17.12.2015
comment
Извините, что говорю это, но я не понимаю, как этот псевдокод будет направлять ОП. Почему функция возвращает int? Без правил очень сложно придумать хороший псевдоним. - person CroCo; 17.12.2015
comment
Ну, @CroCo, это дает общее представление о том, как двигаться дальше. Да, может потребоваться вернуть long, double или даже string. Да, синтаксис похож на C#, и да, я предположил, что разделителями являются ( и ). Но поскольку большинство этих проблем можно решить в других подфункциях, идея остается прежней. Отсюда Псевдокод... - person shapiro yaacov; 17.12.2015

Если вы хотите написать программу на C/C++, которая может анализировать математические выражения, подобные приведенным выше, вы можете сами написать нисходящий синтаксический анализатор на C/C++ или посмотреть на GNU Bison.

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

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

Его также можно использовать для вычисления выражений в «правильном» порядке, поскольку он поддерживает определения приоритета операторов.

person stj    schedule 17.12.2015