1

I hope to get a quick fix to my code, which takes in a list of numbers, numberlist, and a threshold and return the number of items in numberlist that are greater than threshold. I just can't figure out what's wrong, and I am not familiar with debugging. I am very new to stackoverflow, and LISP in general... any comments/criticism/advice will be welcome. Thank you!

ex) (count-greater-than (list 1 2 3 4 5 6 6 7) 5) => 3

(defun count-greater-than (numberlist threshold)
  (if (null numberlist) 0
    (counter (numberlist threshold 0)))
  (defun counter (numberlist threshold count)
    (cond ((null numberlist) count)
          ((> (first numberlist) threshold) (counter (rest numberlist) threshold (+ 1 count)))
          (t (counter (rest numberlist) threshold count)))))
3
  • Check your parentheses. There is only one top-level function and the other function is somehow contained in the first function? That looks strange. Create two independent functions. Commented May 5, 2015 at 11:08
  • @RainerJoswig I wonder if the intent is to create a local recursive function, e.g., with labels. Commented May 5, 2015 at 11:22
  • 1
    @RainerJoswig Thanks. I just realized that was the problem; now it's working. Thanks! (how do I put this question as 'solved'?) Commented May 5, 2015 at 11:24

1 Answer 1

2

First, note that the standard actually contains functions that will help with things like this. There's a useful count-if function that can take a predicate and count how many elements in a list satisfy it. For your case, you could do:

CL-USER> (count-if #'(lambda (x)
                       (> x 5))
                   (list 1 2 3 4 5 6 6 7))
;=> 3

CL-USER> (defun count-greater-than (numbers threshold)
           (count-if (lambda (n) (> n threshold)) numbers))
COUNT-GREATER-THAN
CL-USER> (count-greater-than (list 1 2 3 4 5 6 6 7) 5)
3
CL-USER> (count-greater-than (list 1 2 3 4 5 6 6 7) 6)
1

In your particular case, it looks like you're doing this manually, but you have the parenthesis wrong. It looks like you're trying to create a local helper function called counter. You can either define it outside the function with defun, e.g.:

(defun count-greater-than (numberlist threshold)
  (if (null numberlist) 0
    (counter (numberlist threshold 0))))

(defun counter (numberlist threshold count)
  (cond ((null numberlist) count)
        ((> (first numberlist) threshold) (counter (rest numberlist) threshold (+ 1 count)))
        (t (counter (rest numberlist) threshold count))))

or you could do it with a local definition, using labels:

(defun count-greater-than (numberlist threshold)
  (labels ((counter (numberlist threshold count)
             (cond ((null numberlist) count)
                   ((> (first numberlist) threshold) (counter (rest numberlist) threshold (+ 1 count)))
                   (t (counter (rest numberlist) threshold count)))))
    (if (null numberlist) 0
        (counter numberlist threshold 0))))

Notes

As Xach points out in the comments, you can actually do this even more succinctly using count's :test argument. I don't know whether it captures the notion of "count things with this property" quite as explicitly, but it makes for a very short solution:

CL-USER> (count 5 (list 1 2 3 4 5 6 6 7) :test #'<)
;=> 3

This counts how many times 5 is in the list, but the trick is that rather than checking whether a list element is 5 using eql or =, it uses <. That is, count will end up checking (< 5 1), then (< 5 2), …, up to (< 5 6), (< 5 7). It is specified that the test will be called with the arguments in that order. The glossary entry on satisfy the test says (emphasis added):

  1. (for a two argument test) to be in a state such that the two-place predicate which is the sequence function's test argument returns true when given a first argument that is the object being considered, and when given a second argument that is the result of calling the sequence function's key argument on an element of the sequence function's sequence argument which is being tested for equality;
Sign up to request clarification or add additional context in comments.

7 Comments

This is very helpful! It's only that I am taking an introductory AI course, and I am only allowed few primitives. I haven't learnt lamda yet. Thanks for also teaching me how to use labels!
@Shauny Well, the site is generally intended for "professional and enthusiast programmers", so in general, they'd prefer count-if (no reason to reinvent the wheel after all). There's nothing wrong with asking about homework, exercises, etc., but it's usually considered a courtesy to mention any of those artificial restrictions in the question, since answers would, by default, look for the "typical" ways to do things.
I'd go with (count 5 list :test #'<).
@Xach that's pretty nice (and I'll add it to the answer), but it's a bit less explicit about the "count things with property" nature. The biggest issue with it, in my opinion, is that you have to remember what order the test will be passed its arguments. I.e., for each x in the list, will this call (< x 5) or (< 5 x). That's a bit of a cognitive workload.
You have to memorize a lot of stuff to use CL in general.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.