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:
Here is some boilerplate code:
We get to implementing, and come up with something like this:
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.
Output:
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.
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.
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.
And I think we can be pretty happy with that (until someone adds a new fruit...).
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
← March 2021 →
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |
Tags
My Blog Links
Recent Entries
Recent Comments
Search My Blog
0 user(s) viewing
0 Guests
0 member(s)
0 anonymous member(s)
0 member(s)
0 anonymous member(s)