Welcome to Lilly's Lasagna Leftovers on Exercism's Common Lisp Track.
If you need help running the tests or submitting your code, check out HELP.md
.
If you get stuck on the exercise, check out HINTS.md
, but try and solve it without using those first :)
In Common Lisp a function's argument list is known as a 'lambda list'.
A lambda list can can have arguments of different types.
These different types are designated with the use of 'lambda list keywords' which all begin with &
.
The most commonly used types are optional, keyword and rest arguments types.
Every parameter in the lambda list after a particular lambda list keyword will be of that type.
A lambda list keyword can only be used once in a lambda list.
Lambda lists are also used in other constructs which will be discussed later such as destructuring and macros.
In Common Lisp a function can have some arguments that are optional.
These are designated in the lambda list by &optional
lambda list keyword.
A parameter will be bound to the value nil
if it is not specified.
If there are several optional parameters they are bound in order.
Default values can be specified for optional parameters.
Finally a symbol can be specified for each optional parameter which will be bound to true or false depending on whether that parameter was supplied by the caller of the function (this is referred to as the "supplied-p parameter").
(defun optional-parameters (&optional x (y 'default) (z nil z-supplied-p))
(list x y (if z-supplied-p (list :z-was-supplied z)
(list :z-was-not-supplied z))))
(optional-parameters) ; => (NIL DEFAULT (:Z-WAS-NOT-SUPPLIED NIL))
(optional-parameters 5 nil 10) ; => (5 DEFAULT (:Z-WAS-SUPPLIED 10))
In Common Lisp a function can have named parameters (referred to as "keyword parameters").
These are designated in the lambda list by the &key
lambda list keyword.
Keyword parameters are not required parameters.
Like optional parameters they can be given default values and symbols to bind to their 'supplied-or-not' state.
When calling a function with keyword parameters the name of the parameter as a keyword is used in front of the parameter value. Keyword parameters can be specified by the caller of the function in any order.
(defun keyword-parameters (&key x (y 'default) (z nil z-supplied-p))
(list x y (if z-supplied-p (list :z-was-supplied z)
(list :z-was-not-supplied z))))
(keyword-parameters) ; => (NIL DEFAULT (:Z-WAS-NOT-SUPPLIED NIL))
(keyword-parameters :y 5) ; => (NIL 5 (:Z-WAS-NOT-SUPPLIED NIL))
(keyword-parameters :z 10 :x 5) ; => (5 DEFAULT (:Z-WAS-SUPPLIED 10))
Care should be taken when combining optional and keyword parameters as the keyword name and argument could be consumed by optional parameters:
(defun could-be-confusing (&optional x y &key z) (list x y z))
(could-be-confusing :z 'huh?) ; => (:Z HUH? NIL)
In Common Lisp a function can have a parameter that will contain the "rest" of the arguments after any required or optional parameters are processed.
This parameter is designated by the &rest
lambda list keyword.
If all arguments to a function are used by other types of parameters then the rest parameter will be bound to an empty list.
If there are unused arguments then the rest parameter will be bound to a list of those arguments.
(defun rest-of-it (req &optional opt &rest rest) (list req opt rest))
(rest-of-it 1) ; => (1 NIL NIL)
(rest-of-it 1 2) ; => (1 2 NIL)
(rest-of-it 1 2 3) ; => (1 2 (3))
(rest-of-it 1 2 3 4 5) ; => (1 2 (3 4 5))
Lilly the Lisp Alien has definitely made a lot of Lasagna with your help. They want to thank you by giving you some leftovers. But first they have some feedback about one of the functions you helped them with.
In this exercise you are going to write some more functions to help Lilly with their Lasagna cooking as well as helping them split the leftovers with you.
You have six tasks, three about improving the existing Lasagna cooking functions and three about splitting the leftovers.
Lilly says they found using your preparation-time-in-minutes
a
little awkward to use since they had to count all the layers before
using the functions. Lilly wonders if you could make a version of the
function that takes the layers (any number of them) as arguments and
then calculate the preparation time based upon how many layers there
were? As a reminder, it takes 19 minutes to prepare a single layer.
(preparation-time 'sauce 'cheese 'right-handed-macaroni 'cheese 'sauce
'left-handed-macaroni 'sauce 'sauce 'cheese 'cheese)
;; => 190 (because there are 10 layers.)
While Lilly's parental-units always cooked their lasagna for 337 minutes, Lilly does admit that other Lisp Aliens like to cook their lasagna for longer or shorter amounts of time.
So it would be good if remaining-minutes-in-oven
would not use
expected-minutes-in-oven
but instead could take an optional
parameter to define if the time should be longer, very long, shorter
or very short. Longer or shorter means add or subtract 100 from the
default time of 337. Very long or very short means add or
subtract 200. If no parameter is provided, or the argument is 'normal'
then the default value of 337 should be returned.
(remaining-minutes-in-oven) ;; => 337
(remaining-minutes-in-oven :normal) ;; => 337
(remaining-minutes-in-oven :shorter) ;; => 237
(remaining-minutes-in-oven :very-short) ;; => 137
(remaining-minutes-in-oven :longer) ;; => 437
(remaining-minutes-in-oven :very-long) ;; => 537
Just after you finished that change Lilly remembers that there are
some Lisp Aliens that like really short cooking times. Effectively
they want it raw. So now the function should return 0 if NIL
was the
argument as well as the same values as in the previous task.
(remaining-minutes-in-oven) ;; => 337
(remaining-minutes-in-oven nil) ;; => 0
Now that you've helped improve the lasagna cooking functions there is a lot of lasagna! Lilly wants to repay you for all your help by splitting the leftovers with you. You will need to write a function that takes three parameters: the total amount of leftovers, the number of containers for leftovers Lilly found in their cupboards and the same for you. Because searching for leftover containers may take different amounts of time, and the exact amount of leftovers may be determined while yourself and Lilly are out searching your cupboards you both agree that the function should take the arguments in any order, so as each number is determined you can write it down. When you have all three numbers you can just close the parenthesis on the function call and evaluate it.
The function should return the amount of leftovers which are leftover, that is, the difference between the weight of left-overs and the total number of containers that you and Lilly found.
(split-leftovers :weight 20 :human 10 :alien 5) ;; => 5
(split-leftovers :weight 20 :alien 10 :human 2) ;; => 8
(split-leftovers :alien 12 :weight 20 :human 4) ;; => 4
After a few trips back and forth to your respective cupboards, you both agree that it would be great if the function could just assume that you or Lilly had... say 10 containers if it were not specified.
(split-leftovers :weight 20 :human 5) ;; => 5
(split-leftovers :weight 20 :alien 5) ;; => 5
(split-leftovers :weight 20) ;; => 0
Even better the function could assume that there is enough leftovers
to fit into the containers. So if the weight is not provided the
function should return :just-split-it
. However if the weight is
provided and it is nil
then it should return
:looks-like-someone-was-hungry
.
(split-leftovers :human 5 :alien 5) ;; => :JUST-SPLIT-IT
(split-leftovers :weight NIL :human 5 :alien 5) ;; => :LOOKS-LIKE-SOMEONE-WAS-HUNGRY
- @verdammelt