Boolean expressions are at the heart of a lot of code controlling what happens when. And sometimes they can get a bit complex. In order to keep from getting overwhelmed, itβs useful to be able to manipulate and simplify boolean expressions.
Also the AP exam almost always includes some questions that depend on your ability to determine whether two boolean expressions are actually equivalent, similar to being able to determine that two mathematical expressions such as \(a(b + c)\) and \(ab + ac\) are equivalent.
As with the arithmetic operators, the two logical operators && and || each apply to only two operands and ! only applies to one. But we can build compound expressions where the operands are other expressions using either other logical operators or comparison operators. We saw some examples of this in the previous section.
Once we start writing compound expressions itβs important to know what the precedence of the different operators are and our middle-school PEMDAS is no help. In Java, ! has higher precedence than &&, and && is higher than ||. But parentheses still have the highest precedence of all so we can use them to group things the way we want them to be evaluated. For example !a && b is equivalent to (!a) && b. If instead we want to negate the value produced by the and expression we can write: !(a && b).
Also all the logical operators are lower precedence than the comparison operators which is why we can write 0 <= x && x < 100 rather than having to write: (0 <= x) && (x < 100).
Once we start writing complex compund boolean expressions things can get out of hand. Sometimes this happens organically as we write code because we add subexpressions to an expression as we realize different cases. βI want to run this code when the score is greater than one-hundred. No wait, greater than one-hundred and bonus points are greater than ten. Well, that but also when the player has at least one power up and more than ten bonus points.β Next thing you know weβve got this (with parentheses added for clarity):
Thatβs maybe not terrible but itβs not trivial either. When we look at that expression how sure are we that itβs capturing exactly the cases we care about? Luckily there are some techniques for simplifying an expression like that into something that might be easier to understand.
For instance, in this case we can notice that in both sides of the || the condition is something && bonusPoints > 10. Since the whole compound expression can only be true if one side or the other is true but whichever side it is, bonusPoints > 10 will have to be true. So that means we can factor it out similar to how we can factor \(a\) out of \(ab + ac\) to get \(a(b + c)\text{.}\) That gives us this:
This time in order for the whole expression to be true both of the operands to the &&, i.e. the two || expressions have to be true. Since they both contain bonusPoints > 10, if that subexpression is true they will each be true and therefore the whole thing will be true. So we can factor it out like this:
But now thereβs an even bigger payoff. Look carefully at the second expression. Itβs true when the score is both greater and less than one hundred. Which can never happen so that subexpression will always be false. So we can simplify the larger expression to:
One of the most powerful tools for manipulating boolean expressions are De Morganβs Laws. Developed by Augustus De Morgan in the 1800s, they show how to simplify the negation of && and || expressions, i.e. anything in either of these two forms: !(a && b) or !(a || b)
You can probably convince yourself that these are valid with an actual example. Imagine, for instance, that you are trying to express whether you want to go to the movies. You wonβt go if youβre broke or if youβre too tired but otherwise you will.
So one way to say whether youβll go is to say, youβll go if youβre not broke or tired. Or in Java: !(broke || tired). (In English we hear βbroke or tiredβ as grouped together and both affected by the βnotβ.)
But another way to say it is that you will go to the movies if you are not broke and youβre not tired. Or in Java: !broke && !tired. Thus !(broke || tired) and !broke && !tired are two different ways of saying the same thing.
By themselves De Morganβs laws donβt usually lead to big simplifications but by giving us a way to flip && to || and vice versa, they can be a useful step toward rearranging a complex expression into a form where we can apply other rules to simplify it.
And theyβre really great for answering AP exam questions that give you one boolean expression and ask you to pick out which of several others is equivalent to it.
We can also simplify negated boolean expressions involving comparison operators like <, >, and ==. And we can negate a comparison expression by flipping the comparison operator. For instance !(a == b) is flipped to a != b. The relational operators can be flipped either by swapping the operands (a < b becomes b < a) or by flipping the operator. But we need to remember which are the actual flipped operators. For < it is not > but >= and likewise for > and <=. In other words, to flip a comparison operator change the < or > to the other symbol and add an = if there isnβt one or remove it if there is.
The other main technique for analyzing boolean expressions is called a truth table. Truth tables are a systematic way of analyzing the boolean expressions for all possible combinations values for the subexpressions.
For simple expressions a truth table can be used to spell out exactly what the values of that expression are. For instance, here are truth tables that show, in case you forgot, how &&, ||, and ! work.
But we can also build truth tables for more complex expressions and if we build tables for two different expressions we can then compare the tables to determine whether the expressions are equivalent.
Building truth tables is a brute force approach which can be more tedious than doing a bit of Boolean algebra but doesnβt rely on any algebraic insight to figure out which rules to apply in order to manipulate one expression into the same form as the other. So if you are comfortable with them, you can always use them to grind out an analysis.
To build a truth table for an expression make at table with, initially, a column for each variable that occurs in the expression. Then add a column to the right for each subexpression, working back up to the whole expression. Each new column should be an expression that combines the expressions we have already made columns for using just one operator.
After that, itβs just a matter of filling in the columns, moving from left to right. Since each column is an expression in terms of one or two subexpressions whose column has already been filled in that should be easy.
To determine if two expressions are equivalent, we can build truth tables for both expressions and then use the tables to check if for each combination of variable values the two expressions produce the same result. For instance, we can now build a table for !a || !b in order to show that De Morganβs Law for && is true.
To compare the two tables, just look at the rightmost column of each and check that the values are the same for each now. (This assumes that you filled in the variabel combinations in the same order.) If they are, then the two expressions are equivalent. As you can see in those two tables the last column in both reads true, true, true, false so the expressions are equivalent.
(AP 2.6.A.1) Two Boolean expressions are equivalent if they evaluate to the same value in all cases. Truth tables can be used to prove Boolean expressions are equivalent.
(AP 2.6.B.1) Two different variables can hold references to the same object. Object references can be compared using == and !=. (Two object references are considered aliases when they both reference the same object.)
(AP 2.6.B.3) Classes often define their own equals method, which can be used to specify the criteria for equivalency for two objects of the class. The equivalency of two objects is most often determined using attributes from the two objects.
Yes, by applying De Morganβs Law to !(b || a) we get that a && !(b || a) is equivalent to a && !b && !a. Since a && !a can never be true, the result will always be false.
The value is true when a has the value false, and is false otherwise.