Applying discounts
In this example, I want to show you a technique I like a lot. In many programming languages, other than the if/elif/else constructs, in whatever form or syntax they may come, you can find another statement, usually called switch/case, that in Python is missing. It is the equivalent of a cascade of if/elif/.../elif/else clauses, with a syntax similar to this (warning! JavaScript code!):
/* switch.js */
switch (day_number) {
case 1:
case 2:
case 3:
case 4:
case 5:
day = "Weekday";
break;
case 6:
day = "Saturday";
break;
case 0:
day = "Sunday";
break;
default:
day = "";
alert(day_number + ' is not a valid day number.')
}
In the preceding code, we switch on a variable called day_number. This means we get its value and then we decide what case it fits in (if any). From 1 to 5 there is a cascade, which means no matter the number, [1, 5] all go down to the bit of logic that sets day as "Weekday". Then we have single cases for 0 and 6, and a default case to prevent errors, which alerts the system that day_number is not a valid day number, that is, not in [0, 6]. Python is perfectly capable of realizing such logic using if/elif/else statements:
# switch.py
if 1 <= day_number <= 5:
day = 'Weekday'
elif day_number == 6:
day = 'Saturday'
elif day_number == 0:
day = 'Sunday'
else:
day = ''
raise ValueError(
str(day_number) + ' is not a valid day number.')
In the preceding code, we reproduce the same logic of the JavaScript snippet in Python, using if/elif/else statements. I raised the ValueError exception just as an example at the end, if day_number is not in [0, 6]. This is one possible way of translating the switch/case logic, but there is also another one, sometimes called dispatching, which I will show you in the last version of the next example.
Let's start the new example by simply writing some code that assigns a discount to customers based on their coupon value. I'll keep the logic down to a minimum here, remember that all we really care about is understanding conditionals and loops:
# coupons.py
customers = [
dict(id=1, total=200, coupon_code='F20'), # F20: fixed, £20
dict(id=2, total=150, coupon_code='P30'), # P30: percent, 30%
dict(id=3, total=100, coupon_code='P50'), # P50: percent, 50%
dict(id=4, total=110, coupon_code='F15'), # F15: fixed, £15
]
for customer in customers:
code = customer['coupon_code']
if code == 'F20':
customer['discount'] = 20.0
elif code == 'F15':
customer['discount'] = 15.0
elif code == 'P30':
customer['discount'] = customer['total'] * 0.3
elif code == 'P50':
customer['discount'] = customer['total'] * 0.5
else:
customer['discount'] = 0.0
for customer in customers:
print(customer['id'], customer['total'], customer['discount'])
We start by setting up some customers. They have an order total, a coupon code, and an ID. I made up four different types of coupons, two are fixed and two are percentage-based. You can see that in the if/elif/else cascade I apply the discount accordingly, and I set it as a 'discount' key in the customer dictionary.
At the end, I just print out part of the data to see whether my code is working properly:
$ python coupons.py
1 200 20.0
2 150 45.0
3 100 50.0
4 110 15.0
This code is simple to understand, but all those clauses are kind of cluttering the logic. It's not easy to see what's going on at a first glance, and I don't like it. In cases like this, you can exploit a dictionary to your advantage, like this:
# coupons.dict.py
customers = [
dict(id=1, total=200, coupon_code='F20'), # F20: fixed, £20
dict(id=2, total=150, coupon_code='P30'), # P30: percent, 30%
dict(id=3, total=100, coupon_code='P50'), # P50: percent, 50%
dict(id=4, total=110, coupon_code='F15'), # F15: fixed, £15
]
discounts = {
'F20': (0.0, 20.0), # each value is (percent, fixed)
'P30': (0.3, 0.0),
'P50': (0.5, 0.0),
'F15': (0.0, 15.0),
}
for customer in customers:
code = customer['coupon_code']
percent, fixed = discounts.get(code, (0.0, 0.0))
customer['discount'] = percent * customer['total'] + fixed
for customer in customers:
print(customer['id'], customer['total'], customer['discount'])
Running the preceding code yields exactly the same result we had from the snippet before it. We spared two lines, but more importantly, we gained a lot in readability, as the body of the for loop now is just three lines long, and very easy to understand. The concept here is to use a dictionary as a dispatcher. In other words, we try to fetch something from the dictionary based on a code (our coupon_code), and by using dict.get(key, default), we make sure we also cater for when the code is not in the dictionary and we need a default value.
Notice that I had to apply some very simple linear algebra in order to calculate the discount properly. Each discount has a percentage and fixed part in the dictionary, represented by a two-tuple. By applying percent * total + fixed, we get the correct discount. When percent is 0, the formula just gives the fixed amount, and it gives percent * total when fixed is 0.
This technique is important because it is also used in other contexts, with functions, where it actually becomes much more powerful than what we've seen in the preceding snippet. Another advantage of using it is that you can code it in such a way that the keys and values of the discounts dictionary are fetched dynamically (for example, from a database). This will allow the code to adapt to whatever discounts and conditions you have, without having to modify anything.
If it's not completely clear to you how it works, I suggest you take your time and experiment with it. Change values and add print statements to see what's going on while the program is running.