(define (average ....)) in Lisp
Asked Answered
B

5

8

I'm just playing around with scheme/lisp and was thinking about how I would right my own definition of average. I'm not sure how to do some things that I think are required though.

  • define a procedure that takes an arbitrary number of arguments
  • count those arguments
  • pass the argument list to (+) to sum them together

Does someone have an example of defining average? I don't seem to know enough about LISP to form a web search that gets back the results I'm looking for.

Butchery answered 14/7, 2010 at 2:39 Comment(2)
This is not homework (got downvoted for some reason... probably because someone thought it was). I am working through the SICP course online and I was just playing around with LISP wondering how to create this time of procedure.Butchery
(FWIW, this was pretty clear -- almost all courses avoid going over functions with rest arguments.)Despite
D
11

The definition would be a very simple one-liner, but without spoiling it, you should look into:

  • a "rest" argument -- this (define (foo . xs) ...xs...) defines foo as a function that takes any number of arguments and they're available as a list which will be the value of xs.

  • length returns the length of a list.

  • apply takes a function and a list of values and applies the function to these values.

When you get that, you can go for more:

  • see the foldl function to avoid applying a list on a potentially very big list (this can matter in some implementations where the length of the argument list is limited, but it wouldn't make much difference in Racket).

  • note that Racket has exact rationals, and you can use exact->inexact to make a more efficient floating-point version.





And the spoilers are:

  • (define (average . ns) (/ (apply + ns) (length ns)))

  • Make it require one argument: (define (average n . ns) (/ (apply + n ns) (add1 (length ns))))

  • Use foldl: (define (average n . ns) (/ (foldl + 0 (cons n ns)) (add1 (length ns))))

  • Make it use floating point: (define (average n . ns) (/ (foldl + 0.0 (cons n ns)) (add1 (length ns))))

Despite answered 14/7, 2010 at 2:53 Comment(0)
G
4

In Common Lisp, it looks like you can do:

(defun average (&rest args)
  (when args
    (/ (apply #'+ args) (length args))))

although I have no idea if &rest is available on all implementations of Lisp. Reference here.

Putting that code into GNU CLISP results in:

[1]> (defun average (&rest args)
       (when args
         (/ (apply #'+ args) (length args))))
AVERAGE
[2]> (average 1 2 3 4 5 6)
7/2

which is 3.5 (correct).

Granvillegranvillebarker answered 14/7, 2010 at 2:45 Comment(8)
You shouldn't assume that a lisp implementation can accept any number of inputs (and IIRC, CLisp will break). Also, the function should always return a number -- this code will return NIL when given no inputs.Despite
@Eli, you probably know more about LISP than I (as evidenced by your much more comprehensive answer). But I have to disagree on that last statement. Mathematically, the average of a null set is not a number, it's undefined. If you do want it to return 0 {ugh! IMNSHO :-) }, you can modify the code to do so.Granvillegranvillebarker
paxdiablo: right, it is undefined, and the translation of that to code is in most cases best done by throwing an error. See my code for example: it will throw an error when given no inputs (a bad division by zero in the first version, and a much better arity error later). Of course there are also cases where you'd want to return some "undefined" result (null, undefined, nan, etc), but usually it's better to make code throw an error earlier, rather than allowing it to propagate a potentially bogus result through.Despite
(Oh, and BTW, any lisper would groan at "LISP" -- it should be "Lisp".)Despite
Sorry, I thought it was the acronymn: Lots of Irritating Superfluous Parentheses :-) I only ever played a little with Lisp, more so with Scheme since it was part of a product we once adopted, but all I can remember of both them nowadays is the endless % bracket-matching I had to do in vi.Granvillegranvillebarker
That sounds rather like you did not set up your editor properly.Crampon
Yeah -- you'll also see that Lisp/Scheme programmers are very pedantic about their indentation: this is because after an initial short period of adjustment you just "don't see" the parens anymore. This includes many people who never enter a ( without a closing ), so source code is never unbalanced, which means that you never need to count them -- and that's convenient in all languages. (But of course this doesn't stop the usual paren-related jokes...)Despite
Eli why Lisp? The SICP book says that LISP stands for LISt Processing. Given that it is an acronym wouldn't the appropriate representation be in all caps? Not saying you are wrong, just wondering why Lisp and not LISP given that definition.Butchery
O
2

Two versions in Common Lisp:

(defun average (items)
  (destructuring-bind (l . s)
      (reduce (lambda (c a)
                (incf (car c))
                (incf (cdr c) a)
                c)
              items
              :initial-value (cons 0 0))
    (/ s l)))

(defun average (items &aux (s 0) (l 0))
  (dolist (i items (/ s l))
    (incf s i)
    (incf l)))
Omnivorous answered 14/7, 2010 at 12:53 Comment(0)
Y
1

In Scheme, I prefer using a list instead of the "rest" argument because rest argument makes implementing procedures like the following difficult:

> (define (call-average . ns)
     (average ns))
> (call-average 1 2 3) ;; => BANG!

Packing arbitrary number of arguments into a list allows you to perform any list operation on the arguments. You can do more with less syntax and confusion. Here is my Scheme version of average that take 'n' arguments:

(define (average the-list)
  (let loop ((count 0) (sum 0) (args the-list))
    (if (not (null? args))
        (loop (add1 count) (+ sum (car args)) (cdr args))
        (/ sum count))))

Here is the same procedure in Common Lisp:

(defun average (the-list)
  (let ((count 0) (sum 0))
    (dolist (n the-list)
      (incf count)
      (incf sum n))
    (/ sum count)))
Yen answered 14/7, 2010 at 3:44 Comment(3)
You can use apply for translating list into a list of arguments.Massasoit
(Incf sum n) is better than (setf sum (+ sum n)). Besides, my first prototype would usually be (/ (reduce #'+ list) (length list)), and then I might change it to a (loop for element in list counting element into length summing element into sum finally (return (/ sum length))).Crampon
Couldn't you instead write a simple macro to unpack the list as individual arguments for calling average so it would look like: (define (call-average . ns) (average (unpack ns)))? I'm currently a Scheme/Lisp newbie, so I honestly don't know how possible this is.Tether
L
1

In Scheme R5RS:

(define (average . numbers)  
    (/ (apply + numbers) (length numbers)))
Lonely answered 18/10, 2014 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.