How to stop getting NIL from if statement in LISP - lisp

Sorry to ask this question, but I was learning optional parameters in LISP
Hence I created a function called "calculate-bill" which will take two parameters :
amount (this is the required parameter)
discount (this is an optional parameter)
So as you can guess if the discount is passed to the function it will cut its value from the amount else just print the amount.
Code :
(defun calculate-bill (amount &optional discount)
(if (null discount)
(format t "Total bill is ~d." amount))
(if discount
(format t "Total bill is ~d." (- amount discount))))
(write (calculate-bill 200 50))
(terpri)
(write (calculate-bill 200))
Here I had added null condition in if blocks.
I want my expected output to be :
Total bill is 150.
Total bill is 200.
But the current output is :
Total bill is 150.NIL
Total bill is 200.NIL
How can I avoid that "NIL" ?

You are doing double output. You call FORMAT and WRITE.
Now you need to find out whether you really want to do double output and, if not, which function to keep and which not.

The function calculate-bill prints out the message "Total amount is...", but this is just a side effect. The value returned by the function is, when there is no explicit return <value>, the value of the last executed function, which in this case is format, and the value of format is always nil when the output stream is T. So what the write function does is to print the value returned by calculate-bill which is nil. If you want calculate-bill to return the string then change the output stream of format to nil:
(format nil "Total bill is ~d." amount)
This creates a string which is not printed until you pass it to an output function like e.g. write.
As an extra suggestion, the function if can take three parameters: the test, the code to execute when the test is t, and the code to execute when the test is nil. So, in your case, it is far better to write:
(if (null discount)
(format t "Total bill is ~d." amount)
(format t "Total bill is ~d." (- amount discount)))

Optional parameter admit a default value, you could for example have a default discount of zero.
(defun calculate-bill (amount &optional (discount 0))
(check-type discount real)
(- amount discount))

Choose default for discount as 0 then you save the null testing.
Further, I would format to nil to just return the strings.
(defun calculate-bill (amount &optional (discount 0))
(format nil "Total bill is ~d." (- amount discount)))
write prints and returns. Only printing you achieve by (format t "~a" x).
(progn
(format t "~a" (calculate-bill 200 50))
(terpri)
(format t "~a" (calculate-bill 200)))
Alternatively you can include "~%" into format string instead using (terpri).
(progn
(format t "~a~%" (calculate-bill 200 50))
(format t "~a~%" (calculate-bill 200)))
I would optionally let display the result but return the string.
(defun calculate-bill (amount &key (discount 0) (verbosep nil))
(let* ((result (- amount discount))
(res (format nil "Total bill is ~d." result)))
(if verbosep
(format t "~a~%" res))
res))
Then set :verbosep t if you want the output visible in the terminal.
(calculate-bill 200 :discount 50 :verbosep t)
(calculate-bill 200 :verbosep t)

Related

Error when trying to set variable on LISP in a BMI problem

It's a simple college problem. I have to get the result using the BMI calc
My code below:
(write-line "BMI CALC")
(defun calc nil
(prog (w h) ; define p e h as local variables init with nil
(print "Weight: ")
(setq w (read))
(print "Height: ")
(setq h (read))
(return (/ w (* h h)))
)
)
(format t "BMI: ~D~%" (calc))
(setq bmi calc)
(cond
((< bmi 18.5) (print "Under weight"))
((< bmi 24.9) (print "Normal weight"))
((< bmi 29.9) (print "Overweight"))
((< bmi 34.9) (print "Obesity 1"))
((< bmi 39.9) (print "Obesity 2"))
(t (print "Obesity 3"))
)
And I got this result below:
BMI CALC
"Weight: " 78
"Height: " 1.7
BMI: 26.989618
*** - SETQ:variable CALC has no value
I really don't understand why this error.
I expected to print the BMI result, like "Under weight" or "Obesity 1".
What value do you think variable calc has on this line: (setq bmi calc)?
Because, as that error says, it doesn't have any.
Also, ending parentheses belong to the same line and you should read about let (a special operator for creating local variables).
Here is an improved version, which you can test by calling (my-bmi):
(defun calc ()
(prog (w h)
(print "Weight: ")
(setq w (read *query-io*))
(print "Height: ")
(setq h (read *query-io*))
(return (/ w (* h h)))))
(defun my-bmi ()
(print "BMI CALC")
(let ((bmi (calc)))
(format t "BMI: ~D~%" bmi)
(print
(cond
((< bmi 18.5) "Under weight")
((< bmi 24.9) "Normal weight")
((< bmi 29.9) "Overweight")
((< bmi 34.9) "Obesity 1")
((< bmi 39.9) "Obesity 2")
(t "Obesity 3")))))
Remarks about your code
You are using a global variable and writing the whole assignment as a script: namely you call (setq bmi ...) where bmi is not introduced by a let. And there is very few reusable parts. It's a bit of a bad practice to have side-effects like that. Here you are only writing a small program for an assignment so it is not very bad but, as you are also learning programming, you should also try to structure your code as small functions that don't have side-effects. This would be cleaner and would help the program grow in a real situation.
This is related to the previous point, but you are mixing different things in functions: parsing input, computing the BMI, outputting the result. In practice we often need to architecture code into layers: here you can have a pure mathematics data layer that computes the BMI, a input/ouptut layer that prompt for values, and a main program that glues all of those parts together.
Regarding I/O, you are likely to experience difficulties later if you don't flush the output or clear the input streams during your interactions with the user: as streams are typically buffered, sometimes format won't display immediately a string and this will be confusing. Using force-output is a way to flush all the buffers and ensure the user sees the text you wrote.
Alternative implementation
This is an example of how I would do it, using intermediate functions to encapsulate different tasks.
For example:
(defun bmi-formula (&key height weight)
(/ weight (* height height)))
This is a function that you can invoke interactively as follows in the REPL:
CL-USER> (bmi-formula :height 1.80 :weight 60)
18.51852
Notice that it only computes a value from numerical values, there is no parsing involved.
Likewise, you can convert a BMI to an obesity judgment as follows (as an aside, using BMI for that is an outdated practice, I am a bit suprised/annoyed that you are being asked to write this function):
(defun bmi-obesity-judgment (bmi)
(cond
((< bmi 18.5) 'underweight)
;; etc.
))
Here the function takes a bmi and returns a symbol. If you want later to localize your application in another language, you can map each symbol to a word in another language (e.g. (ecase judgment (underweight "magro") ...) please forgive my attempt at Portuguese).
Now you can write a function that grabs input from the user. Typically I would write an auxiliary function as follows:
(defun prompt (message type)
(loop
(clear-input *query-io*)
(fresh-line *query-io*)
(write-string message *query-io*)
(let ((value (read *query-io*)))
(when (typep value type)
(return value))
(warn "~a is not of type ~a" value type))))
What it does is ensure the input is cleared (there is no pending/buffered values to be read), it prints a new-line if necessary, a custom message, flush the output stream, then read a value and check if that value matches a given type. If that's not the case the code is executed in a loop.
For example, you can produce a property list as follows:
(defun input-bmi-parameters ()
(list
:weight (prompt "Weight (kgs): " '(integer 0 1000))
:height (prompt "Height (meters): " '(real 0 5))))
Finally you can piece all the functions together, write a title, write the output, etc:
(defun bmi-program ()
(format *query-io* "~&BMI Calculator. Enter weight and height and be judged.~%")
(let ((parameters (input-bmi-parameters)))
...))
Notice how I am using let to introduce a local variable parameters bound to a list (see also in prompt where I use local variable value). Apart from interacting with the *query-io*, the code does not have side-effects, all the state is contained in the function. That means that you could run more than one bmi-program in parallel, without them messing around with the same set of global variables: you could have a server where each connection executes bmi-program in a different thread, in which *query-io* is bound to the current TCP stream.

Average of entered numbers

In this program I want to get the average of the entered numbers not only the sum but I can't get the average and the "Enter a number: " keeps repeating.
here's my code
(princ"Enter how many numbers to read: ")
(defparameter a(read))
(defun num ()
(loop repeat a
sum (progn
(format *query-io* "Enter a number: ")
(finish-output)
(parse-integer (read-line *query-io* )))))
(format t "Sum: ~d ~%" (num))
(format t "Average: ~d ~%" (/ (num) a)) ;; I can't get the output for the average and the "Enter a number: " keeps repeating.
Enter how many numbers to read: 5
Enter a number: 4
Enter a number: 3
Enter a number: 2
Enter a number: 1
Enter a number: 3
Sum: 13
Enter a number: <-------
The reason that your prompt is repeating is because your code is calling NUM twice. Each time NUM is called, it asks for more input. A basic fix is to wrap the part of your program that shows the results in a function, just calling NUM once and binding the results to a variable:
(princ "Enter how many numbers to read: ")
(defparameter a (read))
(defun num ()
(loop repeat a
sum (progn
(format *query-io* "Enter a number: ")
(finish-output)
(parse-integer (read-line *query-io*)))))
(defun stats ()
(let ((sum (num)))
(format t "Sum: ~d ~%" sum)
(format t "Average: ~d ~%" (/ sum a))))
This "works", but when the code is loaded the user is prompted for input to set the A parameter; then the user needs to call STATS to enter data and see the results. This is awkward to say the least. But there are lots of little problems with the program. Here are some suggestions:
Avoid using a global parameter at all
Call READ-LINE instead of READ to get the count of input elements
Use *QUERY-IO* consistently
Call FINISH-OUTPUT consistently
Use better variable names (A and NUM are not very descriptive here)
Use ~A instead of ~D
The average may not be an integer; it could be a fraction. When its argument is not an integer, FORMAT uses ~A in place of ~D anyway, but it is probably better to be explicit about this. I would just use ~A in both lines of output.
You can write one function to incorporate all of the above suggestions:
(defun stats ()
(format *query-io* "Enter how many numbers to read: ")
(finish-output *query-io*)
(let* ((count (parse-integer (read-line *query-io*)))
(sum
(loop repeat count
do (format *query-io* "Enter a number: ")
(finish-output *query-io*)
summing (parse-integer (read-line *query-io*)))))
(format *query-io* "Sum: ~A~%Average: ~A~%" sum (/ sum count))
(finish-output *query-io*)))
Here COUNT replaces the earlier A, and it is a local variable within the STATS function. LET* is used instead of LET so that SUM can make use of COUNT, but nested LET expressions could be used instead. Note that SUM is bound to the result of the loop, which is the result from the SUMMING keyword. *QUERY-IO* is used consistently throughout, and printed output is always followed by FINISH-OUTPUT when its sequencing is important.
There is a lot that could be done to further improve this code. There is no input validation in this code; that should be added. It might be good to break STATS into smaller functions that separate input, calculation, and output operations. It might be nice to be able to handle floats in input and output.
Sample interaction:
CL-USER> (stats)
Enter how many numbers to read: 3
Enter a number: 1
Enter a number: 2
Enter a number: 2
Sum: 5
Average: 5/3
OP has asked in a comment:
I tried to add (min count) (max count)) in your given code to get the
minimum and maximum value but the output is the sum. How can I get
minimum and maximum number?
This is really a new question, but it points to shortcomings in the design of the above function that were hinted at with "It might be good to break STATS into smaller functions...."
Firstly, note that (MIN COUNT) or (MAX COUNT) will not be helpful, since we want the minimum or maximum of the data entered; COUNT is just the number of values that the user wants to enter. The original code directly summed the values as they were input; a more flexible approach would be to collect the input in a list, and then to operate on that list to get the desired results. The MIN and MAX functions operate on a number of values, not on a list, so we will need to use APPLY to apply them to a list of results. We can also apply + to the list of input values to get the sum of the elements in the list:
(defun stats ()
(format *query-io* "Enter how many numbers to read: ")
(finish-output *query-io*)
(let* ((count (parse-integer (read-line *query-io*)))
(data
(loop repeat count
do (format *query-io* "Enter a number: ")
(finish-output *query-io*)
collecting (parse-integer (read-line *query-io*))))
(min (apply #'min data))
(max (apply #'max data))
(sum (apply #'+ data))
(avg (float (/ sum count))))
(format *query-io* "Minimum: ~A~%" min)
(format *query-io* "Maximum: ~A~%" max)
(format *query-io* "Sum: ~A~%" sum)
(format *query-io* "Average: ~A~%" avg)
(finish-output *query-io*)))
Here the loop collects input in a list by using the COLLECTING keyword, and the result is bound to DATA. The desired calculations are made, and the results are printed.
This works, but it is really begging for a better design; there are too many different things happening in one function. You could move the display code into another function, returning MIN, MAX, etc. in a list or as multiple values. But then it would be even more flexible if the calculations were made independent of the code that gathers input by allowing STATS to return a list of input. The calculation of AVG requires COUNT; the new code could return COUNT too, but it is not needed anyway, since we can call LENGTH on the input list to get a count. One wonders why we need the user to enter a number of elements at all. What if we took numbers from the user until a non-numeric value is entered?
This answer has already gotten quite long, but below is some code that breaks the STATS function into smaller parts, refining it in the process. By using smaller function definitions that are more focused on their tasks, the functions can be reused or combined with other functions to accomplish other goals more easily, and it will be easier to modify the definitions when changes are required. The final function, PROMPT-FOR-STATS-REPORT does essentially what the earlier STATS function did. It is now much easier to add new functionality by modifying PROMPT-FOR-STATS. New accessor functions can be added as needed when PROMPT-FOR-STATS is augmented, and PROMPT-FOR-STATS-REPORT can be modified to display results differently, or to access and display newly added functionality.
This is by no means the optimal solution to OP's problem, if there is an "optimal" solution. I encourage OP to try to find ways to improve upon the design of this code. There are some comments scattered throughout:
;;; Here is a function to prompt for an integer. Since we want to receive
;;; non-numeric input `:JUNK-ALLOWED` is set to `T`. When input that can not be
;;; parsed into an integer is provided, `PARSE-INTEGER` will now return `NIL`
;;; instead of signalling an error condition.
(defun prompt-for-int (msg)
(format *query-io* "~A" msg)
(finish-output *query-io*)
(parse-integer (read-line *query-io*) :junk-allowed t))
;;; Here is a function to prompt for input until some non-numeric value
;;; is given. The results are collected in a list and returned.
(defun prompt-for-ints (msg)
(format *query-io* "~A~%" msg)
(finish-output *query-io*)
(let (input-num)
(loop
do (setf input-num (prompt-for-int "Enter a number ENTER to quit: "))
while input-num
collecting input-num)))
;;; Some functions to calculate statistics:
(defun count-of-nums (xs)
(length xs))
(defun min-of-nums (xs)
(apply #'min xs))
(defun max-of-nums (xs)
(apply #'max xs))
(defun sum-of-nums (xs)
(apply #'+ xs))
(defun avg-of-nums (xs)
(float (/ (sum-of-nums xs)
(count-of-nums xs))))
;;; A function to prompt the user for input which returns a list of statistics.
(defun prompt-for-stats (msg)
(let ((data (prompt-for-ints msg)))
(list (count-of-nums data)
(min-of-nums data)
(max-of-nums data)
(sum-of-nums data)
(avg-of-nums data))))
;;; Accessor functions for a list of statistics:
(defun get-stats-count (stats)
(first stats))
(defun get-stats-min (stats)
(second stats))
(defun get-stats-max (stats)
(third stats))
(defun get-stats-sum (stats)
(fourth stats))
(defun get-stats-avg (stats)
(fifth stats))
;;; A function that prompts for input and displays results.
(defun prompt-for-stats-report ()
(let ((results (prompt-for-stats "Enter some integers to view statistics")))
(format *query-io* "Count: ~A~%" (get-stats-count results))
(format *query-io* "Minimum: ~A~%" (get-stats-min results))
(format *query-io* "Maximum: ~A~%" (get-stats-max results))
(format *query-io* "Sum: ~A~%" (get-stats-sum results))
(format *query-io* "Average: ~A~%" (get-stats-avg results))
(finish-output *query-io*)))
Sample interaction:
CL-USER> (prompt-for-stats-report)
Enter some integers to view statistics
Enter a number ENTER to quit: 1
Enter a number ENTER to quit: 2
Enter a number ENTER to quit: 1
Enter a number ENTER to quit:
Count: 3
Minimum: 1
Maximum: 2
Sum: 4
Average: 1.3333334

trouble implementing eql and equals in clisp

So I have my sample code down below:
(defvar answer 0)
(defvar response "")
(defun question ()
(write-line "Enter your question")
(setq response (read-line))
(if (eql (subseq response 0 2) 'Is)
(print "T")
(print "nil")
))
The basic premise is to identify if the question asked begins with the word is.
The line that I think is giving me problems is (if (eql (subseq response 0 2) 'Is). I have other programs that use eql, but for some reason this use is always returning false no matter the input. I have already spent 3 hours trying a few different variations of this code, but none have worked. Any help would be much appreciated.
What you probably want is
(string-equal (subseq response 0 2) 'Is)
string-equal compares strings ignoring the character case. Unlike string-equal the string= function compares strings accounting for character case. An equivalent using string= function would be
(string= (string-upcase (subseq response 0 2)) 'Is)
Contrary to string comparison predicates the eql predicate compares lisp objects. For equal constant strings in compiled code it is likely to return true while in your case where one object is quoted literal and another object is computed character string it would fail.
For example (eql 'is 'is) returns true while (eql "is" "is") is false in interpreted code.

Looping through a list and appending to a new one

I'm new to Lisp. I'm trying to write a function that will take a list of dotted lists (representing the quantities of coins of a certain value), e.g.
((1 . 50) (2 . 20) (3 . 10)) ;; one 50 cent coin, two 20 cent coins, three 10 cent coins
and then convert it to list each coin by value, e.g.
(50 20 20 10 10 10)
Shouldn't be too hard, right? This is what I have so far. It returns NIL at the moment, though. Any ideas on fixing this?
(defun fold-out (coins)
(let ((coins-list (list)))
(dolist (x coins)
(let ((quantity (car x)) (amount (cdr x)))
(loop for y from 0 to quantity
do (cons amount coins-list))))
coins-list))
Since you can use loop, simply do
(defun fold-out (coins)
(loop
for (quantity . amount) in coins
nconc (make-list quantity :initial-element amount)))
alternatively, using dolist:
(defun fold-out (coins)
(let ((rcoins (reverse coins)) (res nil))
(dolist (c rcoins)
(let ((quantity (car c)) (amount (cdr c)))
(dotimes (j quantity) (push amount res))))
res))
If I were to do this, I'd probably use nested loops:
(defun fold-out (coins)
(loop for (count . value) in coins
append (loop repeat count
collect value)))
Saves a fair bit of typing, manual accumulating-into-things and is, on the whole, relatively readable. Could do with more docstring, and maybe some unit tests.
The expression (cons amount coins-list) returns a new list, but it doesn't modify coins-list; that's why the end result is NIL.
So you could change it to (setf coins-list (cons amount coins-list)) which will explicitly modify coins-list, and that will work.
However, in the Lisp way of doing things (functional programming), we try not to modify things like that. Instead, we make each expression return a value (a new object) which builds on the input values, and then pass that new object to another function. Often the function that the object gets passed to is the same function that does the passing; that's recursion.

Using remove-if-not with &key parameters

I have the following code, which is supposed to be a higher-order function that filters elements based on the &key arguments entered (in this case :year, :month and :type.
(defun filter-by (&key year month type)
"Remove members of the list not matching the given year and/or month and/or type, returns a
function that takes the list"
(lambda (lst)
(remove-if-not #'(lambda (element)
(when year
(equalp (local-time:timestamp-year (get-record-date element))
year)))
(when month
(equalp (local-time:timestamp-month (get-record-date element))
month)))
(when type
(equalp (get-type element)
type))))
lst)))
The problem is that unless all the keyword arguments are used, it will always return nil, I'm guessing because of how the when form behaves inside remove-if-not.
Is there anyway to make this work without resorting to multiple cond statements? The problem with cond is that I would have to specifically write down all the possible combinations of arguments used, which is ok for 3 arguments but it makes it very hard to extend if in the future I would like to use other keywords for filtering.
Common Lisp's keyword parameters have a special syntax that lets you tell
whether a parameter was supplied or not. I think you should be able to use
this to accomplish what you want.
Here is a working example, albeit with a slightly different data representation
since I don't have your definitions of local-time and get-record-date. You
should be able to easily adapt this to your code.
(defun my-filter-by (lst &key
(year nil year-p) ;; nil is the default
(month nil month-p) ;; year-p/month-p/day-p say whether
(day nil day-p)) ;; the argument was supplied
(remove-if-not
(lambda (element)
(let* ((year-okp (or (not year-p)
(equal year (cdr (assoc :year element)))))
(month-okp (or (not month-p)
(equal month (cdr (assoc :month element)))))
(day-okp (or (not day-p)
(equal day (cdr (assoc :day element)))))
(all-okp (and year-okp month-okp day-okp)))
all-okp))
lst))
And some examples:
(defparameter *lst* '(((:year . 2000) (:month . :may) (:day . 17))
((:year . 2000) (:month . :may) (:day . 18))
((:year . 2001) (:month . :aug) (:day . 2))
((:year . 2002) (:month . :jan) (:day . 5))))
(my-filter-by *lst*) ;; keeps everything
(my-filter-by *lst* :year 2000) ;; everything from 2000
(my-filter-by *lst* :year 2000 :day 17) ;; only 2000 / may 17