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)
 

Haskell Worksheet 3, due Monday, March 3

This worksheet focuses largely on

These topics are covered in some detail in online readings on Syntax in functions and Higher order functions

Problems for this Worksheet 3 with Haskell

This worksheet expands experience with functional problem solving with Haskell. For this lab,

Local Bindings with for .. in and where

Use local bindings, using let .. in and/or where for all problems in this section.

  1. 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:

  2. 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)

    1. Write a version of this function, in which any helper functions are defined using guard constructs.
    2. Write another version of this function, in which any helper functions are defined using pattern matching. (Using an if expression for a parameter within a function call is fine here.)
  3. Normalizing Numbers on a List: Write a function normalize that takes a list of numbers as parameter and determines the sum of the numbers.

    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
      
  4. 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.

  5. 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.

  6. 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:

    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:

  7. 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:

Higher-order Functions

  1. 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.

    1. Write a function semAverageTemplate that has two parameters:
      • the first parameter is a list of weights for the various categories used in a course's grading
      • the second parameter is a list of student scores for the various categories
      In computing this sum, the function should write its own tail-recursive local function(s) to multiple corresponding elements of the two lists and adding the result.
    2. Write a function 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.
    3. Write a function otherCourse that is defined by supplying the weights [.25, .20, .30, .25] to semAverageTemplate, as described for the other course listed above.
  2. 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.)

    1. 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.

    2. Using 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.)
    3. Again, using 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.
  3. Function Addition and Composition: Write two higher order Haskell functions that have two functions of one variable, f and g, as parameters.

    1. Function 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)
    2. Function 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.

Maps and Filters

  1. 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.

    1. Write a Haskell 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.
      Note: This procedure should be quite short, using Haskell's filter function.
    2. Consider 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.
  2. 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.

    1. Write a function 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
        • if ch is found, then encodeChar returns the corresponding character in the cipherAlphabet.
        • if ch is not found, then encodeChar returns ch unchanged.
    2. In Haskell, the function 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.
    3. Use the one-parameter function from part b, with Haskell's map function to create a function encodeString that encrypts an entire string.

Lambdas

Use lambda expressions as part of your solutions to the following problems.

  1. 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.

  2. 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.)

Folds

Use the concepts of folds as part of your solutions to the following problem.

  1. 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:

    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

Miscellaneous

  1. 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:

Extra Credit Problem

  1. 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.

    1. The first part of this problem is to write local functions for other categories of characters.

      • Function isVowel has a single parameter and returns true if the parameter's value is a vowel and false otherwise.
      • Function isConsonant has a single parameter and returns true if the parameter's value is a consonant and false otherwise.
    2. The second part of this problem is to write a tail-recursive higher-order procedure that identifies the pattern of checking whether a string contains a character meeting a particular test. Specifically, write a higher-order procedure
      
         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!)

    3. The third part of this problem is to write procedure password-grade which takes a string as parameter and which gives a grade to that password according to the following grading scale:
      • Assign 1 point for each of the following elements in the password:
        • password contains at least 10 characters
        • password contains at least 1 vowel
        • password contains at least 1 consonant
        • password contains at least 1 upper-case letter
        • password contains at least 1 lower-case letter
        • password contains at least 1 numeric character
        • password contains at least 1 punctuation mark


      • Assign a letter grade to the password by applying the sum of the points above to the following.
        • 6 or 7 points: A
        • 5 points: B
        • 4 points: C
        • 3 points: D
        • 0 or 1 or 2 points: F

    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
Valid HTML 4.01! Valid CSS!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.