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, 2023 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.”
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 andappend
.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 integern
and returns then
th element of the list where the car of the list is 1st. 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 Java. 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 functionstring-append
for concatenating strings and the functionnumber->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 calledsum
(which you may assume is non-negative) and a list of integers, and returns an integer. It returnsn
ifsum
is greater than or equal to the sum of the firstn
elements of the list, but not greater than or equal to the sum of the firstn+1
elements. You may assume thatsum
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 yearday1
andday2
(integers) and returns a list of integers(m1 m2 ... mn)
wherem1
is the month ofday1
,m2
is the month of day1 + 1, …, andmn
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.
- Upload this file to Canvas by the project deadline.
Debugging in 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 Java, 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 Java 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))