Graphic by Keith Ohlfs

CS111, Wellesley College, Spring 2000

Simplifying Boolean Expressions and Conditionals

[CS111 Home Page] [Syllabus] [Lecture Notes] [Assignments] [Labs] [Programs] [Documentation] [Software Installation] [FAQ] [CS Dept.] [CWIS]

When writing methods that return booleans, many people make their code much more complicated than it needs to be. For instance, suppose you want a method that detects if a buggle has a wall both to its left and its right. One way of doing this is as follows:

public boolean hasWallsOnBothSides () {
  if (isWallToLeft()) {
    if (isWallToRight()) {
      return true;
    } else {
      return false;
    }
  } else {
      return false;
  }
}

However, this is much more verbose than it needs to be. First of all, study the inner conditional:

    if (isWallToRight()) {
      return true;
    } else {
      return false;
    }

If isWallToRight() returns true, then the conditional returns true; if isWallToRight() returns false, then the conditional returns false. So the above statement is equivalent to the single line.

    return isWallToRight();

It is alway possible to replace the pattern if (E) {return true;} else {return false;} by the pattern return E; for any boolean expression E. We expect you to make this simplification in your code. Similarly, it is always possible to replace the pattern if (E) {return false;} else {return true;} by the pattern return !E;.

Now our method looks like

public boolean hasWallsOnBothSides () {
  if (isWallToLeft()) {
    return isWallToRight()
  } else {
      return false;
  }
}

It turns out that this can be simplified further to

public boolean hasWallsOnBothSides () {
  return isWallToLeft() && isWallToRight();
}

This makes sense in terms of the contract for hasWallsOnBothSides() --- it should return true if and only if there are walls to both the left and the right. But how do we know when it's legal to make such a simplification?

Just as it is possible to simplify algebraic expressions by using rules like cancellation, commutativity, associativity, distributivity, etc., it is possible to simplify boolean expressions and conditional statements by using rules. Here we present the rules that you need to know.

In the following table, the patterns of boolean expressions in the left column can always be replaced by the ones in the right column. The notation boolExp stands for any expression that evaluates to a boolean.

This pattern ...

... can be replaced by this simpler pattern

boolExp == true

boolExp

boolExp == false

! boolExp

!!boolExp

boolExp

true && boolExp

boolExp

boolExp && true

boolExp

false && boolExp

false

true || boolExp

true

false || boolExp

boolExp

boolExp || false

boolExp

You should convince yourself that all of the above simplifications are logically valid (i.e., substituting both true and false for boolExp makes the two columns have the same value). Additionally, you should convince yourself that the simplifications preserve errors and nontermination (i.e., if boolExp throws an exception or gets stuck in an infinite loop, both columns still have the same behavior.) In some cases, the "short-circuit" nature of && and || is critical for satisfying the second criterion. This second criterion is more subtle than logical validity, and fails for the following two logically valid simplifications; for this reason, they are not included in the above table:

boolExp && false

false

boolExp || true

true

Note that each of the simplifications in the larger table makes the expression shorter. So if you continue to apply the simplifications, you will eventually reach a point where you can no longer apply any more simplifications. Furthermore, it turns out that no matter what order you apply the simplifications in, you will eventually reach a unique shortest expression.

There are other important valid transformations that preserve meaning but destroy the property of reaching a unique simplest result. Here are some of these; the first two are known as DeMorgan's laws:

(! boolExp1) || (! boolExp2)

! (boolExp1 && boolExp2)

(! boolExp1) && (! boolExp2)

! (boolExp1 || boolExp2)

boolExp1 || ((! boolExp1) && boolExp2)

boolExp1 || boolExp2

((! boolExp1) && boolExp2) || boolExp1

boolExp1 || boolExp2

(! boolExp1) || (boolExp1 && boolExp2)

(! boolExp1) || boolExp2

(boolExp1 && boolExp2) || (! boolExp1)

(! boolExp1) || boolExp2

boolExp1 && ((! boolExp1) || boolExp2)

boolExp1 && boolExp2

((! boolExp1) || boolExp2) && boolExp1

boolExp1 && boolExp2

(! boolExp1) && (boolExp1 || boolExp2)

(! boolExp1) && boolExp2

(boolExp1 || boolExp2) && (! boolExp1)

(! boolExp1) && boolExp2

You should verify that these simplifications are logically valid and preserve error and non-termination semantics. To see that the first rule destroys the property of reaching a unique shortest expression, consider the expression

(! (!x)) || (! y)

Using the simplification that eliminates double negation, this can be simplifed in one step to x || (! y). But it can also be simplified by the first DeMorgan simplification to ! ((! x) && y). Both of these expressions cannot be simplified any further. There are similar examples involving the other rules.

Some of the above patterns may seem silly (you're probably saying to yourself, "I would never write that!"). But even though you may not write them yourself, they may show up in the results of some of the simplifications of conditionals shown below. In this context you would be expected to simplify them.

Now consider the following single transformation on conditional statements that return boolean results:

if (boolExp1) {
return boolExp2;
}
else {
return boolExp3;
}

return (boolExp1 && boolExp2)
|| ((!
boolExp1) && boolExp3);

You should convince yourself that this rule is logically valid and preserves error and non-termination semantics. However, it does not simplify the statement. Indeed, the result requires two evaluations of boolExp1 where the initial expression only required one! However, if at least one of boolExp2 or boolExp3 is true or false, then the boolean expression simplifications from above can lead to a simpler result. (Obviously, similar results hold if boolExp1 is true or false.) Below are some important instantiations of this idea.

This pattern ...

... can be replaced by this simpler pattern

if (boolExp1) {
return true;
}
else {
return false;
}

 

return boolExp1;

 

if (boolExp1) {
return false;
}
else {
return true;
}

return (! boolExp1);

if (boolExp1) {
return true;
}
else {
return boolExp3;
}

return boolExp1 || boolExp3;

if (boolExp1) {
return false;
}
else {
return boolExp3;
}

return (! boolExp1) && boolExp3

if (boolExp1) {
return boolExp2;
}
else {
return true;
}

return (! boolExp1) || boolExp2

if (boolExp1) {
return boolExp2;
}
else {
return false;
}

return boolExp1 && boolExp2