I'm working with clojure and while I've dabbled with lisps before, I'm having trouble finding a clean way to nest let statements in cond statements. For example, consider the following function:
(defn operate-on-list [xs]
(let [[unpack vector] (first xs)]
(cond
[(empty? xs) 'empty
unpack vector
:else (operate-on-list (rest xs))])))
It's a pretty standard recursive operation on a list, but it needs to do some work on the first element in the list before it works with the contents. The issue, of course, is that the list may be empty.
In this example, it wouldn't be hard to change unpack to ((first xs) 0) and vector to ((first xs) 1), but this quickly gets ugly if more work needs to be done on (first xs).
Is there any way to effectively use a let statement part-way through a cond?
Thanks.
-Nate
In cases like these, you're best off using if-let:
(defn operate-on-list [xs]
(if-let [[unpack v] (first xs)]
(cond
unpack v
:else (operate-on-list (rest xs)))))
This code walks the given list seq-able (list, vector, array...) of vectors and returns the second element of the first vector whose first element is true (meaning not false or nil). nil is returned if no such vector is found.
Note that vector is a built-in function, so I've chosen v as the variable name, just in case the need to use the function in the body arises in the future. More importantly, you're using too many brackets in your cond syntax; fixed in this version.
UPDATE: Two additional things worth noting about if-let:
The way if-let works, if (first xs) happens to be nil (false would be the same), the destructuring binding never takes place, so Clojure won't complain about not being able to bind nil to [unpack v].
Also, if-let accepts an else clause (in which you can't refer to the variables bound in if-let bindings vector -- though if you're in the else clause, you know they where false or nil anyway).
;use conditional let: http://richhickey.github.com/clojure-contrib/cond-api.html
(use 'clojure.contrib.cond)
(cond-let [b]
nil b
12 (prn (+ b 1))
:else 17 )
;==> 13
Another good example can be found here http://www.mail-archive.com/clojure#googlegroups.com/msg03684.html
Sort of like this, with a let inside the scope of the cond?
(defn operate-on-list [list]
(let [ el_first (first list) ]
(cond
(nil? el_first) (println "Finished")
:else (do
(let [ list_rest (rest list) ]
(println el_first)
(operate-on-list list_rest))))))
(operate-on-list '(1 2 3))
The output is:
1
2
3
Finished
Related
it should count the elements of a list, but says "*** - +: NIL is not a number"
(setq A '(2 3 4 3 2 6 7 8 4 3 5 6))
(defun big (A)
(if (not (null (car A))) (+ 1 (big (cdr A))) ) ;if the first element is not null, add 1 to the count of the elements to the rest of the list
)
(print (big A))
Type error
An IF expression has either 2 or 3 arguments:
(if test something)
(if test something something-else)
When it only has 2 arguments, it is as-if the third argument, something-else, was NIL. That means that the IF expression evaluates to NIL when the test expression is false. In your case, you have 2 arguments:
(defun big (A)
(if (not (null (car A)))
;; "then" branch (when condition is true)
(+ 1 (big (cdr A)))
;; no "else" branch (when condition is false)
))
So you know that sometimes a call to big might return NIL.
But, you also write:
(+ 1 (big (cdr A)))
This expression looks like (+ 1 x) with x being a call to big, meaning that x might evaluate to NIL in some cases. That's the case you hit with the debugger.
If you make sure the if expression always return a number, by returning for example zero in the else branch, then you won't have the same error about trying to add a number to NIL.
Counting elements
But then, you would still have other bugs, since you say that the function big "should count the elements of a list". If you want to count the element of a list, you never need to look at the elements stored in the list, you only need to know that they exist.
When you write (car a), you are accessing the first element of the list. You then check if that value is non-nil, but it is perfectly valid to have a list filled with NIL values:
'(NIL NIL NIL)
That list has 3 elements, and at no point the fact that they are NIL should matter when counting them.
A recursive function working on a list typically needs to cover two cases, namely if the list is empty or not. You check if the current list is empty by calling (null list) or (endp list) (just doing (if list ... ...) works too since NIL is the only false value).
The test for null car doesn’t do any good, cdr will return nil before car does.
You need a base case where you find you’re done and return something instead of recursing. Right now you don’t have that. Look at examples of simple recursive functions and see how they have a base case.
To count the elements in a list there are two cases:
The base case where the list is empty (return 0)
The recursive case where the list isn’t empty (return 1 + the count of the cdr of the passed in list)
I am new to lisp and I have a problem, I'm trying to find the number in the list but it is not working. I haven't made the return statement yet
(defun num (x 'y)
(if (member x '(y)) 't nil))
(write (num 10 '(5 10 15 20)))
My output just outputs the nil instead of doing the function and I'm confused of what I am doing wrong.
Solution
(defun member-p (element list)
"Return T if the object is present in the list"
(not (null (member element list))))
The not/null pattern is equivalent to (if (member element list) t nil) but is more common.
In fact, you do not really need this separate function,
member is good enough.
The -p suffix stands for predicate, cf. integerp and upper-case-p.
Your code
You cannot quote lambda list elements, so you need to replace defun num (x 'y) with defun num (x y)
You need not quote t
Quoting '(y) makes no sense, replace it with y.
You do not need to write the function call, the REPL will do it for you.
See also
When to use ' (or quote) in Lisp?
Can you program without REPL on Lisp?
You are almost certainly expected to not just use member, but to write a function which does what you need (obviously in real life you would just use member because that's what it's for).
So. To know if an object is in a list:
if the list is empty it's not;
if the head of the list is equal to the object it is;
otherwise it is in the list if it's in the tail of the list.
And you turn this into a function very straightforwardly:
(defun num-in-list-p (n l)
;; is N in L: N is assumed to be a number, L a list of numbers
(cond ((null l)
nil)
((= n (first l))
t)
(t
(num-in-list-p n (rest l)))))
You could use the built in position function which will return the index of the number if it is in the list:
(position 1 '(5 4 3 2 1))
If you want to define your own function:
CL-USER> (defun our-member(obj lst)
(if(zerop (length lst))
nil
(if(equal(car lst)obj)
T
(our-member obj (cdr lst)))))
OUR-MEMBER
CL-USER> (our-member 1 '(5 4 3 2 1))
T
CL-USER> (our-member 99 '(1 2 3 4 5))
NIL
We can create a function called "our-member" that will take an object (in your case a number) and a list (in your case a list of numbers) as an argument. In this situation our "base-case" will be whether or not the length of the list is equal to zero. If it is and we still haven't found a match, we will return nil. Otherwise, we will check to see if the car of the list (the first element in the list) is equal to the obj that we passed. If so, we will return T (true). However, if it is not, we will call the function again passing the object and the cdr of the list (everything after the car of the list) to the function again, until there are no items left within the list. As you can see, The first example of a call to this function returns T, and the second example call returns NIL.
What makes this utility function a good example is that it essentially shows you the under workings of the member function as well and what is going on inside.
I don't understand the following paragraph:
(COUNT-ATOMS ’(A (B) C)) should return five.
A, B, and C and two NILs in the tree.
Write a function COUNT-ATOMS that returns the number of atoms in a tree.
I tried this:
(defun count-atoms(l)
(cond
((null l) 0)
(t (+ (cond
((atom (car l)) 1)
(t 0))
(count-atoms (cdr l))))))
However, (COUNT-ATOMS '(A (B) C)) return 2.
How should I do to return 5 instead?
Could you explain in more details?
If you wanted to build (a (b) c) at runtime, using only cons and quote, you would write:
(cons 'a
(cons (cons 'b nil)
(cons 'c nil)))
There are 5 atoms (a, b, c and two nil) in the tree being built. In practice you could use a simpler notation, like (list 'a (list 'b) 'c).
In your function, you do not recurse into the CARS of your trees, only the CDRS. Also, when the CAR is not an atom, like when you encounter (B), you add zero (the default clause in the second cond) (edit. As kmkaplan noted, you also count zero for nil, first cond).
A simple solution is this, based on typecase:
(defun count-atoms (form)
(typecase form
(atom 1)
(cons (+ (count-atoms (car form))
(count-atoms (cdr form))))))
When you encounter an atom, the result is 1.
When you have a cons cell, you sum the number of atoms in its car and cdr.
The typecase dispatches according to the type of its argument, here form. Each clause has the following syntax: (type ...body...), where type is the name of a type and ...body... one or more expressions (an implicit progn): the last value is the return value of the typecase, if the argument matches the type type.
The first clause (atom 1) says: if form is an atom, return 1. The following one, (cons ...) says: else, if form is a cons cell, .... Here, atom is the name of a type, which represents everything that is not a cons. Granted, once you know that something is not an atom, you know that it is necessarily a cons, and the second test is redundant. However, it is more readable and any decent compiler will optimize the second test away.
There is also a function named atom, which is a predicate that tests whether a value is an atom. That's why, when you write (atom 1) on its own, in the REPL, it returns T.
See also wikipedia and Seibel's Practical Common Lisp's chapter about lists.
Your function has two problems. The first, neatly described in coredump's answer, is that your COUNT-ATOMS only recurse on the tail (CDR) and forget to recurse on the CAR element of your cons cell (L). Thus it fails to count the B atom.
The second problem is that you count NIL as 0 while it is an atom and should be counted as 1.
This question already has answers here:
Why does this function return a different value every time?
(4 answers)
Unexpected persistence of data [duplicate]
(1 answer)
Closed 7 years ago.
First of all, let me say I'm a beginner in Lisp. To be honest I have been a beginner for some time now, but there are still many things I don't know well.
While I was writing this question, I came up with a strange bug in my code.
Here is a function that will return the list (0 1 ... n) with the list e appended. It uses rplacd along the way to keep track of the last element, to avoid a final call to last.
For example, (foo 4 '(x)) returns (0 1 2 3 4 x).
The "head" is stored in a, which is not simply nil, because there is only one nil, and never a copy of it (if I understand correctly), hence I can't simply append to nil.
(defun foo (n e)
(let* ((a (list nil)) (tail a))
(loop for i to n
do (rplacd tail (setf tail (list i)))
finally (rplacd tail (setf tail e))
(return (cdr a)))))
(defun bar (n e)
(let* ((a '(nil)) (tail a))
(loop for i to n
do (rplacd tail (setf tail (list i)))
finally (rplacd tail (setf tail e))
(return (cdr a)))))
The only difference between these functions is the (list nil) replaced by '(nil) in bar. While foo works as expected, bar always returns nil.
My initial guess is this happens because the original cdr of a is indeed nil, and the quoted list may be considered constant. However, if I do (setf x '(nil)) (rplacd x 1) I get (nil . 1) as expected, so I must be at least partially wrong.
When evaluated, '(nil) and (list nil) produce similar lists, but the former can be considered constant when present in source code. You should not perform any destructive operations on a constant quoted list in Common Lisp. See http://l1sp.org/cl/3.2.2.3 and http://l1sp.org/cl/quote. In particular, the latter says "The consequences are undefined if literal objects (including quoted objects) are destructively modified."
Quoted data is considered a constant. If you have two functions:
(defun test (&optional (arg '(0)))
(setf (car arg) (1+ (car arg)))
(car arg))
(defun test2 ()
'(0))
These are two functions both using the constant list (0) right?
The implementation may choose to not mutate constants:
(test) ; ==> Error, into the debugger we go
The implementation can cons the same list twice (the reader might do that for it)
(test2) ; ==> (0)
(test) ; ==> 1
(test) ; ==> 2
(test) ; ==> 3
(test2) ; ==> (0)
The implementation can see it's the same and hench save space:
(test2) ; ==> (0)
(test) ; ==> 1
(test) ; ==> 2
(test) ; ==> 3
(test2) ; ==> (3)
In fact. The last two behavior might happen in the same implementation dependent on the function being compiled or not.
In CLISP both functions work the same. I also see when disassembling with SBCL that the constant actually is mutated so I wonder if perhaps it has constant folded (cdr '(0)) at compile time and doesn't use the mutated list at all. It really doesn't matter since both are considered good "undefined" behavior.
The part from CLHS about this is very short
The consequences are undefined if literal objects (including quoted
objects) are destructively modified.
Given a list such as
(list "foo" "bar" nil "moo" "bar" "moo" nil "affe")
how would I build a new list with the duplicate strings removed, as well as the nils stripped, i.e.
(list "foo" "bar" "moo" "affe")
The order of the elements needs to be preserved - the first occurence of a string may not be removed.
The lists I'm dealing with here are short, so there's no need to use anything like a hash table for the uniqueness check, although doing so certainly wouldn't hurt either. However, using cl functionality is not a viable option.
Try "Sets and Lists" in the "Lists" section of the Emacs Lisp Reference Manual:
(delq nil (delete-dups (list "foo" "bar" nil "moo" "bar" "moo" nil "affe")))
The Common Lisp package contains many list manipulation functions, in particular remove-duplicates.
(require 'cl)
(remove-duplicates (list "foo" "bar" nil "moo" "bar" "moo" nil "affe")
:test (lambda (x y) (or (null y) (equal x y)))
:from-end t)
Yes, I realize you said you didn't want to use cl. But I'm still mentioning this as the right way to do it for other people who might read this thread.
(Why is cl not viable for you anyway? It's been shipped with Emacs for about 20 years now, not counting less featured past incarnations.)
If you use dash.el library, that's all you need:
(-distinct (-non-nil '(1 1 nil 2 2 nil 3)) ; => (1 2 3)
dash.el is written by Magnar Sveen and it's a great list manipulation library with many functions for all kinds of tasks. I recommend to install it if you write lots of Elisp code. Function -distinct removes duplicate elements in a list, -non-nil removes nil elements. While the above code is sufficient, below I describe an alternative approache, so feel free to ignore the rest of the post.
-non-nil was added in version 2.9, so if for some reason you have to use earlier versions, another way to achieve the same is to use -keep with built-in identity function, which just returns whatever it is given: (identity 1) ; => 1. The idea is that -keep keeps only elements, for which the predicate returns true (“non-nil” in Lisp jargon). identity obviously returns non-nil only for whatever values that are not nil:
(-distinct (-keep 'identity '(1 1 nil 2 2 nil 3)) ; => (1 2 3)
This is a short example:
(delete-duplicates '("~/.emacs.d" "~/.emacs.d") :test #'string-equal) ;; '("~/emacs.d")
Basically you use the :test keyword to select the function string-equal to test if the elements are duplicated.
Else the default function test doesn't check string equality.
Here ya go:
(defun strip-duplicates (list)
(let ((new-list nil))
(while list
(when (and (car list) (not (member (car list) new-list)))
(setq new-list (cons (car list) new-list)))
(setq list (cdr list)))
(nreverse new-list)))