CSC 153 Grinnell College Spring, 2007
 
Computer Science Fundamentals
 

Supplemental Problems

Quick links: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14

Supplemental Problems extend the range of problems considered in the course and help sharpen problem-solving skills. Starred problems may be turned in for extra credit.

Note: Since this course emphasizes functional problem solving with Scheme and object-oriented problem solving with Java, the nature of programs should reflect the language being used. For example, Scheme programs may not use mutation (e.g., set!), except in the context of object-oriented problem solving (e.g., labs 26-31). Scheme programs that use of set!, particular in the context of global variables, will receive 0 points.

Format: In turning in any programs for the course, please follow these directions:

  1. The first three lines of any Scheme program should be comments containing your name, your mailbox number, and an identification of assignment being solved. For example:
    
        ;;; Henry M. Walker
        ;;; Box:  Science Office
        ;;; Supplemental Problem 
    
    Also, a comment is needed for every procedure, stating in English what that procedure is supposed to do.

  2. Obtain a listing of your program and a record of relevant test runs using the script command:

  3. Either write on your printout or include a separate statement that argues why your program is correct, based upon the evidence from your test runs.

Processing Dates:
  1. A common processing task involves analyzing dates, such as January 8, 2003. For example, one often must determine if a date is the last one in a year or the last one in a month.

    For this problem, dates will have three parts: month day year, where the year and day are integers and the month is a symbol (e.g., january, february, etc.). Write the following procedures:

    Notes:

    1. January, March, May, July, August, October, and December have 31 days.

    2. April, June, September, and November have 30 days.

    3. February has 28 days in non-leap years, but 29 days in leap years. A leap year occurs in years when the year is divisible by 4, except that century years are not leap years unless they are divisible by 400. Thus, the years 1999 and 1900 are not leap years, while 1996 and 2000 are leap years.

    Examples: These procedures should produce the following results:

    
    (year-end? 'march 31 2004)      ===> #f
    (year-end? 'december 30 2004)   ===> #f
    (year-end? 'december 31 2004)   ===> #t
    (month-end? 'january 8 2004)    ===> #f
    (month-end? 'january 31 2004)   ===> #t
    (month-end? 'february 28 2003)  ===> #t
    (month-end? 'feburary 28 2004)  ===> #f
    (month-end? 'february 28 2000)  ===> #f
    
    Note: In this and all other supplemental problems, you may define additional helper functions as you wish. From a user's perspective, the required procedures must be available using the prescribed parameters. Beyond that interface, you may handle processing details in whatever way seems convenient.

Newton's Method:

  1. Given a continuous function y = f(x), it is natural to want to know values of x, called roots, for which f(x) = 0. One approach to this problem is the Bisection Method, discussed in the lab on designing recursive procedures. The Bisection Method works for any continuous function, provided that you can find points a and b, such that f(a)<0 and f(b)>0. However, the Bisection Method can take some time to find good approximations for roots of a continuous function.

    This problem discusses a different approach, called Newton's Method. Newton's method is remarkably efficient for typical functions, but it has some limitations:

    However, when it works, Newton's method produces very accurate approximates to roots in a very short amount of time.

    The following description is a slightly-edited version of what appears in Section 5.1 of Problems for Computer Solutions Using Basic by Henry M. Walker, Winthrop Publishers, 1980.

    The following algorithm, which is credited to Newton, can often be used to approximate the roots of a differentiable function efficiently and accurately. In the algorithm we begin by making a guess x0 for the root of a function f(x). The algorithm then gives us a "closer" approximation x1 to the root. We can then use x1 as our guess, apply the algorithm again, and get a still better guess. This process continues until we are satisfied with our approximation.

    The key, therefore, is to decide how to improve a guess a. Here we observe that the tangent line to a cure is usually a good approximation to the curve "near" the point of tangency. Thus, if the point of tangency is near the root, the tangent line will cross the horizontal coordinate axis near the point where the curve crosses the axis.

    We look at the equation of the tangent line to the curve y = f(x) at the point where x=a, The point of tangency is ((a, f(a)) and the slope at this point is f'(a), so by the point-slope form of a line, the equation of the tangent is given by

    y - y0 = m(x - x0)
    or
    y - f(a) = f'(a)(x - a)

    This tangent crosses the x-axis at a point (b, 0), and since this point is on the tangent, we must have

    0 - f(a) = f'(a)(b - a)
    or
    b = a - f(a) / f'(a)

    The geometry of this process is shown in the following diagram.

    Approximating a Root by a Tangent

    We can now describe Newton's method more precisely.

    First, choose an initial approximation, call it x0, for the root (this may be only a blind guess or it may be based on some knowledge, such as a rough sketch of the graph). Secondly, construct a sequence of numbers:

    x1 = x0 - f(x0)/ f'(x0)
    x2 = x1 - f(x1)/ f'(x1)
    x3 = x2 - f(x2)/ f'(x2)

    Because the above formula gives the x-intercept of the tangent to the curve corresponding to x= = a, the above iterative process can be graphically described by the dotted line in the following graph.

    Approximating a Root by a Tangent

    The general form of Newton's method can be written:

    1. x0 arbitrary
    2. xn+1 = xn - f(xn)/ f'(xn); (n = 0, 1, 2, ...)
    3. stop when you are close to the root.

    We expect that for reasonably shaped curves, the xn values approach the actual root as n increases.

    Notes:

    1. When do you stop the process? There are various ways of deciding when you are close to the root. None of them are completely satisfactory. The procedure that we will use is to choose some measure on the error err that we are willing to tolerate, for example err = 10-6. We then continue the iteration process until |f(xn)| ≤ err

      This procedure cannot give acceptable results for every function, because it states that x = 1 is an acceptable approximation to the root x = 0 of the function f(x) = err * x.

    2. The following figure indicates a situation where Newton's method fails completely because of an unfortunate initial guess of either x = a or x = b.
      A Case wheter Newton's Method Fails

    Problem: Write a Scheme procedure find-root that takes four parameters, an initial guess, an accuracy err, the function f, and the function's derivative fprime. find-root then should follow Newton's Method to approximate the root of a function f.

    Note that find-root may use one or more helper procedures, as long as find-root provides the specified interface.

Shopping Bargains:

  1. A store has two policies to encourage shoppers to buy as many goods as possible:
    1. The purchase of 1 item is full price, but the cost of a second identical item is 10% off, the cost of a third identical item is 20% off, etc., although no item is sold for less that 10% of its original price.
    2. The shop offers a global discount for high spenders:
      • If a shopper's overall bill is between $100 and $200 (inclusive), then the shopper is given a 15% discount (beyond any reduction for purchases of multiple identical items).
      • If a shopper's overall bill is over $200 (i.e., $200.01 or higher), then the shopper is given a 25% discount (beyond any reduction for purchases of multiple identical items).

    Write a procedure compute-bill that takes a list of original item costs as input and computes the total charge a customer will pay after discounts. For this exercise, assume that the costs of identical items will be grouped together on the list, and all consecutive equal costs relate to identical items. To illustrate, consider this example:

    
    (compute-bill '(1.25 8.73 5.55 5.55 5.55 12.84 5.55 5.55 20.30 20.30 20.30 20.30))
    

    Here, we assume the first item purchases costs $1.25 and the second costs $8.73. The third, fourth, and fifth items have the same cost, so we assume they are identical items (the cost of these items after multiple-item discount will be 5.55 + 0.9*5.55 + 0.8*5.55). The sixth item costs 12.84. The next two items again cost 5.55, but these must represent a different item than the earlier ones of the same price, since these costs are not contiguous with the previous run of 5.55 prices. Thus, the cost of the seventh and eighth items is 5.55 + 0.9*5.55. The last four items again are considered identical, with costs 20.30 + 0.9*20.30 + 0.8*20.30 * 0.7*20.30.

    Notes:

    • Processing should make one pass only through the initial list of costs.
    • compute-bill may call any helper functions that seem convenient, although the user interface for compute-bill must be as prescribed. (However, even with helper functions, processing should not make multiple passes through the data.)
    • The procedure should return 0 if the initial list of costs is empty.
    • The procedure should produce an error message, if any of the items on the list is a non-number or if any of the items are negative numbers.

Course Grading

  1. The syllabus for this course states,

    "While some flexibility may be possible in determining a final semester grade, the following percentages approximate the relative weights attached to various activities in this course.
    Lab. Write-ups: 35% Programs: 15% Hour Tests: 30% Final Exam: 20%"

    A recent offering of CSC 105 involved these activities and percentages:
    Lab. Write-ups: 30% Research Exercises: 10% Class Questions: 10% Papers: 50%

    Also, a past offering of CSC 213 involved these activities and percentages:
    Labs and Programs: 60% Hour Tests: 20% Exams: 30%

    Generalizing, final grade averages typically involve some collection of grade categories and percentages for those categories. In computing a student's final score, student scores in each category are multiplied by the prescribed weight. Of course, the student scores vary but the weights are the same for the entire course. This problem asks you to write a higher-order procedure that will compute averages for students in a course:

    • Write a higher-order procedure grade-template that takes a sequence of category percentages for a course as parameters and returns a procedure. This returned procedure similarly takes a sequence of student grades as parameter, and returns the student's final semester score.

    For example, suppose a student in this course had a Lab Write-up average of 80%, a program average of 90%, an hour test average of 95%, and a final exam average of 85%. Then procedure grade-template could be used to compute the student's semester average as follows:

    
    (define course-evaluator (grade-template 0.35 0.15 0.30 0.20))
    (course-evaluator 80 90 95 85)
    
    

    As a result, the computer should return the weighted sum:

    0.35 * 80 + 0.15 * 90 + 0.30 * 95 + 0.20 * 85

    Notes:

    • The parameters for grade-template and course-evaluator are sequences, not lists.
    • Although any correct higher-order procedure is eligible to receive full credit, bonus points will be given to a procedure whose underlying computation is done on one line without recursion.

Reading Test Data

  1. File test.data in directory ~walker/151s/labs contains information on test results for a certain class. Each line of the file contains a students first and last name and the results of three hour tests. Write a program that computes the average test score for each student, the maximum and the minimum scores for each test, and prints the results in a nicely formatted table. For example, the table might have the following form:

    
         Name                        Test
    First        Last        1       2       3     Average
    Egbert       Bacon      88      85      92      88.33   
    .
    .
    .
    Maximum                 −−      −−      −−
    Minimum                 −−      −−      −−
    

A Simple Class

  1. Computer monitors divide an image into a grip of pixels. Each pixel appears as a colored dot. Pixels are identified by coordinate system, with horizontal and vertical (x and y) coordinates. For example, on a Linux system, the pixel at the upper left of the monitor is given the coordinates (0, 0), and pixel part-way down the screen on the left might have coordinates (0, 300).

    When displaying rectangles (e.g., windows) on a monitor, at least two approaches may be used:

    • the coordinates of the upper-left corner of the window are recorded, together with the length and width of the rectangle
    • the horizontal (x) coordinates of the left and right of the window are stored as are the vertical (y) coordinates of the top and bottom of the window.

    In this problem, you are to use the second approach. Often, these coordinates are grouped as the upper-left coordinates and lower-right coordinates, but for this introductory problem you should store the four values as separate integer fields.

    Using Java, write a simple Rectangle class with the following properties:

    1. The Rectangle class should have exactly four integer fields for the coordinates of the four sides of a rectangle.
    2. Two constructors should be provided:
      • One constructor should take no parameters, set the upper left corner to (0,0) and set the other coordinates as needed so the rectangle's width will be 800, and the length 600.
      • One constructor should take four parameters −− the four coordinates of the rectangle boundaries.
    3. Four methods, getLeft, getRight, getTop, and getBottom, should return the relevant integer coordinates.
    4. Two methods, setLeft and setTop should allow the user to designate the given coordinate. (In practice, one might check that the coordinates were on the screen. However, such error checking is beyond the scope of the little Java covered so far in the course, and thus is not expected for this assignment.)
    5. A method setWidth that adjusts the coordinate of the right side of the rectangle, so that the rectangle will begin at the designated left side of the rectangle and extend for the width given.
    6. A method setHeight that adjusts the coordinate of the bottom of the rectangle, so that the rectangle will begin at the designated top and extend downward for the given height.
    7. A method computeArea that computes the area of the rectangle.
    8. A method toString that returns a string that gives the coordinates of the rectangle in a readable form.
    9. A method main that provides suitable test cases for the constructors and other methods.

Partitions and Medians

  1. This problem develops an efficient way to find the kth-smallest element in an array.

    1. Partition: Write a method

      
      int partition (int a[], int left, int right)
      
      which rearranges those elements within the a array with indices between left and right, so that

      • middle is defined as an index with left <= middle <= right,
      • a[left] is moved to a[middle],
      • each element a[left], a[left+1], ..., a[(middle)-1] is less than or equal to the new a[middle], and
      • each element a[(middle)+1], ... a[right-1], a[right] is greater than or equal to the new a[middle].

      Also, the method returns the final value of middle.

      Thus, the array segment a[left] ... a[right] is rearranged as required, to give a new arrangement of values a[left] ... a[(middle)] ... a[right], where all array elements before position middle have values <= a[(middle)] and where a[(middle)] is less than or equal to all array elements after position middle.

      Include your partition method in an appropriate class and testing suite , so that you can adequately check the correctness of your code.

      Discussion: Work within partition should be done with a single pass through the elements a[left] ... a[right], as follows.

      1. move upward in the array through a[right], a[right-1], ... until finding an element a[r_spot] where a[r_spot] < a[left], if such an element exists.

      2. move downward in the array through a[left], a[left+1], ... until finding an element a[l_spot] where a[l_spot] > a[left], if such an element exists.

      3. swap a[l_spot] and a[r_spot].

      4. continue steps 1, 2, and 3 until searching all elements in the array segment. (At this point, "small" values will have been moved early within the array segment, while "large" values will have been moved late.)

      5. place a[left] in its appropriate position a[middle], where middle is the index where l_spot and r_spot have come together.

    2. Finding Median Values: The partition method of part A may be used to find the kth smallest element in an array segment a[left], ..., a[right], as follows:

      1. If there is only one element in a[left], ..., a[right], then one expects k = 1, and one can return that element.

      2. Use the partition procedure to rearrange a[left], ..., a[right] into a[left] ... a[(middle)] ... a[right] as described in problem 1.

      3. If k = ((middle)-left+1), then a[middle] is the desired element, and stop.

      4. If k <= ((middle)-left), then apply this algorithm recursively to find the kth smallest element in an array segment a[left], ..., a[(middle)-1].

      5. If k >= ((middle)-left), then apply this algorithm recursively to find the k-((middle)-left)-1th smallest element in an array segment a[(middle)+1], ...,a[right].

      Problems for part B:

      1. Write a procedure

        
        int select (int a[], int n, int k)
        

        that uses the above algorithm and procedure partition to find the kth smallest element in an array a, where a has n elements.

      2. Write a procedure

        
        int median (int a[], int n)
        

        that uses the above algorithm and procedure partition to find the median element in an array a, where n is the size of the array.

Deleting Duplicates on a List

  1. Write methods deleteDuplicates and deleteDuplicatesOrdered for a singly-linked-list class. For both methods, when duplicate names appear, the first entry on the list should be retained, and all subsequent duplicate entries should be deleted.

    • For deleteDuplicates, you should not assume the list is ordered. Also, this method should use an iterative solution, with no more than 3 temporary [pointer] variables (including function parameters).
    • For deleteDuplicatesOrdered, you should assume the list is ordered. Also, this method should use a recursive solution, with no more than 3 temporary [pointer] variables (including function parameters).


Any of the following problems may be done for extra credit. As noted in the course syllabus, however, a student's overall problems' average may not exceed 120%.

Unusual Canceling

  1. The fraction 64/16 has the unusual property that its reduced value of 4 may be obtained by "canceling" the 6 in the numerator with that in the denominator. Write a program to find the other fractions whose numerators and denominators are two-digit numbers and whose values remain unchanged after "canceling."

    Of course, some fractions trivially have this property. For example, when numerator and denominator are multiples of 10, such as 20/30, one can always "cancel" the zeroes. Similarly, cancellation is always possible when the numerator and denominator are equal, as in 22/22. Your program should omit these obvious cases.

Roman Numerals

  1. Write a procedure that reads an integer N between 1 and 1000 from the keyboard and prints the Roman numerals between 1 and N in alphabetical order.

City Data

  1. (*)The file ~walker/151p/labs/lab26.dat contains several items of information about large American cities. More specifically, in ~walker/151p/labs/lab26.dat , each entry consists of the name of the city (line 1), the county or counties (line 2) and the state (line 3) in which it is situated, the year in which it was incorporated (line 4), its population as determined by the census of 1980 (line 5), its area in square kilometers (line 6), an estimate of the number of telephones in the city (line 7), and the number of radio stations (line 8) and television stations (line 9) serving the city. Thus a typical entry reads as follows:
    
    Albuquerque
    Bernalillo
    New Mexico
    1891
    331767
    247
    323935
    14
    5
    
    A blank line follows each entry, including the last.

    Write a procedure which has a filename as parameter and which answers the following questions about the cities represented in the data files.

    1. Which of those cities has the highest population density (population divided by area)?
    2. Which of these cities has over one million telephones?
    3. Which city has the lowest per capita number of radio and television stations (together)?
    The answers to each of these questions should be printed neatly and clearly by the procedure.

File Analysis

  1. (*)Write a procedure file-analysis that takes the name of a file as its argument, opens the file, reads through it to determine the number of words in each sentence, displays the total number of words and sentences, and computes the average number of words per sentence. The results should be printed in a table (at standard output), such as shown below:
    
         This program counts words and sentences in file "comp.text ".
    
         Sentence:  1    Words: 29
         Sentence:  2    Words: 41
         Sentence:  3    Words: 16
         Sentence:  4    Words: 22
         Sentence:  5    Words: 44
         Sentence:  6    Words: 14
         Sentence:  7    Words: 32
    
         File "comp.text" contains 198 words words in 7 sentences
         for an average of 28.3 words per sentence.
    
    In this program, you should count a word as any contiguous sequence of letters, and apostrophes should be ignored. Thus, "word", "sentence", "O'Henry", "government's", and "friends'" should each be considered as one word.

    Also in the program, you should think of a sentence as any sequence of words that ends with a period, exclamation point, or question mark.
    Exception: A period after a single capital letter (e.g., an initial) or embedded within digits (e.g., a real number) should not be counted as being the end of a sentence.
    White space, digits, and other punctuation should be ignored.

Information on the 1997-1998 Iowa Senate

  1. File /home/walker/151s/labs/ia-senate contains information about the members of the 1997-1998 Iowa Senate. After a title line and a blank line, a typical line has the following form:

    
    Angelo          Jeff        44      Creston           IA 50801
    Kramer          Mary        37      West Des Moines   IA 50265
    Lundby          Mary        26      Marion            IA 52302-0563
    

    Thus, a typical line gives the last name, the first name, the district number, the town of residence, the state (always IA), and the town's zip code. The information in these lines is arranged in columns.

    Design and write a Scheme program that reads in data from this file and creates two output files, senators-by-city and senators-by-zip-code, in the current working directory. The senators-by-city file should contain the same data as the source file, in the same format (including capitalization), but with the lines arranged alphabetically by city (column 4). The other file, senators-by-zip-code, should contain a list of all senators in the following format

    
    Jeff Angelo
    Creston, IA 50801
    

    A blank line should appear after each senator and city address. In this format, the name appears on a first line (first name, then last), and the city, a comma, the state, and zip code is on the next line — separated by single spaces in the format shown. Note that a variation of this format (with a street address, if available) might be used for a mailing label.

A Gambling Simulation

  1. In private games, different types of gamblers set different personal limits according to the following table:

    Gambler Category Amount at
    Start of Evening
    Amount Bet
    per Game
    Game Payoff Probability of
    Winning Game
    Total to Stop
    Average Gambler $25 $2 $4 0.3 $50
    Low-risk Gambler $10 $1 $1 0.5 $18
    High-risk Gambler $50 $5 $15 0.2 $150

    Interpreting this table, for an average gambler, the gambler starts the evening with $25; he bets $2 on each game and stops when he either runs out of money or has a total of $50. To be more specific, for a specific game, the gambler bets $2. If the gambler loses the bet, then $2 is deducted from his account. If the gambler wins the bet, then the gambler wins a payoff amount, and the gambler's new balance is increased by that payoff — the $2 is not deducted. For example, if the payoff is $5 and if the gambler starts the game with $20, then the gambler's new balance would be $18 if the gambler loses a bet and $25 if the gambler wins the bet.

    The following problems will allow you to investigate the likelihood of winning by types of gamblers, by simulating games each evening over a period of 1000 nights of gambling. To accomplish this simulation, you are to proceed in three steps:

    1. Write a PlayGame class that has one method:

      • public String betResult (double prob) that takes a probability as parameter and that uses Java's random number generator to determine if the player wins or loses a specific bet. betResult returns "won" or "lost" according to the corresponding outcome.
    2. Write a Gambler class with these characteristics:

      • The Gambler class has fields for
        
           double betSize;   /* size of each bet */
           double payoff;    /* amount earned (in addition to bet) if bet won */
           double prob;      /* probability of winning a bet */
           double start;     /* amount in gambler's purse at start of evening */
           double purse;     /* current amount gambler holds */
           double quitAmount /* amount gambler must earn before quitting for evening */
        
      • The Gambler class has these methods
        • a constructor, that has parameters for each of the object fields above (except purse which is initialized to start)
        • public String playOneGame (playGame game), that normally places one bet (with the game object), updates the purse field. If the purse is less than betSize or greater than or equal to the quitAmount, then playOneGame retains the current purse without playing. The possible return values are "Won for Evening", "Won Game, Still Playing", "Lost Game, Still Playing" and "Lost for Evening".
        • public String playEvening (playGame game), that starts with the current purse and plays successive games until either the purse is less than betSize or greater than or equal to the quitAmount. The possible return values are "Won for Evening" and "Lost for Evening".
    3. Write 3 subclasses, AverageGambler, LowRiskGambler, and HighRiskGambler, that extend class Gambler by including a constructor with no parameters that set the fields according to the above table.

    4. Write a SimulateGambling class that creates 1000 of each type of Gambler, records how many Gamblers of each type win over a full evening, and prints the results in a table (filling in the question marks below):

      
         Gambler Category    Evenings Won     Evenings Lost
         Average Gambler         ???              ???
         Low-risk Gambler        ???              ???
         High-risk Gambler       ???              ???
      

This document is available on the World Wide Web as

http://www.cs.grinnell.edu/~walker/courses/153.sp07/suppl-prob.shtml

created: 6 February 1997
last revised: 16 April 2007
Valid HTML 4.01! Valid CSS!