Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lisp function count recurring a's in list

I am trying to write a function that takes only a list as a parameter and counts the number of times the symbol a appears in the list, without counting any a's in a sublist within the list.

I am very new to Lisp so please use as basic code as possible so I could understand what it is doing, even if it is inefficient.

(defun times (l)
    (setf x 'a)
    (cond
        ((null l) nil)
        ((equal x (car l)) (+ 1 (times x (cdr L))))
        (t (times x(cdr l)))))

So (times '(a b (a) c)) should return 1. However I am getting the error that with this line times is getting two arguments when it should be getting one.

like image 650
Breanna Burgess Avatar asked May 19 '26 04:05

Breanna Burgess


2 Answers

There are multiple ways to implement this in Common Lisp. The example should be small enough for you to follow (test them).

Recursive implementation

Your approach is fine, except you have small errors (in addition to the other ones reported in comments):

  • Do not use SETF for undeclarded variables.
  • Do not return NIL in the base case: your function should return a number.
  • Also, your code coud be better formatted, and you should use longer names (lowercase l in particular is hard to read)

Here is a modified version:

(defun times (list element)
  (cond
    ((null list) 0)
    ((equal (car list) element) (1+ (times (cdr list) element)))
    (t (times (cdr list) element))))

Example

Let's TRACE the function:

CL-USER> (trace times)

Here is the execution trace:

CL-USER> (times '(a b c d a f a) 'a)
0: (TIMES (A B C D A F A) A)
  1: (TIMES (B C D A F A) A)
    2: (TIMES (C D A F A) A)
      3: (TIMES (D A F A) A)
        4: (TIMES (A F A) A)
          5: (TIMES (F A) A)
            6: (TIMES (A) A)
              7: (TIMES NIL A)
              7: TIMES returned 0
            6: TIMES returned 1
          5: TIMES returned 1
        4: TIMES returned 2
      3: TIMES returned 2
    2: TIMES returned 2
  1: TIMES returned 2
0: TIMES returned 3
3

You can see that the call stack grows for each and every element visited in the list. It is usually a bad practice, especially when the recursive function is basically implementing a loop.

Loops

Use a simple LOOP:

(defun times (list element)
  (loop for value in list count (equal value element)))

Alternatively, use DOLIST:

(defun times (list element)
  (let ((counter 0))
    (dolist (value list counter)
      (when (equal element value)
        (incf counter)))))

Here above, counter is a local variable introduced by LET. It is incremented with INCF inside the loop, only WHEN the comparison holds. Finally, counter is returned from the dolist (the third parameter indicates which form to evaluate to have the result value). The return value of dolist is also the return value of the let and the whole function.

This can be rewritten also with DO:

(defun times (list element)
  (do ((counter 0)) ((null list) counter)
    (when (equal element (pop list)) 
      (incf counter))))

The first list in do introduces bindings, the second list is a termination test (here we stop when the list is empty) followed by a result form (here, the counter). Inside the body of the loop, we POP elements from the input list and do the comparison, as before.

Tail-recursive implementation

If you want to keep a recursive implementation, add an accumulator and compute all the intermediate results before entering a recursive evaluation. If all results are passed as function arguments, there is no need to keep track of intermediate results at each step of the recursion, which eliminates the need to even allocate stack frames. The ability to perform tail-call elimination is not expressly required by the specification of the language, but it is typically available in most implementations.

(defun times (list element)
  (labels ((recurse (list counter)
             (cond
               ((null list) counter)
               ((equal (first list) element)
                (recurse (rest list) (1+ counter)))
               (t (recurse (rest list) counter)))))
    (recurse list 0)))

Here above, recurse is a local recursive function introduced by LABELS, which accepts a counter parameter. The difference with the original recursive function is that when the list is empty, it returns the current value of counter instead of zero. Here, the result of recurse is always the same as the value returned by recursive invocations: the compiler can just rebind inputs and perform a jump instead of allocating intermediate frames.

Higher-order functions

Here are yet two other ways, based on higher-order functions.

First, the usual way to define functions with accumulators is with REDUCE (known as fold in other languages). There is no explicit mutation:

(defun times (list element)
  (reduce (lambda (counter value)
            (if (equal value element)
              (1+ counter)
              counter))
           list
           :initial-value 0))

The anonymous function accepts the current state of the accumulator, the current value being visited in the list, and shall compute the next state of the accumulator (the counter).

Alternatively, call MAP with a nil first argument, so that the iteration is only done for effects. The anonymous function established by the LAMBDA form closes over the local counter variable, and can increment it when comparison holds. It is similar to the previous dolist example w.r.t. incrementing the counter through side-effects, but the iteration is done implicitly with map.

(defun times (list element)
  (let ((counter 0))
    (map () 
         (lambda (value)
           (when (equal value element)
             (incf counter)))
         list)
    counter))

Built-in

For your information, there is a built-in COUNT function:

(defun times (list element)
  (count element list :test #'equal))
like image 166
coredump Avatar answered May 22 '26 06:05

coredump


Here is some code which might help. It uses tail recursion and defines a helper function which is called recursively and keeps track of the number of times the symbol 'a appears with the argument count. The helper function takes two arguments, but the functino count-a takes one. Count-a calls the helper with the list l and the total number of times it has counted the symbol 'a at the beginning, which is zero to kick off the recursive calls.

(defun count-a (l)
  (labels ((helper (x count)
             (if (equalp 'a (car x)) (incf count))
             (cond ((null x) count)
                   (t (helper (cdr x) count)))))
     (helper l 0)))

You can also use the loop macro:

(defun count-a-with-a-loop (l)
  (loop for i in l count (equalp 'a i))\

Or as Coredump points out:

(defun count-a-with-count (l)
   (count 'a l :test #'equal))

Note the '# character before equal lets the Lisp interpreter know that equal is a function, known as a reader macro.

like image 32
Ethan Henderson Avatar answered May 22 '26 08:05

Ethan Henderson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!