A boolean expression is an expression that evaluates to either true
or false
. It can be in one of the following shapes:
't'
that evaluates to true
.'f'
that evaluates to false
.'!(subExpr)'
that evaluates to the logical NOT of the inner expression subExpr
.'&(subExpr1, subExpr2, ..., subExprn)'
that evaluates to the logical AND of the inner expressions subExpr1, subExpr2, ..., subExprn
where n >= 1
.'|(subExpr1, subExpr2, ..., subExprn)'
that evaluates to the logical OR of the inner expressions subExpr1, subExpr2, ..., subExprn
where n >= 1
.Given a string expression
that represents a boolean expression, return the evaluation of that expression.
It is guaranteed that the given expression is valid and follows the given rules.
Example 1:
Input: expression = "&(|(f))" Output: false Explanation: First, evaluate |(f) --> f. The expression is now "&(f)". Then, evaluate &(f) --> f. The expression is now "f". Finally, return false.
Example 2:
Input: expression = "|(f,f,f,t)" Output: true Explanation: The evaluation of (false OR false OR false OR true) is true.
Example 3:
Input: expression = "!(&(f,t))" Output: true Explanation: First, evaluate &(f,t) --> (false AND true) --> false --> f. The expression is now "!(f)". Then, evaluate !(f) --> NOT false --> true. We return true.
Constraints:
1 <= expression.length <= 2 * 104
'('
, ')'
, '&'
, '|'
, '!'
, 't'
, 'f'
, and ','
.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
When parsing a boolean expression the brute force way, we consider every possible combination of interpreting the expression. We evaluate each possibility to determine if it results in true or false. After evaluating all possible scenarios, we can determine the final result.
Here's how the algorithm would work step-by-step:
def parse_boolean_expression_brute_force(expression):
# This function exhaustively checks all variable assignments.
variables = set()
for char in expression:
if char.isalpha():
variables.add(char)
variable_list = list(variables)
number_of_variables = len(variable_list)
number_of_combinations = 2**number_of_variables
results = []
for i in range(number_of_combinations):
# Generate a truth assignment for each combination.
variable_values = {}
for j in range(number_of_variables):
if (i >> j) & 1:
variable_values[variable_list[j]] = True
else:
variable_values[variable_list[j]] = False
# Evaluate the expression with the current assignment.
result = evaluate_expression(expression, variable_values)
results.append(result)
return results
def evaluate_expression(expression, variable_values):
expression = expression.replace(' ', '')
def evaluate(sub_expression):
if sub_expression == 'true':
return True
if sub_expression == 'false':
return False
if sub_expression in variable_values:
return variable_values[sub_expression]
# Handle NOT operator
if sub_expression.startswith('!'):
operand = evaluate(sub_expression[1:])
return not operand
# Find the AND or OR operator with the lowest precedence.
and_index = -1
or_index = -1
parenthesis_level = 0
for index, char in enumerate(sub_expression):
if char == '(':
parenthesis_level += 1
elif char == ')':
parenthesis_level -= 1
elif char == '&' and parenthesis_level == 0:
and_index = index
elif char == '|' and parenthesis_level == 0:
or_index = index
# Evaluate AND operation.
if and_index != -1:
left_operand = evaluate(sub_expression[:and_index])
right_operand = evaluate(sub_expression[and_index + 1:])
return left_operand and right_operand
# Evaluate OR operation.
if or_index != -1:
left_operand = evaluate(sub_expression[:or_index])
right_operand = evaluate(sub_expression[or_index + 1:])
return left_operand or right_operand
# Handle parentheses.
if sub_expression.startswith('(') and sub_expression.endswith(')'):
return evaluate(sub_expression[1:-1])
return False # Should not reach here
return evaluate(expression)
The most efficient way to solve this problem is using a method that understands the order of operations and simplifies the expression bit by bit. This approach uses something called recursion which means solving a smaller version of the same problem until you get to the answer. By breaking down the complex expression into manageable parts, the answer becomes simple.
Here's how the algorithm would work step-by-step:
def parse_boolean_expression(expression):
def evaluate(sub_expression):
if '(' not in sub_expression:
# Base case: no more parentheses, evaluate directly
sub_expression = evaluate_not(sub_expression)
sub_expression = evaluate_and(sub_expression)
sub_expression = evaluate_or(sub_expression)
return sub_expression
start_index = sub_expression.rfind('(')
end_index = find_matching_paren(sub_expression, start_index)
inner_expression = sub_expression[start_index + 1:end_index]
result = evaluate(inner_expression)
return evaluate(sub_expression[:start_index] + result + sub_expression[end_index + 1:])
def find_matching_paren(expression, start_index):
count = 1
for i in range(start_index + 1, len(expression)):
if expression[i] == '(':
count += 1
elif expression[i] == ')':
count -= 1
if count == 0:
return i
return -1
def evaluate_not(expression):
index = 0
while index < len(expression):
if expression[index] == '!':
#Evaluate the NOT operation.
operand = expression[index + 1]
result = 'F' if operand == 'T' else 'T'
expression = expression[:index] + result + expression[index + 2:]
else:
index += 1
return expression
def evaluate_and(expression):
index = 0
while index < len(expression):
if expression[index] == '&':
# Evaluate AND operation from left to right.
left_operand = expression[index - 1]
right_operand = expression[index + 1]
result = 'T' if left_operand == 'T' and right_operand == 'T' else 'F'
expression = expression[:index - 1] + result + expression[index + 2:]
index -= 1 # Adjust index due to string replacement
else:
index += 1
return expression
def evaluate_or(expression):
index = 0
while index < len(expression):
if expression[index] == '|':
# Evaluate OR operation from left to right.
left_operand = expression[index - 1]
right_operand = expression[index + 1]
result = 'T' if left_operand == 'T' or right_operand == 'T' else 'F'
expression = expression[:index - 1] + result + expression[index + 2:]
index -= 1 # Adjust index due to string replacement
else:
index += 1
return expression
final_result = evaluate(expression)
return final_result == 'T'
Case | How to Handle |
---|---|
Empty or Null Input String | Return a default value (e.g., false, or throw an IllegalArgumentException) indicating an invalid expression. |
Input string with only whitespace | Trim the input string before parsing to handle whitespace-only expressions. |
Invalid characters in the expression (e.g., '%', '$') | Throw an exception (e.g., IllegalArgumentException) or return an error code indicating invalid syntax. |
Unbalanced parentheses (more opening or closing) | Detect and report unbalanced parentheses as a syntax error using a stack to track them. |
Nested parentheses with high depth | Ensure the solution handles deeply nested parentheses without causing stack overflow errors (consider iterative approach). |
Consecutive operators (e.g., 'true && && false') | Detect and report consecutive operators as a syntax error because that shouldnt occur in correct expressions. |
Missing operands for operators (e.g., 'true &&') | Detect missing operands and report as syntax errors or evaluate to a predefined value (e.g., false). |
Large input string exceeding memory limits | Implement input validation to prevent excessively large expressions from causing memory exhaustion or consider streaming input processing. |