Programming Pattern
Learning Objectives
At the end of this sub-unit, students should
- know how to generalize a solution into programming patterns.
- know how to use programming patterns to solve problems.
Generalization
By now, we have already solved a number of problems. What we need to do is to compare some of the solutions and try to find the common pattern between some of the solutions. When comparing two solutions, we keep the parts that are the same and convert the parts that are different into blanks. Let us compare counting digit and sum of digit. We highlight the lines that contains the difference except for the function name.
So this is good, with this, we can solve the following problems by filling in the blanks appropriately. You can try finding the appropriate blanks1 as an exercise.
- Sum of the square of the digits.
- Sum of the cubes of the digits.
- Sum of even digits.
- Sum of odd digits.
Can we have a better generalization? Let us now compare the generalized digit above with factorial so that we can now solve at least three problems using a single pattern.
We use ░░░░n░░░░
to indicate that n
should be used within this blank so that we do not just use an arbitrary condition like while True
.
So now, we have one possible patterns called accumulator.
Since this accumulator is intended for integer, we will name it as accumulator (int).
There may be another accumulator for other data types.
What about other patterns? That is up to you to decide. We can, for instance, try to generalize the prime checking function into a pattern that simply enumerate the potential divisor.
Comparing Patterns
Now that we have at least 3 patterns, we can try to compare them. Some of the comparison we want to make is to figure out which pattern is more general. We say that the less general one is more specific. It can be the case that two patterns are incomparable in a sense that none is more general. First, we will take a look at accumulator compared to generalized digit.
-
Accumulator Integer
-
Generalized Digit
One way to compare patterns is to look at the kinds of problems they can solve.
In this case, accumulator_int
is more general than generalized_digit
.
accumulator_int
can solve whatever generalized_digit
can solve and a few more like computing factorial which cannot be done by generalized_digit
.
What about comparison between accumulator and enumerator?
-
Accumulator Integer
-
Enumerator Integer
In this case, it is not exactly clear.
By looking at the problems they can solve, we know that enumerator_int
can solve the prime checking problem which accumulator_int
cannot solve.
On the other hand, we may not have enough information to use enumerator_int
to solve factorial.
The variable i
is used for iteration, so there is no counterpart for res
.
So these two are incomparable.
Hierarchy
Given that we can compare the patterns based on how specific/general they are, we can also arrange the patterns in a hierarchy. The details is left up to you but one way to arrange the patterns in a hierarchy is to put the more general pattern on top of the more specific patterns. Usually, the more specific pattern is easier to use than the more general ones. Creating a border around them is also a good way to categorize them as represented below.
In this hierarchy, "Generalized Digits" is more general than "Sum of Digit" but more specific than "Accumulator Integer". Additionally, we can also see based on our hierarchy that "Accumulator Integer" and "Enumerator Integer" are incomparable but both are more specific than "Iteration". The code for these patterns are also left as an exercise. In the case of "For-Loop", we will learn this construct only when we are dealing with sequence.
There are some potential work to be done while building this hierarchical pattern bank as shown above. For instance, if we encounter a gap in the pattern, then we may want to invent some pattern that is in between the two patterns. Consider the case of "While-Loop" and "Guess-and-Check Arbitrary". If we feel that there should be an intermediate pattern, then we try to think of a problem that it can solve, and create the pattern.
In the end, all of these are personal notes. It should be catered to individual needs. Each one of us would have a different way of thinking and so would come to a different conclusion on what constitutes an "easy to use" pattern. Since the task of the programming pattern in the pattern bank is to help you solve future problem, then it is imperative that the categorization is done by you for your need.
Furthermore, this is a continuous exercise. So when you encounter a new problem, you may want to do both of these.
- Find a pattern that can be used to solve the problem.
- Since our pattern is hierarchical, we can search from the bottom of the hierarchy.
- Once you have accumulated proficiency in problem solving, you may no longer need to compare one by one and may quickly identify the necessary pattern.
- Abstract the solution into more patterns.
- This can then be used to solve more future problems.
- So the more problems you solve → the more patterns you have → the more future problems you can solve and the more patterns you can accumulate.
We will show how we can use these some of these patterns to solve some problems.
Sum of Digit at Even Index
Sum of Even Index
Problem Description
Given a \(k\) digit integer \(d_{k-1}d_{k-2}{\cdots}d_1d_0\), we want to sum only the digits at even index. So we want to sum \(d_0 + d_2 + d_4 + \cdots\). Whether \(d_{k-1}\) is included or not depends on whether it is even or odd.
Task
Write the function sum_even_index(n)
to compute and return the sum of the digits of n
that are at even index.
The index is computed from the rightmost digit with the index of this digit being 0.
Assumptions
n
is a positive integer (i.e.,n > 0
).
Restrictions
- You are NOT allowed to convert to other data types.
- You must work only on integer.
At this point, we can use all the previous technique and come up with a separate solution but we want to highlight the use of programming pattern. So we rephrase the question as follows.
Which pattern can best be used to solve this problem?
Think about a similar problems that you have solved2. Which of those problems is the closest? Looking at the problem more closely, we can see that it is similar to the sum of digit problem.
Now, what is the programming pattern that is close to sum of digit in the hierarchy? We can consider using accumulator integer to try to solve this. At this point, we can simply write down the code for accumulator integer as shown below.
So this will be our template to solve the problem. We still to figure out the blanks. What kind of changes is needed to adapt this pattern to solve our problem? There are two ways to do this.
- We need to keep a flag on whether we are at even or odd index.
- We need to remove two digits instead of one digit in each iteration.
In both cases, we can guarantee that we perform an actual work only if we are at even index. The first is by using the flag and the second is by ensuring that the rightmost index will always be an even index.
As a bonus, we can also find the sum of digits at odd index by removing the rightmost digit.
ISBN-10
ISBN-10
Problem Description
The final character of a ten-digit International Standard Book Number is a check digit computed so that multiplying each digit by its position in the number (counting from the right) and taking the sum of these products modulo 11 is 0. The digit the farthest to the right (which is multiplied by 1) is the check digit, chosen to make the sum correct. It may need to have the value 10, which is represented as the letter X. For example, take the ISBN 0-201-53082-1. The sum of product is:
Since 99 is multiples of 11 (i.e., \(99 \equiv 0 \ (\text{mod} \ 11)\)), then the ISBN is valid.
Task
Write the function isbn10(n)
to check if n
is a valid ISBN-10 number.
If it is valid, the function returns True
.
Otherwise, it returns False
.
Assumptions
n
is a positive integer (i.e.,n > 0
).n
can have any number of digits including more than 10 digits.
Restrictions
- You are NOT allowed to convert to other data types.
- You must work only on integer.
To use programming pattern technique of problem solving, we need to try to match the problem into a problem that we have solved before. In the understanding the problem step, we can try to analyze the problem in more details. We may then figure out that in the summation above, we have two series.
-
A series of running number that corresponds to the right operand of each term in the summation.
\(\_ \times 10 + \_ \times 9 + \_ \times 8 + \_ \times 7 + \_ \times 6 + \_ \times 5 + \_ \times 4 + \_ \times 3 + \_ \times 2 + \_ \times 1\) -
A series of digits from
n
that corresponds to the left operand of each term in the summation.
\(0 \times \_ + 2 \times \_ + 0 \times \_ + 1 \times \_ + 5 \times \_ + 3 \times \_ + 0 \times \_ + 8 \times \_ + 2 \times \_ + 1 \times \_\)
Can we generate these two series independently?
Problem #1: Right Operands
Consider the problem of generating the right operand.
- What is the similar problem?
- Counting digit because this is a running number that corresponds to the position of the digit.
- What is the programming pattern?
- Accumulator integer.
Extract the pattern.
Then we rename this into isbn10_R(n)
.
Problem #2: Left Operands
Now we look at the left operand and go through the entire process again.
- What is the similar problem?
- Sum of digit because we are extracting the digit.
- What is the programming pattern?
- Accumulator integer.
Similarly, we rename this into isbn10_L(n)
.
The Remaining Problem
Once we have an idea on how to solve the two problems above, the next step is to think about the remaining problem. That is the problem that we have yet to solve. We have the left operand and we have the right operand. What is left is to multiply them together.
Consider the code for isbn10_L(n)
above3.
This is where we extract each digit.
The extraction is done on Line 4 in the expression (n % 10)
.
But this needs to be multiplied with something first before it can be used in the summation.
We will modify this a little.
How do we get the value for ???
.
Now we consider isbn10_R(n)
, the answer can be found here.
During the computation of isbn10_R(n)
, we have all the right operands in the correct order stored inside the variable res
.
So we need to combined the two code together via an interleaving.
Unfortunately, we cannot really combine them as it is because the same variable res
is used differently.
The variable n
is fine as it is used in the same way in both codes.
So we rename the all occurrences of variable res
in isbn10_R(n)
into idx
.
This gives us the following code.
Now we put both codes side by side and extract the common code.
Then we interleave the two codes in the blank space we have already created to accommodate both codes.
Next, we fill in the missing value for ???
.
The answer should be quite obvious, we need to use idx
in place of ???
.
Finally, we need to decide if we need to return idx
or res
.
This answer should hopefully also be obvious.
Left Operand
Common Code
Left Operand
Common Code
Left Operand
Common Code
A word of caution, the order of the interleaving matter.
Try changing the order and put idx
after res
and you will see that the code will not work.
Fortunately, there is no need to record this interleaved pattern into your pattern bank.
We will discuss ways to keep our pattern bank simple by looking at more advanced problem solving technique called divide-and-conquer.
-
square:
(n % 10) ** 2
; cube:(n % 10) ** 3
; even:(1 - (n % 2)) * (n % 10)
; odd:(n % 2) * (n % 10)
. ↩ -
The more you solve, the more similar problems you can use as a sample. ↩
-
This analysis below can be done by considering
isbn10_R(n)
first, so the choice to considerisbn10_L(n)
is an arbitrary decision. ↩