Due Dates: Due by
midnight on the morning of your next
lab:
The problems in this assignment exercise low-level functional programming concepts, in conjunction with high-level ones, as well as more on data representation. Recursion, including mutual recursion, will be useful in this assignment.
This assignment covers material through Chapter 4 in the text.
Comment
your . Be sure your comments
indicate what each function does, including
what the inputs and outputs are, and how the function does what it does.
As with assignment 1, there are tests you can run in the /cs/cs60/a/02 directory to try out your functions. They are Test1.in through Test25.in. Each problem tells you which tests you can run to test that problem's code. However, you can also test ALL of the problems in this assignment with
rex a02.rex < /cs/cs60/a/02/AllTests.in
Do test your code before submitting it to
be sure there are no typos, misspellings, etc. and that it works as expected.
Failure to do so may cost you points.
You should submit your rex functions in a single file named a02.rex using
cs60submit a02.rex
“Unicalc” is the name we give to a unit-conversion application. The purpose of this application is to convert one type of unit (e.g. a physical unit, such as “miles per hour”) into another (such as “meters per second”). Below is an example of how a version of Unicalc (not exactly the one that you will construct) runs on turing. The boldface parts are what the user types. The rest is done by the program.
turing> unicalc
To convert from units of: miles / hour to units of: meters / second
Multiply by: 0.447032
or
Divide by: 2.23698
Unicalc is limited in the kinds of expressions it will accept: Conversions and expressions must be “multiplicative” rather than, say, “additive”. Unicalc will thus not convert degrees Farenheit to degrees Celsius, which entails addition. Mulitiplicative includes division and raising to a power. For example:
To convert from units of: pounds / meter^3 to units of: tons / acre footMultiply by: 0.616707
or
Divide by: 1.62151
Here
/ indicates division and ^ represents exponentiation, or raising to a power. Juxtaposition
(placing strings next to each other) indicates multiplication. Exponentiation
binds the most tightly, then multiplication, then division. Only integer powers
are allowed, so exponentiation is really just a shorthand for repeated
multiplication.
The Unicalc application converts quantities expressed in one type of unit to
those of another. The end result will be able to do conversions such as the
following:
Unicalc
works on multiplicative conversions only, so it does not perform conversions such as Fahrenheit to Celsius.
For illustration, there is also a web version of Unicalc at:
http://www.cs.hmc.edu/~keller/unicalc/
(The display may be somewhat broken right now, as Java on browsers keep the way screen widgets.)
Unicalc conversions are based on a stored database that expresses some units in terms of others. For example, a minute might be expressed as 60 seconds, a mile as 5280 feet, etc.. For each unit, there is at most one conversion. This can be regarded as an equation:
mile = 5280 footIn general, every conversion gives, for the unit on the left-hand side, a multiplicative expression on the right hand side consisting of a numeric factor, a numerator product, and denominator product. Here are some more equations from a typical database:coulomb = 1 ampere second
cup = 0.5 pint
lux = 1 lumen / meter^2watt = 1 joule / second
In Unicalc, the database used for conversions is completely decoupled from the application itself, to permit revision by an administrator without reprogramming. You can view the exact database used by the command-line version of Unicalc on turing in this file:
/cs/cs60/bin/unicalc.db
How do all the conversions get accomplished then? Unicalc chains together single conversions to perform compound conversions. This makes maintenance of the database extremely simple.
What if no conversion is given for a unit? In this case, the unit is considered to be primitive, so no further conversions are considered. The choice of primitive units is up to the database administrator. They are simply left out of the list of the database conversions. So Unicalc can perform conversions on entailing units it has never even seen before (below fish is such a unit):
To convert from units of: fish / acre foot
to units of: fish / liter
Multiply by: 8.10757e-07
or
Divide by: 1.23341e+06
What if the units are not inter-convertible? The way Unicalc handles this is to note that all units are inter-convertible. It is just a matter of allowing scale factors other than numbers:
To convert from units of: watt
to units of: joule
Multiply by: 1 / second
or
Divide by: 1 second
The units are above are not inter-convertible in the every day sense, since watt is a unit of power whereas joule is a measure of energy. But power is energy per unit time. Unicalc says if we have N watts, we can convert it to joules by multiplying by 1/second, i.e. N watts = N joules/second. I realize this may seem counter-intuitive at first, but it simplifies the problem of dealing with such units.
The
objective of this assignment is to implement a version of Unicalc in rex. This
version will not have to provide nice input and output formatting. Rather, we
will provide the input in the form of rex lists, and expect that output to be
in that form as well. An example, the interpretation of which will be explained
presently, is
rex > convert( [1, ["mile"], ["hour"]],
[1, ["meter"], ["second"]], db);
[0.447032, [], []]
Here db is a variable bound to the database, which is expressed as an association list
When solving any problem of this sort, one of the key issues to be addressed is how the data are going to be represented in the computer. Normally this would be decision that is totally up to the designer. However, for this assignment we are going to dictate a specific one. This will help everyone maintain sanity as we progress toward a working application. We will justify the decisions that went into this choice of data representation.
The simplest data item in Unicalc will be called a Unit. A Unit is simply a string with no embedded blanks to which we humans assign an interpretation. Examples of units are “meter”, “second”, “foot”, and so on. Units will typically be equated to combinations of other units in the database, unless the unit is basic, in which case no conversion is specified.
The most general data item in Unicalc will be called a Quantity. It consists of three components:
· A floating-point numeric factor
· A numerator representing a product of Units.
· A denominator also representing a product of Units.
In the rex implementation, we will use a list of three items. The numerator and denominator will be a sorted lists of strings. Examples of Quantities in this representation are:
[1., ["meter"], ["second"]]
stands for 1 meter per second. Similarly,
[2.5, ["meter"], ["second", "second"] ]
represents 1 meter per second^2, while
[12., [“inch”], []]
represents 12 inches. (An empty numerator or denominator is equated to 1.)
The reason for keeping the strings will become apparent when we consider algorithms for implementing Unicalc. The sorted assumption is an example of a representation invariant for the data. This means that all Quantities are to be maintained in this form, so simplify the attendant algorithms.
An association list is an appropriate representation for the database in the rex implementation. Each equation in the database is represented as a pair:
· Unit
· Quantity
An example of a small database is:
[
["foot", [12.0, ["inch"], []]],
["watt", [1.0, ["joule"], [“coulomb”]]],
["coulomb", [1.0, ["ampere", "second"], []]]
]
It would be a good idea to make sure you understand what the purpose of each of the brackets above, because there are three distinct uses: to form the association list, to form the pairs, and to form the numerators and denominators.
We are going to walk through some functions deemed useful in implementing Unicalc, taking you up to a design that will work for the general conversion problem.
1.
(IN LAB) The representation of a Quantity is called simplified if there is no element common to its numerator and
denominator lists. One way to convert a Quantity to an equivalent simplified
form is to "cancel" any pairs of elements common to its numerator and
denominator. This can be done in two steps: canceling from the numerator and
canceling from the denominator. Define a function simplify in rex one argument, a
Quantity, that yields an equivalent simplified representation.
It is strongly suggested that you adopt the merge technique for canceling (/cs/cs60/a/02/merge.rex), which exploits the assumption
that the numerator and denominator lists are sorted.
Some example of simplify are shown:
rex > simplify([3,
["kg", "meter", "second", "second"],
["kg", "kg", "second"]]);
[3, ["meter", "second"], ["kg"]] rex > simplify([3.14,
["meter", "second"],
["meter", "second", "second"]]);
[3.14, [], ["second"]]
2. (IN LAB) Construct rex functions multiply and divide that respectively multiply and divide two Quantities, yielding a simplified Quantity. Again, you may increase your code's efficiency by using merge (as discussed in class) to to combine two sorted lists into a single sorted list. However, as with simplify, any technique is OK. Also, you may assume that the first element of a quantity list will never be 0.
Some examples are:
rex > multiply([2.0, ["kg"], ["second"]],
[3.0, ["meter"], ["second"]]);
[6.0, ["kg", "meter"], ["second", "second"]]
rex > divide([12.5, ["meter"], ["second"]],
[0.5, ["meter"], []]);
[25.0, [], ["second"]]
3. (IN LAB) The Unicalc database is represented as an association list, that is, a list of pairs in which each pair consists of a single unit and an equivalent Quantity for that unit. Essentially, these pairs define equations for various units, and we can refer to the elements of the pairs as the LHS (left-hand side) and RHS (right-hand side) for this reason. For example, a sample database might be defined as:
db = [["foot", [12., ["inch"], []]],
["mile", [5280., ["foot"], []]],
["inch", [0.0253995, ["meter"], []]],
["hour", [60., ["minute"], []]],
["minute", [60., ["second"], []]],
["mph", [1., ["mile"], ["hour"]]],
["coulomb", [1, ["ampere", "second"], []]],
["joule", [1, ["kg", "meter", "meter"],
["second", "second"]]],
["ohm", [1, ["volt"], ["ampere"]]],
["volt", [1, ["joule"], ["coulomb"]]],
["watt", [1, ["joule"], ["second"]]] ];
which happens to be the current contents of
/cs/cs60/a/02/unicalc_mini_db.rex
If
this were the entire database under consideration, note that "ampere", "meter", and "second" are all basic
quantities, since they are not the LHS of any definition.
You can have this database loaded into your program at execution by including in your a02.rex file:
sys(in,"/cs/cs60/a/02/unicalc_mini_db.rex");
There is a substantial database of elements that you are also welcome to try out in the file
/cs/cs60/a/02/unicalc_db.rex .
Note: You may make the assumption that the database is consistent, in the sense that it does not contain any circular definitions of units.
Construct a rex function conv_unit that takes a string u representing a unit and the database association list as arguments. If the unit is defined in the database, it returns that unit's equivalent Quantity, otherwise it returns a standard Quantity representing the unit: [1.0, [u], []]. For example, if db is the database consisting of the list of the pairs above, then:
rex > conv_unit("hour", db);
[60, [minute], [] ]
rex > conv_unit("meter", db);
[1, [meter], [] ]
Hint: When you use assoc to look up an element that's not in a database, it returns [].
4.
This and the next problem go together, in the sense that the
two functions prescribed are to be used in a mutually recursive fashion.
A Quantity is called normalized (with
respect to a database) if it is simplified and furthermore the numerator and
denominator lists within a quantity list contain only basic units. Again, basic units are those that do
not appear on the left-hand side of any entry in the unit database.
In order to convert one unit to another, we can first express those units in a common form. This common form will be normalized Quantities.
For this problem, construct a function norm_unit that returns a normalized Quantity representing its first argument, a unit, with respect to its second argument, a database. Thus norm_unit has the same type as conv_unit, but does more. It is suggested that you not try use conv_unit in norm_unit, but rather test the result of doing assoc on the database for being [] (not found). Then react accordingly:
·
If [] is returned, synthesize a Quantity as with
conv_unit.
· If a pair is returned, use the second element of the pair, which is a Quantity. Recursively normalize the units in the numerator and denominator, then combine them. You will want to use some auxiliary functions to make this simple and readable.
Here
are some example runs of norm_unit,
using the database in unicalc_mini_db.rex
rex > sys(in,"/cs/cs60/a/02/unicalc_mini_db.rex");
rex > norm_unit("second",db);
[1, [second], []]
rex > norm_unit("hour", db);
[3600, [second], []] rex > norm_unit("ohm", db);
[1, [kg, meter, meter],
[ampere, ampere, second, second, second]]
5. Construct a function norm that takes as input any Quantity and a database and returns an equivalent normalized quantity. (The difference between norm and norm_unit is that norm_unit works only on a single unit, whereas norm works on an arbitrary quantity.
Some examples of norm in action:
rex > norm([20.0, ["hour"], []], db);
[72000, ["second"], [] ]
rex > norm([15.0, ["ohm"], ["volt"]], db);
[15, [], [ampere]]
6. Construct the user function convert such that convert(A, B, DB) will return the conversion factor fromQuantity A to Quantity B with respect to the database DB. An example of convert was given in the introduction to this assignment.