![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Given a test and a list, BIPART divides the list in two: the ones which pass the test, and the ones which don't.
;;;I want to implement such a bipart function functionally: no PUSHs, and no LOOPS
;;using 'select' and 'not-p' from my personal LISP library, it's easy to come up with:
;;ok, let me try something else:
;;I thought I had it until the tests showed that the list comes back backwards. The reason is that the 'cons' is inside the recursive call. (couldn't we use a backwards CONS?)
;;Calling a reverse function would be inefficient. So can we bring it out, as in the following code?
;;this one is the right way around. Can we apply it to the general bipart?
;;the problem is that 'bipart' keeps two lists, so it's more complex:
;;;PROBLEM: we're going in and out of the pair, and unless some smart optimization is going on, it's going to be inefficient.
;;We clean it up a bit, with a 'let':
;;Clean it up even more, with an 'flet' this time:
;;it's still bad that we are calling the if with the same condition twice, so we
;;ok, we've abbreviated the condition, but the code "if t" is still redundant
Recursive functions often have similarly redundant code. Should we start using higher-level code, or meta-programming in such cases?
;;;I want to implement such a bipart function functionally: no PUSHs, and no LOOPS
;;using 'select' and 'not-p' from my personal LISP library, it's easy to come up with:
(defun bipart (test l) (list (select test l) (select (not-p test) l))) ;;PROBLEM: inefficient: will do each test twice
;;ok, let me try something else:
(defun bipart (test l yeses noes) (cond ((null l) (list yeses noes)) ((funcall test (car l)) (bipart test (cdr l) (cons (car l) yeses) noes)) (T (bipart test (cdr l) yeses (cons (car l) noes)))))
;;I thought I had it until the tests showed that the list comes back backwards. The reason is that the 'cons' is inside the recursive call. (couldn't we use a backwards CONS?)
;;Calling a reverse function would be inefficient. So can we bring it out, as in the following code?
(defun bipart-yes (test l) ;;;i.e. the same as 'select' (cond ((null l) nil) ((funcall test (car l)) (cons (car l) (bipart test (cdr l)))) (T (bipart test (cdr l)))))
;;this one is the right way around. Can we apply it to the general bipart?
;;the problem is that 'bipart' keeps two lists, so it's more complex:
(defun bipart (test l) (cond ((null l) (list nil nil)) ((funcall test (car l)) (list (cons (car l) (first (bipart test (cdr l)))) (second (bipart test (cdr l))))) (T (list (first (bipart test (cdr l))) (cons (car l) (second (bipart test (cdr l))))))))
;;;PROBLEM: we're going in and out of the pair, and unless some smart optimization is going on, it's going to be inefficient.
;;We clean it up a bit, with a 'let':
(defun bipart (test l) (if (null l) (list nil nil) (let ((bip (bipart test (cdr l)))) (if (funcall test (car l)) (list (cons (car l) (first bip)) (second bip)) (list (first bip) (cons (car l) (second bip)))))))
;;Clean it up even more, with an 'flet' this time:
(defun bipart (test l) (if (null l) (list nil nil) (let ((bip (bipart test (cdr l)))) (flet ((app #'(lambda (x) (cons (car l) x)))) (list (if (funcall test (car l)) (app (first bip)) (first bip)) (if (funcall test (car l)) (second bip) (app (second bip)))))))
;;it's still bad that we are calling the if with the same condition twice, so we
(defun bipart (test l) (if (null l) (list nil nil) (let ((bip (bipart test (cdr l))) ((t (funcall test (car l))))) (flet ((app #'(lambda (x) (cons (car l) x)))) (list (if t (app (first bip)) (first bip)) (if t (second bip) (app (second bip))))))))
;;ok, we've abbreviated the condition, but the code "if t" is still redundant
Recursive functions often have similarly redundant code. Should we start using higher-level code, or meta-programming in such cases?
partition
Date: 2005-06-17 06:10 pm (UTC)Why not do what Haskell does?
Re: partition
Date: 2005-06-17 06:29 pm (UTC)Let me guess: you're defining 'select' on the fly.
applies select iteratively for each element of xs starting out with ([],[]). I don't know of a similar function in Lisp (or any language, for that matter).
It's interesting that you didn't have to type 'x' or 'select', since 'partition' is typed.
Btw, I think 'insert' would be a better name for 'select'.
Re: partition
Date: 2005-06-17 06:51 pm (UTC)foldr
, right fold, is a fundamental list operation. It must exist in lisp. You are right about the cons notation.Actually you don’t have to type anything at all.
partition
is typed for both documentation, and preventing errors.Re: partition
Date: 2005-06-17 07:04 pm (UTC)Re: partition
Date: 2005-06-17 07:11 pm (UTC)You need to flip around your function call and recursive call in your implementation of
foldr
. What you’ve got there isfoldl
.Re: partition
Date: 2005-06-17 07:20 pm (UTC)foldl
is tail-recusive butfoldr
is not?That's not very symmetrical, IMHO!
Re: partition
Date: 2005-06-17 07:25 pm (UTC)That’s the way it is. Tail-recursion isn’t preserved under this symmetry. In this case there is no point in being tail-recursive because your output is O(n) anyways.
Re: partition
Date: 2005-06-20 06:53 pm (UTC)Re: partition
Date: 2005-06-17 08:23 pm (UTC)There's definitely an argument to be made for a "heavier" list implementation: doubly linked with two head pointers. Then cons is symmetric (append is constant time), and reverse is also constant time (just swap the head pointers), and it would require only n+1 more pointers for an n element list. However, this would have to be natively implemented and use opaque primitive operations. The advantage of the list construct of Haskell and friends is that it's directly expressible as a datatype; you can "do it yourself" if you really want to, and it requires no notion of pointers or memory traversal.
Re: partition
Date: 2005-06-17 08:25 pm (UTC)Re: partition
Date: 2005-06-17 06:49 pm (UTC)Haskell is handier than Lisp with its pattern-matching, since you can write (ts,fs) to represent any pair (two element list, right?).
... although
Re: partition
Date: 2005-06-17 07:05 pm (UTC)Actually, pairs are not two-element lists in Haskell (or any ML-like language), which pinpoints an interesting distinction between Haskell/ML-family and Lisp. In ML-like languages, lists must be homogeneous in type: [1,2,3] is a legal list, whereas [1, "one", (fn f => (fn x => f x))] is not. Tuples can be heterogeneous (2, "two"), but their length (2), and type (int * string), must be known statically.
(no subject)
Date: 2005-06-17 07:31 pm (UTC)listof any size?Sum Types
Date: 2005-06-17 08:13 pm (UTC)There is none. However there are sum types (and lists of sum types) which solve all the problems you may worry about.
Re: Sum Types
Date: 2005-06-17 08:20 pm (UTC)Re: Sum Types
Date: 2005-06-17 08:47 pm (UTC)Re: partition
Date: 2005-06-17 07:33 pm (UTC)Re: partition
Date: 2005-06-17 08:11 pm (UTC)Re: partition
Date: 2005-06-28 08:36 pm (UTC)You could do polymorphic functions working on both types, but behaving differently - i.e.
(no subject)
Date: 2005-06-17 11:27 pm (UTC)I'd like to complain about the "l" too -- looks like a 1.
How I would write it:
(no subject)
Date: 2005-06-20 06:08 pm (UTC)(no subject)
Date: 2005-06-20 05:50 pm (UTC)select = remove-if-not (http://www.lispworks.com/documentation/HyperSpec/Body/f_rm_rm.htm#remove-if-not)
not-p = complement (http://www.lispworks.com/documentation/HyperSpec/Body/f_comple.htm)
foldl/foldr = reduce (http://www.lispworks.com/documentation/HyperSpec/Body/f_reduce.htm#reduce)
-- Sebastian "What-if-I-don't-want-to-sign-my-name?" Stern
(no subject)
Date: 2005-06-20 06:06 pm (UTC)Re: 'not-p', I also have an 'and-p', 'or-p' and 'implies-p'. Unless all of these are conveniently named in Lisp, I think my standard is worthy of existence.
Re: select vs remove-if-not
remove-if-not is a terrible name, because I'm not mutating anything. It's also too long.
>"What-if-I-don't-want-to-sign-my-name?"
You won't be notified of my reply, and if it takes me days to get around to it, I'll have no idea who it is, and I'll probably think that it's not worth responding to. This is the reason I invited you to join LJ... you don't even have to login if you use a cookie. You could also read my dark secrets that way.
(no subject)
Date: 2005-06-20 06:27 pm (UTC)I think you need the :from-end t option.
Re: 'not-p', I also have an 'and-p', 'or-p' and 'implies-p'. Unless all of these are conveniently named in Lisp, I think my standard is worthy of existence.
Maybe a character macro would be more convenient. Something like (!and ... ...).
You won't be notified of my reply
Which notification do you mean?
(no subject)
Date: 2005-06-20 06:35 pm (UTC)(2) I'd like to learn about that.
(3) The link to this that I send to your email. Do you have a bot to watch this page for you? Anyway, a reply to an anonymous comment is quite likely to never be read by anyone.
(no subject)
Date: 2005-06-20 06:44 pm (UTC)How would you write a REVERSE using REDUCE and #'cons?
reduce
Date: 2005-06-28 08:33 pm (UTC)is
(foo (foo (foo nil 1) 2) 3)
(reduce #'foo '(1 2 3) :from-end t :initial-value nil)
is
(foo 1 (foo 2 (foo 3 nil)))
Also, I would write bipart like this, but that may just be me:
(no subject)
Date: 2005-06-28 08:35 pm (UTC)(no subject)
Date: 2005-06-27 03:15 pm (UTC)(no subject)
Date: 2005-06-27 04:23 pm (UTC)If we're passing a simply-linked list,
nreverse
will be just as bad as reverse, right?(no subject)
Date: 2005-06-27 04:28 pm (UTC)