Sorry, I forgot to CC.
---------- Forwarded message ---------- From: Gustavo gugamilare@gmail.com Date: 2010/8/8 Subject: Re: [alexandria-devel] Fold operators on lists and trees in Alexandria? To: Heka Treep zena.treep@gmail.com
Hello, Heka,
Just to clarify, I'm not a Alexandria developer, but I'll make some criticism.
I'm sorry but flatten/fold-right is not faster than flatten. It has a bug that prevents it from working correctly: fold-right version is destructive (it uses nreverse in the incoming list, it should be used in the result).Then, if you make a loop that flattens the same tree a lot of times, the original tree is modified so the running time is altered.
cl-user> (defvar tree '((1 2 3) (4 5 6))) tree cl-user> (flatten/fold-right tree) (1 2 3 4 5 6) cl-user> tree ((1))
It doesn't make sense flatten/fold-right to be faster than flatten because it calls append which needs to traverse the first argument, so it gets slower when the list to be flattened has large sublists. Also, it treats NIL as an atom while flatten treats it as the empty list.
The following version of flatten is the only one that I have noted to be faster than flatten (25% to 45% faster) because it doesn't have to call nreverse at the end:
(defun flatten/tail (tree) (let (list tail)
(labels ((traverse (subtree) (when subtree (cond ((consp subtree) (progn (traverse (car subtree)) (traverse (cdr subtree)))) (tail (setf (cdr tail) (cons subtree nil) tail (cdr tail))) (t (setf list (list subtree) tail list)))))) (traverse tree)) list))
Don't worry, everyone commits mistakes. It took me some time to figure out the problem too.
Gustavo.
2010/8/7 Heka Treep zena.treep@gmail.com
Sorry, I made a small typo:
(defun flatten/fold-rigth (list) (fold-rigth #'(lambda (e rest) (typecase e (atom (cons e rest)) - (list (append (flatten e) rest)) + (list (append (flatten/fold-rigth e) rest)))) list nil))
But this, of course, does not affect the benchmark.
2010/8/8 Heka Treep zena.treep@gmail.com
Hi.
I was found that there is no effective fold operators on lists or trees in CL. I know that `reduce' can do this (as an article in Wikipedia sayz) but `reduce' is not very effective. As stated in the Graham Hutton's article ``fold is a standard operator that encapsulates a simple pattern of recursion for processing lists'', also catamorphism at some ADT plays the same role. So I thought that if we introduce an effective fold operators, it becomes possible to express many functions through its shortly and effectively (in fact, almost any function on that ADT).
Take for example the `flatten' function that is defined in Alexandria as follows:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ok, TCO recursion: ;; (defun flatten (tree) (let (list) (labels ((traverse (subtree) (when subtree (if (consp subtree) (progn (traverse (car subtree)) (traverse (cdr subtree))) (push subtree list))))) (traverse tree)) (nreverse list)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
We need an ADT for the trees, but in the first approximation we can use nested lists.
When expressed in terms of reduce:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun flatten/reduce (list) (reduce #'(lambda (e rest) (typecase e (atom (cons e rest)) (list (append (flatten/reduce e) rest)))) list :initial-value nil :from-end t))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Now if we translate pure functional operator (here is `reduce') to the instructions for tagbody/go "state machine":
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; fold-left f '(a b c) i ;;; ;;; f ;;; /\ ;;; f c ;;; /\ ;;; f b ;;; /\ ;;; i a ;;; ;;; foldl f z [] = z ;;; foldl f z (x:xs) = foldl f (f z x) xs
(defun fold-left (function list &optional initial-value) (let ((result initial-value)) (tagbody :start (unless (endp list) (setq result (funcall function result (car list))) (setq list (cdr list)) (go :start))) result))
;;; fold-rigth f '(a b c) i ;;; ;;; f ;;; /\ ;;; a f ;;; /\ ;;; b f ;;; /\ ;;; c i ;;; ;;; foldr f z [] = z ;;; foldr f z (x:xs) = f x (foldr f z xs)
(defun fold-rigth (function list &optional initial-value) (let ((result initial-value) (list (nreverse list))) (tagbody :start (unless (endp list) (setq result (funcall function (car list) result)) (setq list (cdr list)) (go :start))) result))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Then `flatten' can be written as:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun flatten/fold-rigth (list) (fold-rigth #'(lambda (e rest) (typecase e (atom (cons e rest)) (list (append (flatten e) rest)))) list nil))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I try to benchmarc (this three functions) and has the following results:
flatten/fold-rigth
X time Y memory
alexandria:flatten
10 * X time 23 * Y memory
flatten/reduce
42 * X time 83 * Y memory
So, its look resonable to use folders there.
((sorry for my english - I just using google.translate :))
alexandria-devel mailing list alexandria-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/alexandria-devel
Thanks for this explanation, it helped. Now I can say that you was right - using a list folders on a trees is not a good idea. However, this folders expressed in an imperative style may be used to express some functions on the linear lists (hm, probably not - the list is the essence of Lisp, and all important functions are already defined in standard).
Well, I checked problem of the global variables affection:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun fold-left/list (function list &optional initial-value) (let ((list (copy-list list))) (tagbody :start (unless (endp list) (setq initial-value (funcall function initial-value (car list))) (setq list (cdr list)) (go :start))) initial-value))
(defun fold-rigth/list (function list &optional initial-value) (let ((list (nreverse (copy-list list)))) (tagbody :start (unless (endp list) (setq initial-value (funcall function (car list) initial-value)) (setq list (cdr list)) (go :start))) initial-value))
(defun flatten/fold-rigth (list) (fold-rigth/list #'(lambda (e rest) (if (consp e) (append (flatten/fold-rigth e) rest) (cons e rest))) list nil))
;; Also, this is a folder on vectors:
(defun fold-left/vector (function vector &optional initial-value) (declare (type vector vector)) (let ((i 0) (length (length vector))) (declare (type integer i length)) (tagbody :start (unless (>= i length) (setq initial-value (funcall function initial-value (aref vector i))) (setq i (1+ i)) (go :start))) initial-value)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
It work fine on small lists (better then flatten in TCO-way), but worse on big lists, for example:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defparameter *big-list* (labels ((gen-list (n) (if (zerop n) (random 10) (loop :for e :in (make-list n) :collect (gen-list (1- n)))))) (gen-list 9)))
(time (progn (alexandria:flatten *big-list*) nil))
1x time 1x memory
(time (progn (flatten/fold-rigth *big-list*) nil))
4x time 10x memory ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
By the way, this is a natural folder for trees:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun fold/tree (f g tree) (typecase tree (atom (funcall f tree)) (cons (apply g (mapcar #'(lambda (e) (fold/tree f g e)) tree)))))
(defun flatten/fold/tree (tree) (fold/tree #'identity #'list* tree))
(time (progn (flatten/fold/tree *big-list*) nil))
2x time 6x memory ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
if it would be possible to write this in an imperative style as well as fold-list this could give a good performance.
Ok, sorry for offtop ;)
2010/8/8, Gustavo gugamilare@gmail.com:
Sorry, I forgot to CC.
---------- Forwarded message ---------- From: Gustavo gugamilare@gmail.com Date: 2010/8/8 Subject: Re: [alexandria-devel] Fold operators on lists and trees in Alexandria? To: Heka Treep zena.treep@gmail.com
Hello, Heka,
Just to clarify, I'm not a Alexandria developer, but I'll make some criticism.
I'm sorry but flatten/fold-right is not faster than flatten. It has a bug that prevents it from working correctly: fold-right version is destructive (it uses nreverse in the incoming list, it should be used in the result).Then, if you make a loop that flattens the same tree a lot of times, the original tree is modified so the running time is altered.
cl-user> (defvar tree '((1 2 3) (4 5 6))) tree cl-user> (flatten/fold-right tree) (1 2 3 4 5 6) cl-user> tree ((1))
It doesn't make sense flatten/fold-right to be faster than flatten because it calls append which needs to traverse the first argument, so it gets slower when the list to be flattened has large sublists. Also, it treats NIL as an atom while flatten treats it as the empty list.
The following version of flatten is the only one that I have noted to be faster than flatten (25% to 45% faster) because it doesn't have to call nreverse at the end:
(defun flatten/tail (tree) (let (list tail)
(labels ((traverse (subtree) (when subtree (cond ((consp subtree) (progn (traverse (car subtree)) (traverse (cdr subtree)))) (tail (setf (cdr tail) (cons subtree nil) tail (cdr tail))) (t (setf list (list subtree) tail list)))))) (traverse tree)) list))
Don't worry, everyone commits mistakes. It took me some time to figure out the problem too.
Gustavo.
2010/8/7 Heka Treep zena.treep@gmail.com
Sorry, I made a small typo:
(defun flatten/fold-rigth (list) (fold-rigth #'(lambda (e rest) (typecase e (atom (cons e rest)) - (list (append (flatten e) rest)) + (list (append (flatten/fold-rigth e) rest)))) list nil))
But this, of course, does not affect the benchmark.
2010/8/8 Heka Treep zena.treep@gmail.com
Hi.
I was found that there is no effective fold operators on lists or trees in CL. I know that `reduce' can do this (as an article in Wikipedia sayz) but `reduce' is not very effective. As stated in the Graham Hutton's article ``fold is a standard operator that encapsulates a simple pattern of recursion for processing lists'', also catamorphism at some ADT plays the same role. So I thought that if we introduce an effective fold operators, it becomes possible to express many functions through its shortly and effectively (in fact, almost any function on that ADT).
Take for example the `flatten' function that is defined in Alexandria as follows:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ok, TCO recursion: ;; (defun flatten (tree) (let (list) (labels ((traverse (subtree) (when subtree (if (consp subtree) (progn (traverse (car subtree)) (traverse (cdr subtree))) (push subtree list))))) (traverse tree)) (nreverse list)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
We need an ADT for the trees, but in the first approximation we can use nested lists.
When expressed in terms of reduce:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun flatten/reduce (list) (reduce #'(lambda (e rest) (typecase e (atom (cons e rest)) (list (append (flatten/reduce e) rest)))) list :initial-value nil :from-end t))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Now if we translate pure functional operator (here is `reduce') to the instructions for tagbody/go "state machine":
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; fold-left f '(a b c) i ;;; ;;; f ;;; /\ ;;; f c ;;; /\ ;;; f b ;;; /\ ;;; i a ;;; ;;; foldl f z [] = z ;;; foldl f z (x:xs) = foldl f (f z x) xs
(defun fold-left (function list &optional initial-value) (let ((result initial-value)) (tagbody :start (unless (endp list) (setq result (funcall function result (car list))) (setq list (cdr list)) (go :start))) result))
;;; fold-rigth f '(a b c) i ;;; ;;; f ;;; /\ ;;; a f ;;; /\ ;;; b f ;;; /\ ;;; c i ;;; ;;; foldr f z [] = z ;;; foldr f z (x:xs) = f x (foldr f z xs)
(defun fold-rigth (function list &optional initial-value) (let ((result initial-value) (list (nreverse list))) (tagbody :start (unless (endp list) (setq result (funcall function (car list) result)) (setq list (cdr list)) (go :start))) result))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Then `flatten' can be written as:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun flatten/fold-rigth (list) (fold-rigth #'(lambda (e rest) (typecase e (atom (cons e rest)) (list (append (flatten e) rest)))) list nil))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I try to benchmarc (this three functions) and has the following results:
flatten/fold-rigth
X time Y memory
alexandria:flatten
10 * X time 23 * Y memory
flatten/reduce
42 * X time 83 * Y memory
So, its look resonable to use folders there.
((sorry for my english - I just using google.translate :))
alexandria-devel mailing list alexandria-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/alexandria-devel
alexandria-devel@common-lisp.net