| CS 291 | University of Puget Sound | Spring, 2020 |
|
Programming Language Paradigms:
|
||
|
Explorations with Functional Problem Solving (supported by Scheme/Haskell)
and Declarative Prblem Solving (supported by Prolog) |
||
This worksheet focuses largely on
case expressions
for .. in and where,
These topics are covered in some detail in online readings on Syntax in functions and Higher order functions
This worksheet expands experience with functional problem solving with Haskell. For this lab,
for .. in
and where
Use local bindings, using let .. in
and/or where for all problems in this section.
let
.. in expressions.
where statements.
Car Insurance Rates for Drivers 16-24 Years Old: Insurance rates for drivers 16-24 years old depend on numerous factors, including age, driving environment (e.g,. urban, suburban, rural), make, model and age of car, etc. Also, after establishing a basic insurance rate, discounts may be available for certain types of drivers. This problem considers a simplified rate schedule for car insurance.
According to autoblog.com, the average annual base rate for a high school or college-age driver is:
| Age | Average Annual Rate |
|---|---|
| 16-19 | $2,999 |
| 20-24 | 2,040 |
From this base, insurance companies offer various discounts. For the purposes of this problem, consider the following discounts that may or may not be typical.
When a young driver is eligible for several of these discounts, combination rules may apply. For the purposes of this problem, apply the following rules which may or may not reflect practice.
The following table summarizes these discounts:
| Good Student | Driver Education | Good Driver | Discount |
|---|---|---|---|
| Yes | No | No | 25% |
| No | Yes | No | 21% |
| No | No | Yes | 17% |
| Yes | Yes | No | 40% |
| Yes | No | Yes | 40% |
| No | Yes | Yes | 38% |
| Yes | Yes | Yes | 50% |
Write a function findRate that determines the
insurance rate of a driver, given the driver's age and whether
or not the driver is a "good student", has had "drivers
education", and is a "good driver" (drivers at least 19).
Specifically, findRate utilizes two parameters:
Programming Notes: In computing the discount, the function should include these elements:
let .. in statement to create local bindings
for the base rate and for the amount of discount (if any)
case expression or an if ...
then ... else ... expression
to determine the base insurance rate, given the student's age.
case expression to determine the amount of
discount, based on the 'y' or 'n' values for the three
discounts.
Yet Another Maximum Function: Write your own tail-recursive functions to find the maximum item on a list. (As noted in the instructions for this worksheet, all helper functions must be defined locally)
if expression for a parameter within a function
call is fine here.)
Normalizing Numbers on a List:
Write a function normalize that takes a list of
numbers as parameter and determines the sum of the numbers.
normalize returns
the original list unchanged.
normalize
returns a list of each numbers, where each number in the final
list is the result of dividing the orignal number by the sum.
Note Haskell's map or equivalent might be
considered here (or not).
Examples:
normalize [1, 2, 1] ==> [0.25, 0.5, 0.25] normalize [1, -2, 1] ==> [1, -2, 1] -- sum is 0, so list unchanged normalize [1, 1, 1] ==> [0.33333, 0.33333, 0.33333] normalize [1, -1, 1] ==> [1, -1, 1] -- sum is 1, so division yields same list
First in Alphabetical Order Write a tail-recursive function (with local helper functions as needed) to search a list of strings and return the one that comes first in alphabetical order.
Reversing a List:
Haskell has a reverse that takes a list as
parameter and returns a list with the elements in the opposite
order. Write your own tail-recursive
function myReverse that provides your own
implementation of reverse.
Interest in a Savings Account: A simple savings account earns compound interest over time, based upon an annual interest rate (e.g., 1% or 9%) per year. The basic computations proceed as follows:
monthlyRate = annRate / 1200.0;
interest = monthlyRate * (old balance)
new balance = (old balance) + interest
For example, at an annual rate of 8%, the monthly rate is 0.00666667. With a starting balance of $100, the computations proceed as follows for the first year:
Starting Monthly Ending
Month Balance Interest Balance
0 ------ ---- 100.00
1 100.00 0.67 100.67
2 100.67 0.67 101.34
3 101.34 0.68 102.01
4 102.01 0.68 102.69
5 102.69 0.68 103.38
6 103.38 0.69 104.07
7 104.07 0.69 104.76
8 104.76 0.70 105.46
9 105.46 0.70 106.16
10 106.16 0.71 106.87
11 106.87 0.71 107.58
12 107.58 0.72 108.30
This problem asks to you write a function computeUntilDouble that returns a list of the monthly balances until the amount in the account doubles from the initial balance.
Programming Note: Function computeUntilDouble should have two parameters:
Anagrams by Item Removal: Sometimes one can simplify a problem by removing the parts that don't matter, and then looking at what's left.
For instance if you wanted to figure out if two lists of "stuff" were the same, you might remove matching items from each list until you see if there are items left over. If you have leftover items, the lists were different, and if both collections become empty at the same time, they were anagrams.
Use this technique to write a function isAnagram
which will determine whether or not two strings are anagrams of
each other.
Examples:
isAnagram "one plus twelve" "eleven plus two" ==> True
isAnagram "aab" "abb" ==> False -- number a's and number b's unequal
isAnagram "abb" "aab" ==> False -- check same result if parameters swapped
isAnagram "abb" "ab" ==> False -- number b's different
isAnagram "!!--**abc" "a!-*b*-!c" ==> True
isAnagram "!!--*" "**--!!" ==> False -- number *s unequal
Programming Notes:
elem function determines if an item occurs
within a list.
removeElement function that takes an item and
a list and returns the list with one copy of that item removed.
Computing Semester Averages: Many courses organize work into categories, and the final semester average is a weighted average of student averages in each category. For example, the syllabus for CSCI 291 states that the final semester average will be computed as follows:
| Lab Write-ups: 30% | Projects: 20% | Quizzes: 18% | Test: 12% | Exam: 20% |
A different course might utilized these categories and weights.
| Programming Assignments: 25% | Labs: 20% | Tests: 30% | Exam: 25% |
In each case, weights for the categories in a course could be organized into a list, and grades for a student could be organized into a corresponding list.
This problem asks you to develop functions for individual courses, based on a higher-order function that has parameters for both a list of category weights and a list of student scores.
semAverageTemplate that has two
parameters:
course291 that is defined by
supplying the weights [.30, .20, .18, .12, .20]
to semAverageTemplate. Thus,
function course291 will take a list of student
averages as parameter and return an overall semester average.
otherCourse that is defined by
supplying the weights [.25, .20, .30, .25] to
semAverageTemplate, as described for the other
course listed above.
Global Replace in a List: Consider a function that replaces one item by another in a list. (Haskell already some capabilities for replacing elements in a list, but this question asks you to write functions on your own.)
Write a function myReplace that takes two
items, old and new and a list of
items, and returns a list with all occurrences
of old replaced with new.
myReplace as a higher-order function,
write function changeMonthToMarch that takes
a list of strings as parameter and replaces ever occurence
of "month" with "March". (Hint: This is easy, given a.)
myReplace as a base, write a
function "oneIsSeven" that takes a list of integers as
parameter and replaces every 1 on the list by 7.
Function Addition and Composition:
Write two higher order Haskell functions that have two
functions of one variable, f and g, as parameters.
funcAdd returns the functional sum
of f and g. That is, if h =
funcAdd f g ,
then h x would return the value (f x) + (g x)
funcCompose f g returns the
composition f applied to the result
of g.
That is, if k = funcCompose f g, then
k x would return the value (f (g
x)).
Observation: This problem might make a lovely question for a quiz or test, since the code is short, but requires reasonable understanding of higher-order functions in Haskell.
Filtering Outliners:
Early in the semester, when working with Scheme, we considered a
Scheme procedure filter-outliers that took a list
of numbers as parameter and filtered out those that were not in
a specified range. In those examples, the range was hard-coded
within the function.
filterOutliersTemplate that takes
an lower and upper bound and a list of numbers and returns a
list of the numbers between the lower and upper bound,
inclusive.
filter function.
filterOutliersTemplate as a higher-order
function, and define two derivative functions that have just a
list of numbers as a single parameter.
filter0To100 should return those elements
on a numeric list that lie between 0 and 100, inclusive.
filterMinus40To40 should return those
elements on a numeric list that lie between -40 and 40,
inclusive.
Monoalphabetic Substitution Encryption: Many cryptograms in newspapers are based on an encryption algorithm called monoalphabetic substitution. The idea is reasonably simple: Replace one letter by another throughout the message. As an example, consider the following encoding scheme:
Plain alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher alphabet: XDQTVBKRAUGMZHYWCJOSENILPF
Now consider the message, "THIS IS A MESSAGE TO ENCODE." For each letter in the message, we encode it by looking up each letter in the plain alphabet and replacing it by the corresponding letter in the cipher alphabet. Characters not in the plain alphabet (e.g., punctuation) are left unchanged. Thus, the letter T is replaced by the letter S, "THIS" becomes "SRAO", and the entire message is encoded as "SRAO AO X ZVOOXKV SY VHQYTV." Note that the space and period characters are not changed.
encodeChar plainAlphabet cipherAlphabet
ch with these features.
plainAlphabet and cipherAlphabet
are two strings of characters of the same length (but not
necessarily 26 characters long).
ch is a character
encodeChar looks for ch in successive characters
of the cipherAlphabet
ch is found,
then encodeChar returns the corresponding
character in the cipherAlphabet.
ch is not found,
then encodeChar returns ch
unchanged.
encodeChar may be
considered a function of just 1 parameter, if
the plainAlphabet and cipherAlphabet
are given. That is, for a given plainAlphabet
and cipherAlphabet, use of currying would derive a
function of just one parameter ch.
map function to create a
function encodeString that encrypts an entire string.
Use lambda expressions as part of your solutions to the following problems.
A Triple is just short of a Home Run: Using a lambda expression, write a 1-line function that has a list of numbers as parameter and returns a list with each number on the original list tripled.
Swapping Uppercase and Lowercase Letters:
Write a function swapCase str that takes a String
parameter and then uses map and an anonymous case
to change every lowercase letter to uppercase and every
uppercase letter to lowercase.
Programming Notes: The Data.Char contains
several functions that may be helpful. (Use import
Data.Char to use these functions.)
isLower ch returns True if ch is a
lowercase letter and False otherwise.
isUpper ch returns True if ch is an
uppercase Letter and False otherwise.
toLower ch transforms an uppercase letter to the
corresponding lowercase letter. If ch is not an
uppercase letter, then toLower returns the
original character unchanged.
toUpper ch transforms a lowercase letter to the
corresponding uppercase letter. If ch is not a
lowercase letter, then toUpper returns the
original character unchanged.
toUpper
and toLower can be applied to any character
regardless of whether the character is an uppercase letter,
lowercase letter or a non-letter.
if expression.
Use the concepts of folds as part of your solutions to the following problem.
Parity Bits and Check Digits: In working with data, such as binary data or credit card numbers, a common practice involves adding one or more bits or digits to check a number's validity. Although many systems are in use, the simplest uses a single extra bit or digit. For example,
For simplicity in this problem we assume the first digit in a binary or decimal number is this parity bit or check digit.
Write a function checkCorrectDigit that has two
parameters:
numberBase, the base of the number system for
the numbers (e.g., 2 for binary, 10 for decimal)
numberString, a String representing the number
to be checked.
With these parameters, checkCorrectDigit returns
True if the first digit equals the sum of the remaining digits
(modulo the base) and False otherwise.
Programming Notes
digitToInt in
the Data.Char library converts a digit character to
an integer.
foldl
or foldr to determine the sum of digits, mod a
given base.
Months for a Savings Account to Double:
Write a function that takes a positive integer bound as parameter and
returns a list containing the number of months required for a savings
account to double with interest rates 1, 2,
..., bound percent.
Notes:
Grading Passwords: Since many modern computer systems use passwords as a means to provide protection and security for users, a major issue can be the identification of appropriate passwords. The main point should be to choose passwords that are not easily guessed, but which the user has a chance of remembering. For example, passwords related to birthdays, anniversaries, family names, or common words are all easily guessed and should be avoided.
Some common guidelines suggest that a password should contain at least 10 characters and include characters from at least three of the following categories:
Other guidelines indicate that elements of passwords should be pronounceable. One simple measure of this guideline suggests that any group of letters in a password should contain both vowels and consonants.
In Haskell, some functions are already built-in to test some of these
conditions. Specifically, Haskell's
Data.Char library identifies the functions
isAlpha, isDigit, isPunctuation,
isLower, and isUpper.
Reminder: To use this library, add the line import
Data.Char at the start of your file of functions.
contains pred str
which returns a function of one parameter, a string. When given a string parameter, the result will apply the predicate pred to each character in string str and which return 1 if some character meets this predicate test and 0 otherwise. (Note the returned values here are 1 or 0, NOT True or False!)
According to this scale, "HenryWalker" would receive a "B" grade, with points for string length, vowel, consonant, upper-case letter, and lower-case letter.
|
created 21 February 2020 revised 29 February-1 March 2020> typos corrected 9, 12 March 2020 |
|
| For more information, please contact Henry M. Walker at walker@cs.grinnell.edu. |