Удаление ненужных / дубликатов скобок из арифметических выражений с помощью стека(стеков)
написать программу для поиска повторяющихся скобок в выражении. Например :
(( a + b ) + (( c + d ))) = a + b + c + d
(( a + b ) * (( c + d ))) = (a + b) * (c + d)
один подход, о котором я знаю, включает в себя следующие два шага:
- преобразуйте данное выражение infix в выражение postfix.
- преобразовать постфикс обратно в infix
Я не хочу делать весь этот процесс преобразования из одного представления в другое, а затем преобразовать его обратно.
Я хочу сделать это использование стека(стеков), но за один проход. Возможно ли это ?
прошу подсказать алгоритм или код.
5 ответов
можно использовать парсер рекурсивного спуска. Это использует стек вызова функции неявно, но не явно стек Java. Он может быть реализован следующим образом:
public class Main {
public static void main(String[] args) {
System.out.println(new Parser("(( a + b ) + (( c + d )))").parse());
System.out.println(new Parser("(( a + b ) * (( c + d )))").parse());
}
}
public class Parser {
private final static char EOF = ';';
private String input;
private int currPos;
public Parser(String input) {
this.input = input + EOF; // mark the end
this.currPos = -1;
}
public String parse() throws IllegalArgumentException {
nextToken();
Result result = expression();
if(currToken() != EOF) {
throw new IllegalArgumentException("Found unexpected character '" + currToken() + "' at position " + currPos);
}
return result.getText();
}
// "expression()" handles "term" or "term + term" or "term - term"
private Result expression() throws IllegalArgumentException {
Result leftArg = term();
char operator = currToken();
if (operator != '+' && operator != '-') {
return leftArg; // EXIT
}
nextToken();
Result rightArg = term();
if(operator == '-' && (rightArg.getOp() == '-' || rightArg.getOp() == '+')) {
rightArg = encloseInParentheses(rightArg);
}
return new Result(leftArg.getText() + " " + operator + " " + rightArg.getText(), operator);
}
// "term()" handles "factor" or "factor * factor" or "factor / factor"
private Result term() throws IllegalArgumentException {
Result leftArg = factor();
char operator = currToken();
if (operator != '*' && operator != '/') {
return leftArg; // EXIT
}
nextToken();
Result rightArg = factor();
if(leftArg.getOp() == '+' || leftArg.getOp() == '-') {
leftArg = encloseInParentheses(leftArg);
}
if(rightArg.getOp() == '+' || rightArg.getOp() == '-' || (operator == '/' && (rightArg.getOp() == '/' || rightArg.getOp() == '*'))) {
rightArg = encloseInParentheses(rightArg);
}
return new Result(leftArg.getText() + " " + operator + " " + rightArg.getText(), operator);
}
// "factor()" handles a "paren" or a "variable"
private Result factor() throws IllegalArgumentException {
Result result;
if(currToken() == '(') {
result = paren();
} else if(Character.isLetter(currToken())) {
result = variable();
} else {
throw new IllegalArgumentException("Expected variable or '(', found '" + currToken() + "' at position " + currPos);
}
return result;
}
// "paren()" handles an "expression" enclosed in parentheses
// Called with currToken an opening parenthesis
private Result paren() throws IllegalArgumentException {
nextToken();
Result result = expression();
if(currToken() != ')') {
throw new IllegalArgumentException("Expected ')', found '" + currToken() + "' at position " + currPos);
}
nextToken();
return result;
}
// "variable()" handles a variable
// Called with currToken a variable
private Result variable() throws IllegalArgumentException {
Result result = new Result(Character.toString(currToken()), ' ');
nextToken();
return result;
}
private char currToken() {
return input.charAt(currPos);
}
private void nextToken() {
if(currPos >= input.length() - 1) {
throw new IllegalArgumentException("Unexpected end of input");
}
do {
++currPos;
}
while(currToken() != EOF && currToken() == ' ');
}
private static Result encloseInParentheses(Result result) {
return new Result("(" + result.getText() + ")", result.getOp());
}
private static class Result {
private final String text;
private final char op;
private Result(String text, char op) {
this.text = text;
this.op = op;
}
public String getText() {
return text;
}
public char getOp() {
return op;
}
}
}
Если вы хотите использовать явный стек, вы можете преобразовать алгоритм из рекурсивного в итеративный, используя стек чего-то похожего на Result
внутренний класс.
Фактически, компилятор Java / JVM преобразует каждый рекурсивный алгоритм в стек, основанный на локальные переменные в стеке.
но рекурсивные приличные Парсеры легко читаются людьми, поэтому я предпочел бы решение, представленное выше.
Если вы только заботитесь о дублировать круглые скобки (как, по-видимому, подразумевает вопрос), а не те, которые считаются необходимыми из-за приоритета оператора (как, по-видимому, подразумевают другие ответы), вы действительно можете использовать стек, чтобы отслеживать, с какими скобками вы столкнулись, и решить, что любые символы без пробелов без скобок для каждой пары скобок имеет значение, что дает вам гораздо более простой итерационный обход с использованием стека:
public class BracketFinder {
public List<BracketPair> findUnnecessaryBrackets(String input) {
List<BracketPair> unneccessaryBrackets = new LinkedList<BracketPair>();
Deque<BracketPair> bracketStack = new LinkedBlockingDeque<BracketPair>();
for (int cursor = 0; cursor < input.length(); cursor++ ) {
if (input.charAt(cursor) == '(') {
BracketPair pair = new BracketPair(cursor);
bracketStack.addLast(pair);
} else if (input.charAt(cursor) == ')') {
BracketPair lastBracketPair = bracketStack.removeLast();
lastBracketPair.end = cursor;
if (!lastBracketPair.isNecessary) {
unneccessaryBrackets.add(lastBracketPair);
}
} else if (input.charAt(cursor) != ' ') {
if (!bracketStack.isEmpty()) {
bracketStack.getLast().isNecessary = true;
}
}
}
return unneccessaryBrackets;
}
class BracketPair {
public int start = -1;
public int end = -1;
public boolean isNecessary = false;
public BracketPair(int startIndex) {
this.start = startIndex;
}
}
}
что вы можете протестировать со следующим
public static void main(String... args) {
List<BracketPair> results = new BracketFinder().findUnnecessaryBrackets("(( a + b ) + (( c + d ))) = a + b + c + d");
for (BracketPair result : results) {
System.out.println("Unneccessary brackets at indices " + result.start + "," + result.end);
}
}
не программировал его, но он может выглядеть так:
дайте операции + / - значение 1 дайте операции * & / значение 2 дайте операцию ) (значение 2 (как его же, как *)
1 go to inner parenthesis and check if the next operation is higher in its value (means the parenthesis is necessary) or equal/lower to the own operation. if equal or lower the parenthesis is not necessary.
2 go to 1
вы закончили, когда нет никаких изменений между 2 шагами
надеюсь, что это помогло.. Если у вас есть решение, дайте мне знать, пожалуйста. Если это не помогло, дайте мне знать тоже:)
поздравления
это возможно за один проход. Идея состоит в том, чтобы искать предыдущую / следующую операцию вокруг каждого блока () и применять правила ассоциативности. Вот небольшая таблица с отметками да/нет, когда () необходимо.
// (a + b) + c NO
// (a + b) - c NO
// (a + b) / c YES
// (a + b) * c YES
// (a / b) + c NO
// (a / b) - c NO
// (a / b) / c NO
// (a / b) * c NO
// a + (b + c) NO
// a - (b + c) YES
// a / (b + c) YES
// a * (b + c) YES
// a + (b / c) NO
// a - (b / c) NO
// a / (b / c) YES
// a * (b / c) NO
// (a) ((a)) NO
вот код C++ (я не уверен, что его не хватает в некоторых случаях-это просто идея):
string clear(string expression)
{
std::stack<int> openers;
std::stack<int> closers;
std::stack<bool> isJustClosed;
std::stack<char> prevOperations;
std::stack<bool> isComposite;
std::stack<int> toDelete;
prevOperations.push(' ');
isJustClosed.push(false);
isComposite.push(false);
string result = expression + "@";
for (int i = 0; i < result.length(); i++)
{
char ch = result[i];
if ((ch == '*') || (ch == '/') || (ch == '+') || (ch == '-') || (ch == '(') || (ch == ')') || (ch == '@'))
if (isJustClosed.size() > 0)
if (isJustClosed.top() == true) {
// pop all and decide!
int opener = openers.top(); openers.pop();
int closer = closers.top(); closers.pop();
char prev = prevOperations.top(); prevOperations.pop();
char prevOperationBefore = prevOperations.top();
isJustClosed.pop(); //isJustClosed.push(false);
bool isComp = isComposite.top(); isComposite.pop();
bool ok = true;
if (prev == ' ')
ok = false;
else
{
ok = false;
if (((isComp) || (prev == '+') || (prev == '-')) && (ch == '/')) ok = true;
if (((isComp) || (prev == '+') || (prev == '-')) && (ch == '*')) ok = true;
if (((isComp) || (prev == '+') || (prev == '-')) && (prevOperationBefore == '-')) ok = true;
if (prevOperationBefore == '/') ok = true;
if (((isComp) || (prev == '+') || (prev == '-')) && (prevOperationBefore == '*')) ok = true;
}
if (!ok)
{
toDelete.push(opener);
toDelete.push(closer);
}
}
if (ch == '(') {
openers.push(i);
prevOperations.push(' ');
isJustClosed.push(false);
isComposite.push(false);
}
if (ch == ')') {
closers.push(i);
isJustClosed.top() = true;
}
if ((ch == '*') || (ch == '/') || (ch == '+') || (ch == '-')) {
if (!isComposite.top())
{
char prev = prevOperations.top();
if ((ch == '+') || (ch == '-'))
if ((prev == '*') || (prev == '/'))
isComposite.top() = true;
if ((ch == '*') || (ch == '/'))
if ((prev == '+') || (prev == '-'))
isComposite.top() = true;
}
prevOperations.top() = ch;
isJustClosed.top() = false;
}
}
while (toDelete.size() > 0)
{
int pos = toDelete.top();
toDelete.pop();
result[pos] = ' ';
}
result.erase(result.size() - 1, 1);
return result;
}
внутри каждого блока мы отслеживаем последнюю операцию, а также отслеживаем, является ли содержимое составным (a+b*c).
лично я думаю, что есть по крайней мере 2 способа:
- использование дерева
- используя арифметические польская нотация
дерево
дерево может быть создано из входного выражения. После того, как дерево создано, его можно сплющить без бесполезного скобки
польская нотация
-
(( a + b ) + (( c + d )))
станет(+ (+ a b) (+ c d))
-
(( a + b ) * (( c + d )))
станет(* (+ a b) (+ c d))
отсюда вы можете сравнить каждый операнд и факторы, чтобы увидеть, имеют ли они одинаковый приоритет при решении арифметического уравнение
Я бы пошел с деревом.