this is part of a my first homework in common lisp.
We have to define a variable representing any 10 random integers from 0 to 100:
I am not sure what is being asked here.
Should I write: (setf var1 '())
Then, we have to define a function that generates a list of 10 random integers and returns a list that contains those numbers.
Here is what I wrote, but I keep getting NIL as my output. Do you know what's wrong with the code:
(setf *random-state* (make-random-state t))
(setf var1 '())
(defun randlist (var1)
(dotimes (i 10)
(setf temp (random 101))
(cons 'temp var1 ) ) )
Write a function that returns a new list with 10 random integers:
(defun randlist ()
(let ((lst ()))
(dotimes (i 10)
(setf lst (cons (random 101) lst)))
lst))
And then you can assign its result to a variable:
(defvar var1 (randlist))
DEMO.
You don't need to initialize *random-state* like that. That's the default anyway. You'd need to initialize it if you have a different implementation of random number generator.
While (setf whatever nil) will probably work, this is not the way you declare variables. setf is a macro for assigning values to symbols. Normally you first create symbols and then assign to them. If you need a dynamic variable, your options are defparamter or defvar macros, e.g.:
(defparameter var1 nil)
(defvar var2 nil)
Your function randlist returns nil as this is what dotimes macro returns, and this is the last form executed in the function. You could've changed it to return var1, but the use of dynamic variables, especially in this way is not a good coding practice. It sounds from the task as if you were asked to simply write a function that returns a list of ten integers, no need to also assign that to a dynamic variable outside the function.
(defun randlist ()
(loop for i from 0 below 10 collect (random 101)))
Would be perhaps the simplest way to do it. But there are really lots and lots of other ways :)
Just for fun, here's an alternative, it's not particularly good, though. I hoped for it to be shorter, but it wasn't really :)
(defun random-list ()
(mapcar #'random (make-list 10 :initial-element 101)))
Lets do this with recursion:
(defun rnd-list()
(rnd-list-1 10 10))
(defun rnd-list-1 (limit count)
(cond ((<= count 0)
nil)
(t
(cons (random limit)
(rnd-list-1 limit (1- count))))))
Or you can do like this --
(defun rnd-list()
(let (lst)
(dotimes (n 100)
(push (random 101) lst))
(reverse lst)))
Empowering do, iterate over n and define lst as a void list; when n reaches 10 return lst. On every iteration push one random number to lst.
(do ((n 1 (incf n))
(lst ()))
((> n 10) lst)
(push (random 101) lst))
=> (31 35 59 19 84 54 62 44 69 54)
Related
Prelude
In Raku there's a notion called infinite list AKA lazy list which is defined and used like:
my #inf = (1,2,3 ... Inf);
for #inf { say $_;
exit if $_ == 7 }
# => OUTPUT
1
2
3
4
5
6
7
I'd like to implement this sort of thing in Common Lisp, specifically an infinite list of consecutive integers like:
(defun inf (n)
("the implementation"))
such that
(inf 5)
=> (5 6 7 8 9 10 .... infinity)
;; hypothetical output just for the demo purposes. It won't be used in reality
Then I'll use it for lazy evaluation like this:
(defun try () ;; catch and dolist
(catch 'foo ;; are just for demo purposes
(dolist (n (inf 1) 'done)
(format t "~A~%" n)
(when (= n 7)
(throw 'foo x)))))
CL-USER> (try)
1
2
3
4
5
6
7
; Evaluation aborted.
How can I implement such an infinite list in CL in the most practical way?
A good pedagogical approach to this is to define things which are sometimes called 'streams'. The single best introduction to doing this that I know of is in Structure and Interpretation of Computer Programs. Streams are introduced in section 3.5, but don't just read that: read the book, seriously: it is a book everyone interested in programming should read.
SICP uses Scheme, and this sort of thing is more natural in Scheme. But it can be done in CL reasonably easily. What I've written below is rather 'Schemy' CL: in particular I just assume tail calls are optimised. That's not a safe assumption in CL, but it's good enough to see how you can build these concepts into a language which does not already have them, if your language is competent.
First of all we need a construct which supports lazy evaluation: we need to be able to 'delay' something to create a 'promise' which will be evaluated only when it needs to be. Well, what functions do is evaluate their body only when they are asked to, so we'll use them:
(defmacro delay (form)
(let ((stashn (make-symbol "STASH"))
(forcedn (make-symbol "FORCED")))
`(let ((,stashn nil)
(,forcedn nil))
(lambda ()
(if ,forcedn
,stashn
(setf ,forcedn t
,stashn ,form))))))
(defun force (thing)
(funcall thing))
delay is mildly fiddly, it wants to make sure that a promise is forced only once, and it also wants to make sure that the form being delayed doesn't get infected by the state it uses to do that. You can trace the expansion of delay to see what it makes:
(delay (print 1))
-> (let ((#:stash nil) (#:forced nil))
(lambda ()
(if #:forced #:stash (setf #:forced t #:stash (print 1)))))
This is fine.
So now, we'll invent streams: streams are like conses (they are conses!) but their cdrs are delayed:
(defmacro cons-stream (car cdr)
`(cons ,car (delay ,cdr)))
(defun stream-car (s)
(car s))
(defun stream-cdr (s)
(force (cdr s)))
OK, let's write a function to get the nth element of a stream:
(defun stream-nth (n s)
(cond ((null s)
nil)
((= n 0) (stream-car s))
(t
(stream-nth (1- n) (stream-cdr s)))))
And we can test this:
> (stream-nth 2
(cons-stream 0 (cons-stream 1 (cons-stream 2 nil))))
2
And now we can write a function to enumerate an interval in the naturals, which by default will be an half-infinite interval:
(defun stream-enumerate-interval (low &optional (high nil))
(if (and high (> low high))
nil
(cons-stream
low
(stream-enumerate-interval (1+ low) high))))
And now:
> (stream-nth 1000 (stream-enumerate-interval 0))
1000
And so on.
Well, we'd like some kind of macro which lets us traverse a stream: something like dolist, but for streams. Well we can do this by first writing a function which will call a function for each element in the stream (this is not the way I'd do this in production CL code, but it's fine here):
(defun call/stream-elements (f s)
;; Call f on the elements of s, returning NIL
(if (null s)
nil
(progn
(funcall f (stream-car s))
(call/stream-elements f (stream-cdr s)))))
And now
(defmacro do-stream ((e s &optional (r 'nil)) &body forms)
`(progn
(call/stream-elements (lambda (,e)
,#forms)
,s)
,r))
And now, for instance
(defun look-for (v s)
;; look for an element of S which is EQL to V
(do-stream (e s (values nil nil))
(when (eql e v)
(return-from look-for (values e t)))))
And we can then say
> (look-for 100 (stream-enumerate-interval 0))
100
t
Well, there is a lot more mechanism you need to make streams really useful: you need to be able to combine them, append them and so on. SICP has many of these functions, and they're generally easy to turn into CL, but too long here.
For practical purposes it would be wise to use existing libraries, but since the question is about how to implemented lazy lists, we will do it from scratch.
Closures
Lazy iteration is a matter of producing an object that can generate the new value of a lazy sequence each time it is asked to do so.
A simple approach for this is to return a closure, i.e. a function that closes over variables, which produces values while updating its state by side-effect.
If you evaluate:
(let ((a 0))
(lambda () (incf a)))
You obtain a function object that has a local state, namely here the variable named a.
This is a lexical binding to a location that is exclusive to this function, if you evaluate a second time the same expression, you'll obtain a different anonymous function that has its own local state.
When you call the closure, the value stored in a in incremented and its value is returned.
Let's bind this closure to a variable named counter, call it multiple times and store the successive results in a list:
(let ((counter (let ((a 0))
(lambda () (incf a)))))
(list (funcall counter)
(funcall counter)
(funcall counter)
(funcall counter)))
The resulting list is:
(1 2 3 4)
Simple iterator
In your case, you want to have an iterator that starts counting from 5 when writing:
(inf 5)
This can implemented as follows:
(defun inf (n)
(lambda ()
(shiftf n (1+ n))))
Here is there is no need to add a let, the lexical binding of an argument to n is done when calling the function.
We assign n to a different value within the body over time.
More precisely, SHIFTF assigns n to (1+ n), but returns the previous value of n.
For example:
(let ((it (inf 5)))
(list (funcall it)
(funcall it)
(funcall it)
(funcall it)))
Which gives:
(5 6 7 8)
Generic iterator
The standard dolist expects a proper list as an input, there is no way you can put another kind of data and expect it to work (or maybe in an implementation-specific way).
We need a similar macro to iterate over all the values in an arbitrary iterator.
We also need to specify when iteration stops.
There are multiple possibilities here, let's define a basic iteration protocol as follows:
we can call make-iterator on any object, along with arbitrary arguments, to obtain an iterator
we can call next on an iterator to obtain the next value.
More precisely, if there is a value, next returns the value and T as a secondary value; otherwise, next returns NIL.
Let's define two generic functions:
(defgeneric make-iterator (object &key)
(:documentation "create an iterator for OBJECT and arguments ARGS"))
(defgeneric next (iterator)
(:documentation "returns the next value and T as a secondary value, or NIL"))
Using generic functions allows the user to define custom iterators, as long as they respect the specified behaviour above.
Instead of using dolist, which only works with eager sequences, we define our own macro: for.
It hides calls to make-iterator and next from the user.
In other words, for takes an object and iterates over it.
We can skip iteration with (return v) since for is implemented with loop.
(defmacro for ((value object &rest args) &body body)
(let ((it (gensym)) (exists (gensym)))
`(let ((,it (make-iterator ,object ,#args)))
(loop
(multiple-value-bind (,value ,exists) (next ,it)
(unless ,exists
(return))
,#body)))))
We assume any function object can act as an iterator, so we specialize next for values f of class function, so that the function f gets called:
(defmethod next ((f function))
"A closure is an interator"
(funcall f))
Also, we can also specialize make-iterator to make closures their own iterators (I see no other good default behaviour to provide for closures):
(defmethod make-iterator ((function function) &key)
function)
Vector iterator
For example, we can built an iterator for vectors as follows. We specialize make-iterator for values (here named vec) of class vector.
The returned iterator is a closure, so we will be able to call next on it.
The method accepts a :start argument defaulting to zero:
(defmethod make-iterator ((vec vector) &key (start 0))
"Vector iterator"
(let ((index start))
(lambda ()
(when (array-in-bounds-p vec index)
(values (aref vec (shiftf index (1+ index))) t)))))
You can now write:
(for (v "abcdefg" :start 2)
(print v))
And this prints the following characters:
#\c
#\d
#\e
#\f
#\g
List iterator
Likewise, we can build a list iterator.
Here to demonstrate other kind of iterators, let's have a custom cursor type.
(defstruct list-cursor head)
The cursor is an object which keeps a reference to the current cons-cell in the list being visited, or NIL.
(defmethod make-iterator ((list list) &key)
"List iterator"
(make-list-cursor :head list))
And we define next as follows, specializeing on list-cursor:
(defmethod next ((cursor list-cursor))
(when (list-cursor-head cursor)
(values (pop (list-cursor-head cursor)) t)))
Ranges
Common Lisp also allows methods to be specialized with EQL specializers, which means the object we give to for might be a specific keyword, for example :range.
(defmethod make-iterator ((_ (eql :range)) &key (from 0) (to :infinity) (by 1))
(check-type from number)
(check-type to (or number (eql :infinity)))
(check-type by number)
(let ((counter from))
(case to
(:infinity
(lambda () (values (incf counter by) t)))
(t
(lambda ()
(when (< counter to)
(values (incf counter by) T)))))))
A possible call for make-iterator would be:
(make-iterator :range :from 0 :to 10 :by 2)
This also returns a closure.
Here, for example, you would iterate over a range as follows:
(for (v :range :from 0 :to 10 :by 2)
(print v))
The above expands as:
(let ((#:g1463 (make-iterator :range :from 0 :to 10 :by 2)))
(loop
(multiple-value-bind (v #:g1464)
(next #:g1463)
(unless #:g1464 (return))
(print v))))
Finally, if we add small modification to inf (adding secondary value):
(defun inf (n)
(lambda ()
(values (shiftf n (1+ n)) T)))
We can write:
(for (v (inf 5))
(print v)
(when (= v 7)
(return)))
Which prints:
5
6
7
I'll show it with a library:
How to create and consume an infinite list of integers with the GTWIWTG generators library
This library, called "Generators The Way I Want Them Generated", allows to do three things:
create generators (iterators)
combine them
consume them (once).
It is not unsimilar to the nearly-classic Series.
Install the lib with (ql:quickload "gtwiwtg"). I will work in its package: (in-package :gtwiwtg).
Create a generator for an infinite list of integers, start from 0:
GTWIWTG> (range)
#<RANGE-BACKED-GENERATOR! {10042B4D83}>
We can also specify its :from, :to, :by and :inclusive parameters.
Combine this generator with others: not needed here.
Iterate over it and stop:
GTWIWTG> (for x *
(print x)
(when (= x 7)
(return)))
0
1
2
3
4
5
6
7
T
This solution is very practical :)
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.
The code below server to show the number of integer in a list.
(defun isNum (N)
(and (<= N 9) (>= N 0)))
(defun count-numbers (list)
(let ((count 0))
(dolist (item list count)
(cond
((null list) nil)
(((and (<= N 9) (>= N 0))) item)(incf count))
(setq(0 + count))))))
I get the error A' is not of the expected typeREAL' when I run the command
(count-numbers '(3 4 5 6 a 7 b) )
I'm surprised it runs at all, given that your cond is improperly constructed, you switch to infix notation in the unnecessarily side-effect-generating bit of your code and you're using unbound variables in count-numbers. Hypothetically, if it did run, that error sounds about right. You're doing numeric comparisons on a parameter (and those error on non-numeric input).
I've got my codereview hat on today, so lets go through this a bit more in-depth.
Lisp (it actually doesn't matter which, afaik this applies to CL, Scheme and all the mongrels) uses lower-case-snake-case-with-dashes, and not lowerCamelCase for variable and function names.
(defun is-num (n)
(and (<= n 9) (>= n 0)))
Common Lisp convention is to end a predicate with p or -p rather than begin them with is-. Scheme has the (IMO better) convention of ending predicates with ? instead
(defun num-p (n)
(and (<= n 9) (>= n 0)))
((and (<= N 9) (>= N 0))) is not how you call a function. You actually need to use its name, not just attempt to call its body. This is the source of one of the many errors you'd get if you tried to run this code.
(defun count-numbers (list)
(let ((count 0))
(dolist (item list count)
(cond
((null list) nil)
((num-p item) item)(incf count))
(setq(0 + count))))))
numberp already exists, and does a type check on its input rather than attempting numeric comparisons. You should probably use that instead.
(defun count-numbers (list)
(let ((count 0))
(dolist (item list count)
(cond
((null list) nil)
((numberp item) item)(incf count))
(setq(0 + count))))))
((numberp item) item) (incf count)) probably doesn't do what you think it does as a cond clause. It actually gets treated as two separate clauses; one checks whether item is a number, and returns it if it is. The second tries to check the variable incf and returns count if it evaluates to t (which it doesn't, and won't). What you seem to want is to increment the counter count when you find a number in your list, which means you should put that incf clause in with the item.
(defun count-numbers (list)
(let ((count 0))
(dolist (item list count)
(cond ((null list) nil)
((numberp item)
(incf count)
item))
(setq (0 + count)))))
(setq (0 + count)) is the wrong thing for three reasons
You seem to have tripped back into infix notation, which means that the second bit there is actually attempting to call the function 0 with the variables + and count as arguments.
You don't have a second part to the setq, which means you're trying to set the above to NIL implicitly.
You don't actually need to set anything in order to return a value
At this point, we finally have a piece of code that will evaluate and run properly (and it doesn't throw the error you mention above).
(defun count-numbers (list)
(let ((count 0))
(dolist (item list count)
(cond ((null list) nil)
((numberp item)
(incf count)
item))
count)))
dolist is an iteration construct that does something for each element in a given list. That means you don't actually need to test for list termination manually with that cond. Also, because dolist doesn't collect results, there's no reason to return item to it. You're also unnecessarily shadowing the local count you declare in the let.
(defun count-numbers (list)
(let ((count 0))
(dolist (item list)
(when (numberp item) (incf count)))
count))
As usual, you can do all this with a simpler loop call.
(defun count-numbers (list)
(loop for item in list
when (numberp item) sum 1))
which makes the counter implicit and saves you from needing to return it manually. In fact, unless this was specifically an exercise to write your own iteration function, Common Lisp has a built in count-if, which takes predicate sequence [some other options] and returns the count of items in sequence that match predicate. If you wanted to name count-numbers specifically, for stylistic reasons, you could just
(defun count-numbers (list) (count-if #'numberp list))
and be done with it.
In conclusion, good try, but please try reading up on the language family for realzies before asking further questions.
Yet another way to do it would be:
(reduce
#'(lambda (a b)
(if (numberp b) (1+ a) a))
'(3 4 5 6 a 7 b) :initial-value 0) ; 5
I.e. process the sequence in a way that you are given at each iteration the result of the previous iteration + the next member of the sequence. Start with zero and increment the result each time the element in the sequence is a number.
EDIT
Sorry, I haven't seen Inaimathi mentioned count-if. That would be probably better.
Im really having problems understanding how I can create variable that would act as an accumulator in racket. This is definitely a really stupid question....but racket's documentation is pretty difficult for me to read.
I know I will use some kind of define statement or let statement.
I want to be able to pass a number to a variable or function and it adds the current value with the new value keeps the sum...How would I do this....?? Thank you..
(define (accumulator newvalue) "current=current+newvalue"
something like this..
An accumulator is generally just a function parameter. There are a few chapters in How to Design Programs (online, starting here) that cover accumulators. Have you read them?
For example, the reverse function is implemented using an accumulator that remembers the prefix of the list, reversed:
;; reverse : list -> list
(define (reverse elems0)
;; reverse/accum : list list -> list
(define (reverse/accum elems reversed-prefix)
(cond [(null? elems)
reversed-prefix]
[else
(reverse/accum (cdr elems)
(cons (car elems) reversed-prefix))]))
(reverse/accum elems null))
Note that the scope of the accumulator reversed-prefix is limited to the function. It is updated by calling the function with a new value for that parameter. Different calls to reverse have different accumulators, and reverse remembers nothing from one call to the next.
Perhaps you mean state variable instead. In that case, you define it (or bind it with let or lambda) at the appropriate scope and update it using set!. Here's a global state variable:
;; total : number
(define total 0)
;; add-to-total! : number -> number
(define (add-to-total! n)
(set! total (+ total n))
total)
(add-to-total! 5) ;; => 5
(add-to-total! 31) ;; => 36
Here's a variation that creates local state variables, so you can have multiple counters:
;; make-counter : -> number -> number
(define (make-counter)
(let ([total 0])
(lambda (n)
(set! total (+ total n))
total)))
(define counterA (make-counter))
(define counterB (make-counter))
(counterA 5) ;; => 5
(counterB 10) ;; => 10
(counterA 15) ;; => 20
(counterB 20) ;; => 30
But don't call state variables accumulators; it will confuse people.
Do you mean something like this?
(define (accumulator current newvalue)
(let ((current (+ current newvalue)))
...)
You can close over the accumulator variable:
(define accumulate
(let ((acc 0))
(λ (new-val)
(set! acc (+ acc new-val))
acc)))
(accumulate 10) ;=> 10
(accumulate 4) ;=> 14
It seems that I have to make it in detail; it's my homework. I don't
want to copy the code written by you. I'm a newbie; what I'm trying
to learn is how to decompose a subject to single pieces, and then
focus on what function should I use to solve the problem. It's a
little hard to finish these problems by my own, because I'm completely
a newbie in Lisp, actually in how to program. I hope you can help me
out.
Here is the problem: there is a given constant
(defconstant *storms* '((bob 65)
(chary 150)
(jenny 145)
(ivan 165)
(james 120)))
Each storm is represented by a list of its name and its wind speed.
The wind speeds are to be categorized as follows:
39–74 → tropical
75–95 → cat-1
96–110 → cat-2
111–130 → cat-3
131–155 → cat-4
156 or more → cat-5
Now I have to write two functions:
storm-categories should generate category names, like this: (bob
tropical), (chary cat-1), …
and storm-distribution should generate the number of storms in
each category, like this: (cat-1 1), (cat-2 0), …
The way I try to solve this problem is:
Use if statements to judge the type of windspeed:
(if (and (> x 39) (< x 73)) (print 'tropical))
(if (and (> x 74) (< x 95)) (print 'cat-1))
(if (and (> x 96) (< x 110)) (print 'cat-2))
(if (and (> x 111) (< x 130)) (print'cat-3))
(if (and (> x 131) (< x 155)) (print'cat-4))
(if (and (> x 156)) (print 'cat-5))
Replace the windspeed (like 65) with windtype (like cat-1)
(loop for x in storms
do (rplacd x ‘windtype)
I just have a simple idea of the first function, but still don't know
how to implement it. I haven't touched the distribution function,
because I am still stuck on the first one.
DEFCONSTANT is wrong. It makes no sense to make your input a constant. A variable defined with DEFVAR or DEFPARAMETER is fine.
Instead of IF use COND. COND allows the testing of several conditions.
You don't want to use PRINT. Why print something. You want to compute a value.
RPLACA is also wrong. That's used for destructive modification. You don't want that. You want to create a new value. Something like RPLACA might be used in the function DISTRIBUTION (see below).
Use functional abstraction. Which functions are useful?
BETWEEN-P, is a value X between a and b ?
STORM-CATEGORY, for a given wind speed return the category
STORM-CATEGORIES, for a list of items (storm wind-speed) return a list of items (storm category). Map over the input list to create the result list.
DISTRIBUTION, for a list of items (tag category) return a list with items (category number-of-tags-in-this-category).
STORM-DISTRIBUTION, for a list of items (storm category) return a list with items (category number-of-storms-in-this-category). This basically calls DISTRIBUTION with the right parameters.
The function DISTRIBUTION is the most complicated of the above. Typically one would use a hashtable or a assoc list as an intermediate help to keep a count of the occurrences. Map over the input list and update the corresponding count.
Also: a good introduction into basic Lisp is the book Common Lisp: A Gentle Introduction to Symbolic Computation - it is freely available as a PDF for download. A more fun and also basic introduction to Lisp is the book Land of Lisp.
Okay roccia, you have posted your answer. Here comes mine hacked in a few minutes, but it should give you some ideas:
First let's start with the data:
(defparameter *storms2004*
'((BONNIE 65)
(CHARLEY 150)
(FRANCES 145)
(IVAN 165)
(JEANNE 120)))
(defparameter *storm-categories*
'((39 73 tropical-storm)
(74 95 hurricane-cat-1)
(96 110 hurricane-cat-2)
(111 130 hurricane-cat-3)
(131 155 hurricane-cat-4)
(156 nil hurricane-cat-5)))
A function that checks if a value is between two bounds. If the right bound can also be missing (NIL).
(defun between (value a b)
(<= a value (if b b value)))
Note that Lisp allows the comparison predicate with more than two arguments.
Let's find the category of a storm. The Lisp functions FIND and FIND-IF find things in lists.
(defun storm-category (storm-speed)
(third (find-if (lambda (storm)
(between storm-speed (first storm) (second storm)))
*storm-categories*)))
Let's compute the category for each storm. Since we get a list of (storm wind-speed), we just map over the function which computes the category over the list. We need to return a list of storms and category.
(defun storm-categories (list)
(mapcar (lambda (storm)
(list (first storm)
(storm-category (second storm))))
list))
Now we take the the same list of storms, but use a hash table to keep track of how many storms there were in each category. MAPC is like MAPCAR, but only for the side effect of updating the hash table. ÌNCF increments the count. When we have filled the hash table, we need to map over it with MAPHASH. For each pair of key and value in the table, we just push the pair onto a result list and then we are returning that result.
(defun storm-distribution (storms)
(let ((table (make-hash-table)))
(mapc (lambda (storm)
(incf (gethash (second storm) table 0)))
(storm-categories storms))
(let ((result nil))
(maphash (lambda (key value)
(push (list key value) result))
table)
result)))
Test:
CL-USER 33 > (storm-category 100)
HURRICANE-CAT-2
CL-USER 34 > (storm-categories *storms2004*)
((BONNIE TROPICAL-STORM)
(CHARLEY HURRICANE-CAT-4)
(FRANCES HURRICANE-CAT-4)
(IVAN HURRICANE-CAT-5)
(JEANNE HURRICANE-CAT-3))
CL-USER 35 > (storm-distribution *storms2004*)
((HURRICANE-CAT-5 1)
(HURRICANE-CAT-4 2)
(HURRICANE-CAT-3 1)
(TROPICAL-STORM 1))
Looks fine to me.
Finally finished this problem. the second part is really makes me crazy. I cant't figure out how to use hashtable or assoc list to slove it. Anyway the assignment is done, but I want to know how can I simplify it... Hope u guys can help me . Thanks for your help Joswing, your idea really helps me a lot...
(defconstant *storms2004* '((BONNIE 65)(CHARLEY 150)(FRANCES 145)(IVAN 165)(JEANNE 120)))
(defun storm-category (x) ; for given windspeed return the category
(cond
((and (> x 39) (< x 73) 'tropical-storm))
((and (> x 74) (< x 95) 'hurricane-cat-1))
((and (> x 96) (< x 110) 'hurricane-cat-2))
((and (> x 111) (< x 130) 'hurricane-cat-3))
((and (> x 131) (< x 155) 'hurricane-cat-4))
( t 'hurricane-cat-5)
)
);end storm-category
(defun storm-categories (lst) ;for a list of storm and windspeed return storm's name and wind type
(let ((result nil))
(dolist (x lst (reverse result)) ;
(push
(list (first x) (storm-category (second x)) ) result)
)
)
);end storm-categories
(defun storm-distribution (lst)
(setq stormcategories '(tropical-storm hurricane-cat-1 hurricane-cat-2 hurricane-cat-3 hurricane-cat-4 hurricane-cat-5))
(setq stormlist (storm-categories lst))
(let( (tropicalcount 0)
(hurricane-cat-1count 0)
(hurricane-cat-2count 0)
(hurricane-cat-3count 0)
(hurricane-cat-4count 0)
(hurricane-cat-5count 0)
(result nil)
)
(dolist (y stormlist )
(cond
((eql (second y) 'tropical-storm) (setq tropicalcount (+ tropicalcount 1)))
((eql (second y) 'hurricane-cat-1) (setq hurricane-cat-1count (+ hurricane-cat-1count 1)))
((eql (second y) 'hurricane-cat-2) (setq hurricane-cat-2count (+ hurricane-cat-2count 1)))
((eql (second y) 'hurricane-cat-3) (setq hurricane-cat-3count (+ hurricane-cat-3count 1)))
((eql (second y) 'hurricane-cat-4) (setq hurricane-cat-4count (+ hurricane-cat-4count 1)))
((eql (second y) 'hurricane-cat-5)(setq hurricane-cat-5count (+ hurricane-cat-5count 1)))
)
);ebd dolist
(push
(list (list 'tropicalstorm tropicalcount )
(list 'hurricane-cat-1 hurricane-cat-1count)
(list 'hurricane-cat-2 hurricane-cat-2count )
(list 'hurricane-cat-3 hurricane-cat-3count )
(list 'hurricane-cat-4 hurricane-cat-4count )
(list 'hurricane-cat-5 hurricane-cat-5count )
) ;end list
result) ;end push
);end let
);end distribution