Programming Languages: Project 1
Calendar Calculations
You will write 11 Racket functions related to
calendar dates. In the descriptions below, a "date" is an Racket list containing
three values: the first part is the year, the second part
is the month, and the third part is the day. For example, September 16, 2017
would be represented by the '(2017 9 16). A "reasonable" date
would have a positive year, a month between 1 and 12, and a day no
greater than 31 (or less depending on the month). However, most
problems do not assume "reasonable" dates; solutions should
work for any date except where noted. A "day of year"
is a number from 1 to 365 where, for example, 33 represents February
2. (We ignore leap years entirely.)
In the examples, we use ==> to mean "evaluates to."
The sample solution is roughly 60-65 lines.
- Write a function before? that takes two dates and
evaluates to #t or #f. It evaluates to #t if the first
argument is a date that comes chronologically before the second argument. (If the
two dates are the same, the result is #f.)
Example: (before? '(2017 4 2) '(2017 5 1)) ==> #t
- Write a function number-in-month that takes a list of dates
and a month (i.e., an integer) and returns how many dates in the
list are in the month.
Example:
(number-in-month '((2017 1 2) (2016 2 1) (2019 2 3) (2017 12 1)) 2) ==> 2
- Write a function number-in-months that takes a list of
dates and a list of months (i.e., a list of integers) and returns
the number of dates in the list of dates that are in any of the
months in the list of months. Assume the list of months has
no number repeated (or if a number is repeated then dates in that
month are counted multiple times). Use your answer to the
previous problem.
Example:
(number-in-months '((2017 1 2) (2016 2 1) (2019 2 3) (2017 12 1)) '(12 2))
==> 3
- Write a function dates-in-month that takes a list of dates
and a month (i.e., an integer) and returns a list holding the
dates from the argument list of dates that are in the month.
Example:
(dates-in-month '((2017 1 2) (2016 2 1) (2019 2 3) (2017 12 1)) 2)
==> '((2016 2 1) (2019 2 3))
- Write a function dates-in-months that takes a list of
dates and a list of months (i.e., a list of integers) and returns a
list holding the dates from the argument list of dates that are in
any of the months in the list of months. Assume the list of
months has no number repeated (or if a number is repeated then
dates in that month should appear in the result list multiple times). Use
your answer to the previous problem and append.
Example:
(dates-in-months '((2017 1 2) (2016 2 1) (2019 2 3) (2017 12 1)) '(12 2))
==> '((2017 12 1) (2016 2 1) (2019 2 3))
- Write a function get-nth that takes a list and
an integer $n$ and returns the $n^{\text{th}}$ element of the list where
the car of the list is $1^{\text{st}}$. You may assume the list argument
will never have fewer arguments than needed based on the integer argument.
Example: (get-nth '(7 5 3 8 1) 2) ==> 5
Note that we wrote a very similar function with the same name in class, but this version starts the indices
at 1, not 0. This makes the following function, date->string, simpler because we usually consider
January to be month 1, not month 0.
- Racket contains a string data type that is has
similar functionality to strings in Python or C++. For instance, a literal string
in Racket is a sequence of characters enclosed by double quotes. Write a function date->string that takes a date and
returns a string of the form "April 11, 2017" (for
example). Use the function string-append for concatenating strings
and the function number->string for converting an
integer to a string. For producing the month part, do
not use a sequence of conditionals. Instead, use a list holding
12 strings and your answer to the previous problem.
Example: (date->string '(2016 7 29)) ==> "July 29, 2016"
Note: To write a literal list that contains string literals, you should use double
quotes around each individual string:
Right: '("Hello" "World")
Wrong: '(Hello World)
- Write a function number-before-reaching-sum that takes an
integer argument called sum (which you may assume is non-negative) and a
list of integers and returns an integer. It returns $n$ if sum is
greater than or equal to the sum of the first $n$ elements of the
list, but not greater than or equal to the sum of the first $n+1$
elements. You may assume that sum will never be greater than the sum of all
the numbers in
the list.
In other words, this function is answering
the question "How many items from the beginning of the list do I need to sum up before I reach (or surpass) the argument $n$?"
Examples:
(number-before-reaching-sum 2 '(3 2 4 2 5)) ==> 1
(number-before-reaching-sum 3 '(3 2 4 2 5)) ==> 1
(number-before-reaching-sum 4 '(3 2 4 2 5)) ==> 2
(number-before-reaching-sum 5 '(3 2 4 2 5)) ==> 2
(number-before-reaching-sum 6 '(3 2 4 2 5)) ==> 3
- Write a function what-month that takes a day of year
(i.e., an integer between 1 and 365) and returns an integer representing the month that day
is in (1 for January, 2 for February, etc.). Use a list holding 12
integers and your answer to the previous problem.
Examples:
(what-month 1) ==> 1 ; January 1
(what-month 31) ==> 1 ; January 31
(what-month 32) ==> 2 ; February 1
(what-month 138) ==> 5 ; May 18
- Write a function month-range that takes two days of the
year day1 and day2 (integers) and returns a list of integers
$(m_1\ m_2\ \ldots\ m_n)$ where $m_1$ is the month of day1,
$m_2$ is the month of day1 + 1, ..., and $m_n$ is the
month of day2. Note the result will have length
day2 - day1 + 1 or length 0 if day1 > day2.
Example:
(month-range 30 34) ==> '(1 1 2 2 2)
- Write a function earliest that takes a list of dates and
returns the earliest date in the list. The argument list is guaranteed to contain
at least one date.
Example:
(earliest '((2017 1 2) (2016 2 1) (2019 2 3) (2017 12 1))) ==> '(2016 2 1)
Grading
Solutions should be:
- Correct
- Written in good style, including indentation and line breaks
- Written using features discussed in class. In
particular, you must not use any mutation operations nor arrays (even though
Racket has them).
Turn-in instructions
- Put all your solutions in one file, named proj1_lastname_firstname.rkt, where
lastname is replaced with your last name, and
firstname is replaced with your first name.
- Upload this file to Moodle by the project deadline.
Debugging Racket
Debugging in a functional language can be tricky because normally programming in a "pure" functional manner
means we don't use any statements with side effects, such as printing to the screen. Racket, however, includes
plenty of functions that will display things on the screen,
if we temporarily relax the strict rules of functional programming.
I recommend making plenty of use of the displayln
function when debugging. This function takes
one argument and prints it to the screen, along with a newline at the end. To print multiple things on
one line, you have to combine the pieces in some way; the easiest way is with the list
function,
which makes a list out of its arguments.
displayln
must always take one single argument, so if you want to print a blank line by itself, use (newline)
As an example, here's the solution code for our sublist function:
(define (sublist lst start end)
(cond ((and (= start 0) (= end 0)) '())
((= start 0) (cons (car lst) (sublist (cdr lst) 0 (- end 1))))
(#t (sublist (cdr lst) (- start 1) (- end 1)))))
Here it is again, after adding some debugging code:
(define (sublist lst start end)
(newline)
(displayln (list "lst=" lst "start=" start "end=" end))
(cond ((and (= start 0) (= end 0)) '())
((= start 0)
(displayln (list "start is 0" "car=" (car lst)))
(cons (car lst) (sublist (cdr lst) 0 (- end 1))))
(#t
(displayln "start is not 0")
(sublist (cdr lst) (- start 1) (- end 1)))))
I've added debugging statements to print at the beginning of each function call,
and inside the function call to illustrate which recursive case is being triggered.
When I call (sublist '(4 5 2 3 7 5) 1 3)
, I now see the following output:
> (sublist '(4 5 2 3 7 5) 1 3)
(lst= (4 5 2 3 7 5) start= 1 end= 3)
start is not 0
(lst= (5 2 3 7 5) start= 0 end= 2)
(start is 0 car= 5)
(lst= (2 3 7 5) start= 0 end= 1)
(start is 0 car= 2)
(lst= (3 7 5) start= 0 end= 0)
'(5 2)
>
Notice how the displayln
output is easily distinguished from the return value
of the function call by the colors of the text.
Like you do in Python or C++, always comment out or remove debugging statements before turning
in your code!
Other notes on Racket
- Because the DrRacket interpreter supports multiple varieties of the Racket language, always begin your source code files with a line
containing the directive
#lang racket
and nothing else.
- Racket contains two types of comments. A semicolon begins a line comment. Everything past the semicolon to the
end of the current line is ignored by Racket.
(define (add1 n) (+ 1 n)) ; This function adds one to its argument.
Block comments in Racket are enclosed by #|
and |#
, similar to how C++ uses /*
and */
.
These comments may extend over multiple lines.
#| Here is a single-line comment. |#
#| This comment
has multiple lines! |#
- To write a literal list that contains sub-lists, do not use additional quotes:
Right: '(1 2 (3 4))
Wrong: '(1 2 '(3 4))