Reputation: 119
So I'm, trying to learn lisp, and I figured an easy program to help me learn it is just a program that checks for primes. It worked at first:
(dotimes (i 100)
(let ((devisors 0))
(if (> i 2)
(progn
(dotimes (j (+ (/ i 2) 1))
(if (> j 1)
(if (= (rem i j) 0) (setq devisors 1) )
))
(if (= devisors 0) (print i))
)
)
)
)
I then tried to abstract the prime checking into a function, and wrote this code:
(defun isprime (num)
(defvar devisors 0)
(dotimes (j (+ (/ num 2) 1))
(if (> j 1)
(if (= (rem num j) 0) (setq devisors 1) )
))
(if (= devisors 0) num 0)
)
(dotimes (i 100)
(if (/= (isprime i) 0) (print i))
)
But this one doesn't work. It prints 123 and finishes. The thing is, I tried to just print (print (isprime 5)) and IT WORKS but it doesn't when in the loop.
Why is this? Also keep in mind I'm new to lisp... (I'm using clisp if it helps)
Upvotes: 0
Views: 118
Reputation: 36611
Your first step should be to break the question down. You need to know if anything in a range of numbers meets a criteria represented as a predicate function. This is readily accomplished with simple recursion, and is naturally tail-recursive.
(defun any-in-range (s e predicate)
(cond ((> s e) nil)
((funcall predicate s) t)
(t (any-in-range (+ 1 s) e predicate))))
Now checking if something is prime is just looking to see if any number in a range is a divisor of an input number. The range will be 2 to the square root of the input number.
(defun is-prime (n)
(not (any-in-range 2 (floor (sqrt n))
(lambda (x) (= 0 (rem n x))))))
* (is-prime 17)
T
* (is-prime 16)
NIL
Now you can write:
(dotimes (i 100)
(when (is-prime i)
(print i)))
Upvotes: 5
Reputation: 7578
Important note: this code shouldn't work. Documentation for dotimes
says:
dotimes iterates over a series of integers.
dotimes evaluates count-form, which should produce an integer.
Imagine that i
is 3. Then the value of j
is 5/2
, and that isn't an integer.
But for some reason, this code works in CLISP. Your error is caused by defvar
- look again into documentation:
defparameter unconditionally assigns the initial-value to the dynamic variable named name.
defvar, by contrast, assigns initial-value (if supplied) to the dynamic variable named name only if name is not already bound.
When you call isprime
with a new number, devisors
is already set to 1, so it won't reset to 0.
Quick fix- replace (defvar devisors 0)
with (let ((devisors 0)) ...
inside isprime
.
I also made some other changes (zerop
, 1+
and floor
, when
instead of if
with only one branch, and also directly returning t
(true) or nil
(false) instead of some 0 or 1):
(defun isprime (num)
(if (or (zerop num)
(= num 1))
nil
(let ((devisors t))
(dotimes (j (1+ (floor (sqrt num))))
(when (> j 1)
(when (zerop (rem num j))
(setq devisors nil))))
devisors)))
(dotimes (i 100)
(when (isprime i)
(print i)))
Upvotes: 4