Calculator Series
Last updated
Was this helpful?
Last updated
Was this helpful?
So far, we have encountered the following series of calculator problems:
Though each of them may be solved using different methodologies, in this post I'd like to sort out the complexities and develop one "generic" solution to help us approach these problems.
Note that this post is NOT intended to deal with general calculator problems that involve complicated operands/operators. The analyses below are limited to the scope as specified by the problem descriptions of the above four problems.
I -- Definitions and terminology
In this section I will spell out some definitions to facilitate the explanation.
Expression
: An expression is a string whose value is to be calculated. Every expression must be alternating betweenoperands andoperators.
Operand
: An operand is a "generalized value" that can be the target of an operator. It must be one of the following three types --number,variable,subexpression.
Number
: A number consists of digits only. The value of an operand of this type is given by the literal value of the number.
Variable
: A variable consists of lowercase letters only. It can be either a free variable whose value is unknown, or a bound variable whose value is mapped to some number (can be looked up). Here we define the value of an operand of free variable type is given by the variable itself, while that of bound variable type is given by the value of the number to which the variable is mapped.
Subexpression
: A subexpression is a valid expression enclosed in parentheses (which implies a recursive definition back toExpression
). The value of an operand of this type is given by the calculated value of the subexpression itself.
Operator
: An operator is some prescribed action to be taken on the target operands. It must be one of the following four types:+
,-
,*
,/
, corresponding to addition, subtraction, multiplication and integer division, respectively. Operators have precedences, where+
and-
havelevel oneprecedence, while*
and/
havelevel twoprecedence (the higher the level is, the higher the precedence is).
II -- Rules for calculations
In this section, I will specify the general rules for carrying out the actual evaluations of the expression.
Separation rule:
We separate the calculations into two different levels corresponding to the two precedence levels.
For each level of calculation, we maintain two pieces of information: thepartial result_and the_operator in effect.
For level one, the partial result starts from0
and the initial operator in effect is+
; for level two, the partial result starts from1
and the initial operator in effect is*
.
We will usel1
ando1
to denote respectively the partial result and the operator in effect for level one;l2
ando2
for level two. The operators have the following mapping:
o1 == 1
means+
;o1 == -1
means-
;
o2 == 1
means*
;o2 == -1
means/
.
By default we havel1 = 0
,o1 = 1
, andl2 = 1
,o2 = 1
.
Precedence rule:
Each operand in the expression will be associated with a precedence of level two by default, meaning they can only take part in calculations of precedence level two, not level one.
The operand can be any of the aforementioned types (number, variable or subexpression), and will be evaluated together withl2
under the action prescribed byo2
.
Demotion rule:
The partial resultl2
of precedence level two can be demoted to level one. Upon demotion,l2
becomes the operand for precedence level one and will be evaluated together withl1
under the action prescribed byo1
.
The demotion happens when either a level one operator (i.e.,+
or-
) is hit or the end of the expression is reached. After demotion,l2
ando2
will be reset for following calculations.
III -- Algorithm implementations
In this section, I will lay out the general structure of the algorithm using pseudo-codes. From sectionI
, we know there are at most five different types of structs contained in the expression: number, variable, subexpression, level one operators, level two operators. We will check each of them and proceed accordingly.
IV -- List of solutions
It is now straightforward to adapt the algorithm in sectionIII
to tackle each of the calculator problems. Note that not all the checks are needed for a particular version of the calculator problems. More specifically,
Basic Calculator I does not have variables and level two operators;
Basic Calculator II does not contain variables as well as subexpressions;
Basic Calculator III does not have variables;
Basic Calculator IV is the most general form but its level two operators do not include division (/
).
In this section, I will list two solutions for Basic Calculator III (recursive and iterative), and one solution for Basic Calculator IV (recursive).
Basic Calculator III: Solutions for this version pretty much follow the general structure in sectionIII
, except that we do not need to check for variables since the input expression does not contain any.
Recursive solution:O(n^2)
time,O(n)
space
Iterative solution:O(n)
time,O(n)
space
Basic Calculator IV: Solutions for this version, however, require some extra effort apart from the general structure in sectionIII
. Due to the presence of variables (free variables, to be exact), the partial results for each level of calculations may not be pure numbers, but instead expressions (simplified, of course). So we have to come up with some structure to represent these partial results.
Though multiple options exist as to designing this structure, we do want it to support operations like addition, subtraction and multiplication with relative ease. Here I represent each expression as a collection of mappings from terms to coefficients, where each term is just a list of free variables sorted in lexicographic order. Some quick examples:
"2 * a * b - d * c"
: this expression contains two terms, where the first term is["a", "b"]
and the second is["c", "d"]
. The former has a coefficient of2
while the latter has a coefficient of-1
. So we represent the expression as two mappings:["a", "b"] --> 2
and["c", "d"] --> -1
.
"4
": this expression contains a single term, where the term haszerofree variables and thus will be written as[]
. The coefficient is4
so we have the mapping[] --> 4
. More generally, for any expression formed only by a pure numbernum
, we have[] --> num
.
See below for a detailed definition of theTerm
andExpression
classes.
TheTerm
class will support operations for comparing two terms according to their degrees as well as for generating a customized string representation.
TheExpression
class will support operations for adding additional terms to the existing mapping.
Addition of two expressions is done by adding all mappings in the second expression to those of the first one, and if possible, combine the coefficients of duplicate terms.
Subtraction of two expressions is implemented by first negating the coefficients of terms in the second expression and then applying addition (and thus can be combined with addition).
Multiplication of two expressions is done by collecting terms formed by merging every pair of terms from the two expressions (as well as their coefficients).
Lastly to conform to the format of the output, we have a dedicated function to convert the mappings in the result expression to a list of strings, where each string consists of the coefficient and the term connected by the*
operator. Note that terms with0
coefficient are ignored and terms without free variables contains numbers only.
As to complexity analysis, the nominal runtime complexity of this solution is similar to the recursive one for Basic Calculator III --O(n^2)
. It is also possible to use stacks to simulate the recursion process and cut the nominal time complexity down toO(n)
(I will leave this as an exercise for you).
Here I used the word "nominal" because the above analyses assumed that the addition, subtraction and multiplication operations of two expressions take constant time, as is the case when the expressions are pure numbers. Apparently this assumption no longer stands true, since the number of terms may grow exponentially (think about this expression(a + b) * (c + d) * (e + f) * ...
). Nevertheless, this solution should work against average/general test cases.