Its possible in one pass. The idea is to look for previous / next operation around each () block and apply associativity rules. Here is small table with yes/no marks when () is necessary.
// (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
Here is C++ code (im not sure if its not missing some cases - its just an idea):
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;
}
Inside each block we track last operation and also track if content is composite like (a+b*c).
Test:
void test()
{
LOG << clear("((a + (a + b))) - ((c)*(c) + d) * (b + d)") << NL;
LOG << clear("a + (a + b) - ((c) + d) * (b + d)") << NL;
LOG << clear("(a/b)*(c/d)") << NL;
LOG << clear("(a/b)*((((c)/d)))") << NL;
LOG << clear("((a + b) - (c - d))") << NL;
LOG << clear("((a + b)*((c - d)))+c/d*((a*b))") << NL;
LOG << clear("a+a*b*(a/b)") << NL;
LOG << clear("a+a*b*(a+b)") << NL;
}
Result:
a + a + b - ( c * c + d) * b + d
a + a + b - ( c + d) * b + d
a/b * c/d
a/b * c /d
a + b - (c - d)
(a + b)* c - d +c/d* a*b
a+a*b* a/b
a+a*b*(a+b)