Subscribe to MentalFloss Minutes        RSS Feed
-----

Logic Exploration

Icon Leave Comment
I want to talk a bit about the various ways of manipulating logic for potentially more readable code.

Let's consider a problem:

You are to test two fruits (APPLE, BANANA, LEMON, LIME) to report on them. There are two conditions:

  • If neither is LIME or LEMON, then they should be the same fruit. Otherwise, print "ERROR: MIXED FRUIT"
  • If one is a LIME or LEMON, then the other should be a LIME or LEMON. Otherwise, print "ERROR: LOW CITRUS"
  • If both conditions are good, then print "HEALTHY CHOICE".


Here is some boilerplate code:

#include <iostream>

using namespace std;

typedef enum {
	APPLE,
	BANANA,
	LEMON,
	LIME,
	NONE
} Fruit;

void test_fruit(Fruit f1, Fruit f2)
{
	// If neither is LIME or LEMON, then they should be the same fruit. Otherwise, print "ERROR: MIXED FRUIT"

	// If one is a LIME or LEMON, then the other should be a LIME or LEMON. Otherwise, print "ERROR: LOW CITRUS"
	
	// If both conditions are good, then print "HEALTHY CHOICE".
}

int main()
{	
	string names[] = { "APPLE", "BANANA", "LEMON", "LIME" };
	Fruit fruits[] = { APPLE, BANANA, LEMON, LIME };
		
	for(Fruit f1 : fruits)
	{
		for(Fruit f2 : fruits)
		{
			cout << names[f1] << " and " << names[f2] << " = ";
			test_fruit(f1, f2);
			cout << endl;
		}
	}	
}



We get to implementing, and come up with something like this:

void test_fruit(Fruit f1, Fruit f2)
{
	// If neither is LIME or LEMON, then they should be the same fruit. Otherwise, print "ERROR: MIXED FRUIT"
	// If one is a LIME or LEMON, then the other should be a LIME or LEMON. Otherwise, print "LOW CITRUS"
	// If both conditions are good, then print "HEALTHY CHOICE".
	if(f1 != LIME && f1 != LEMON && f2 != LIME && f2 != LEMON)
	{
		if(f1 == f2)
		{
			// success
		}
		else
		{
			cout << "ERROR: MIXED FRUIT";
		}
	}
	else if((f1 == LIME || f1 == LEMON) && (f2 == LIME || f2 == LEMON))
	{
		// success
	}
	else
	{
		cout << "ERROR: LOW CITRUS";
	}
	
	// OK. But how do we print "HEALTHY CHOICE" now?
}



Obviously, something is wrong here. We can't print "HEALTHY CHOICE" because we've ran out of branches. So, we can introduce one, but that requires a flag.

void test_fruit(Fruit f1, Fruit f2)
{
	// If neither is LIME or LEMON, then they should be the same fruit. Otherwise, print "ERROR: MIXED FRUIT"
	// If one is a LIME or LEMON, then the other should be a LIME or LEMON. Otherwise, print "LOW CITRUS"
	// If both conditions are good, then print "HEALTHY CHOICE".
	bool is_healthy = false;
	
	if(f1 != LIME && f1 != LEMON && f2 != LIME && f2 != LEMON)
	{
		if(f1 == f2)
		{
			// success
			is_healthy = true;
		}
		else
		{
			cout << "ERROR: MIXED FRUIT";
			is_healthy = false;
		}
	}
	else if((f1 == LIME || f1 == LEMON) && (f2 == LIME || f2 == LEMON))
	{
		// success
		is_healthy = true;
	}
	else
	{
		cout << "ERROR: LOW CITRUS";
		is_healthy = false;
	}
	
	if(is_healthy)
	{
		cout << "HEALTHY CHOICE";
	}
}



Output:

APPLE and APPLE = HEALTHY CHOICE
APPLE and BANANA = ERROR: MIXED FRUIT
APPLE and LEMON = ERROR: LOW CITRUS
APPLE and LIME = ERROR: LOW CITRUS
BANANA and APPLE = ERROR: MIXED FRUIT
BANANA and BANANA = HEALTHY CHOICE
BANANA and LEMON = ERROR: LOW CITRUS
BANANA and LIME = ERROR: LOW CITRUS
LEMON and APPLE = ERROR: LOW CITRUS
LEMON and BANANA = ERROR: LOW CITRUS
LEMON and LEMON = HEALTHY CHOICE
LEMON and LIME = HEALTHY CHOICE
LIME and APPLE = ERROR: LOW CITRUS
LIME and BANANA = ERROR: LOW CITRUS
LIME and LEMON = HEALTHY CHOICE
LIME and LIME = HEALTHY CHOICE



Hey, it works. We can call it a day, right? Well, no. I don't know about you, but I think that this looks terrible. Let's try to make it better by instead doing quick returns on error. After all, we only care about one error here.

void test_fruit(Fruit f1, Fruit f2)
{
	// If neither is LIME or LEMON, then they should be the same fruit. Otherwise, print "ERROR: MIXED FRUIT"
	// If one is a LIME or LEMON, then the other should be a LIME or LEMON. Otherwise, print "LOW CITRUS"
	// If both conditions are good, then print "HEALTHY CHOICE".
	
	if(f1 != LIME && f1 != LEMON && f2 != LIME && f2 != LEMON)
	{
		if(f1 == f2)
		{
			// success
		}
		else
		{
			cout << "ERROR: MIXED FRUIT";
			return;
		}
	}
	else if((f1 == LIME || f1 == LEMON) && (f2 == LIME || f2 == LEMON))
	{
		// success
	}
	else
	{
		cout << "ERROR: LOW CITRUS";
		return;
	}
	
	cout << "HEALTHY CHOICE";
}



And the output is the same.

But there's more. What if instead of checking for successes, we check for failures? Our universe is APPLE, BANANA, LEMON, LIME. Instead of checking if neither are a LIME or LEMON, we check if both are APPLE or BANANA, and that they are not the same.

void test_fruit(Fruit f1, Fruit f2)
{
	// If neither is LIME or LEMON, then they should be the same fruit. Otherwise, print "ERROR: MIXED FRUIT"
	// If one is a LIME or LEMON, then the other should be a LIME or LEMON. Otherwise, print "LOW CITRUS"
	// If both conditions are good, then print "HEALTHY CHOICE".
	if((f1 == APPLE || f1 == BANANA) && (f2 == APPLE || f2 == BANANA) && f1 != f2)
	{
		cout << "ERROR: MIXED FRUIT";
		return;
	}
	
	if(((f1 == LIME || f1 == LEMON) && (f2 != LIME && f2 != LEMON) || (f2 == LIME || f2 == LEMON) && (f1 != LIME && f1 != LEMON)))
	{
		cout << "ERROR: LOW CITRUS";
		return;
	}
	
	cout << "HEALTHY CHOICE";
}



The new if statement may look complicated, but we are now only checking errors. You may recognize this as XOR. So, let's introduce that.

void test_fruit(Fruit f1, Fruit f2)
{
	// If neither is LIME or LEMON, then they should be the same fruit. Otherwise, print "ERROR: MIXED FRUIT"
	// If one is a LIME or LEMON, then the other should be a LIME or LEMON. Otherwise, print "LOW CITRUS"
	// If both conditions are good, then print "HEALTHY CHOICE".
	if((f1 == APPLE || f1 == BANANA) && (f2 == APPLE || f2 == BANANA) && f1 != f2)
	{
		cout << "ERROR: MIXED FRUIT";
		return;
	}
	
	if((f1 == LIME || f1 == LEMON) ^ (f2 == LIME || f2 == LEMON))
	{
		cout << "ERROR: LOW CITRUS";
		return;
	}
	
	cout << "HEALTHY CHOICE";
}



APPLE and APPLE = HEALTHY CHOICE
APPLE and BANANA = ERROR: MIXED FRUIT
APPLE and LEMON = ERROR: LOW CITRUS
APPLE and LIME = ERROR: LOW CITRUS
BANANA and APPLE = ERROR: MIXED FRUIT
BANANA and BANANA = HEALTHY CHOICE
BANANA and LEMON = ERROR: LOW CITRUS
BANANA and LIME = ERROR: LOW CITRUS
LEMON and APPLE = ERROR: LOW CITRUS
LEMON and BANANA = ERROR: LOW CITRUS
LEMON and LEMON = HEALTHY CHOICE
LEMON and LIME = HEALTHY CHOICE
LIME and APPLE = ERROR: LOW CITRUS
LIME and BANANA = ERROR: LOW CITRUS
LIME and LEMON = HEALTHY CHOICE
LIME and LIME = HEALTHY CHOICE



And I think we can be pretty happy with that (until someone adds a new fruit...).

0 Comments On This Entry

 

November 2018

S M T W T F S
    123
45678910
11121314151617
18192021 22 2324
252627282930 

Tags

    Recent Entries

    Recent Comments

    Search My Blog

    0 user(s) viewing

    0 Guests
    0 member(s)
    0 anonymous member(s)