From: Thierry Pirot on 17 Mar 2010 12:57 Tamas K Papp writes: > I am trying to rewrite GROUP from On Lisp without recursion (just as an > exercise). My first attempt is below, I am wondering if there is a > nicer (more idiomatic, etc) way to do it. > > (defun group (list n) > "Return elements of LIST as a list of lists in groups of N." > (check-type n (integer 1)) > (let (sublist > result > (i 0)) > (dolist (element list) > (push element sublist) > (incf i) > (when (= i n) > (push (nreverse sublist) result) > (setf i 0 > sublist nil))) > (assert (zerop i) () "~A could not be broken up to sublists of ~A elements" list n) > (nreverse result))) > I think it's nicer to separate the functionalities, such as MULTIPOP or SUBSEQ ; and the ASSERT should stand alone and before the main computation. (defun divides (divisor dividend) (= 0 (mod dividend divisor))) (defun group (a_sequence n) (check-type n (integer 1)) (assert (divides n (length a_sequence))) (loop for i to (1- (length a_sequence)) by n collect (subseq a_sequence i (+ n i)))) (defmacro multipop (a_list_place multi) "The list of MULTI elements popped from A_LIST_PLACE. " `(loop repeat ,multi collect (pop ,a_list_place))) (defun group (a_list by_n) (check-type by_n (integer 1)) (assert (divides by_n (length a_list))) (loop while a_list collect (multipop a_list by_n))) But why make things simple when they can be made complicated and pedantic ? I have had those parenthesis in my weapon box (defun regroup (a_sequence &optional (test #'eql)) "The regroupment of adjacent A_SEQUENCE's elements verifying TEST two by two. () -> (); (1) -> ((1)); (1 1) -> ((1 1)); (1 2) -> ((1) (2)); (1 2 2 3 3 3 1) -> ((1) (2 2) (3 3 3) (1)). " (reduce (lambda (x r) ;r is a partial result, ie a list of regroupments. (if (funcall test x (caar r)) (cons (cons x (car r)) (cdr r)) ;(1 1) -> ((1 1)) (cons (cons x () ) r ))) ;(1 2) -> ((1) (2)) a_sequence :from-end t :initial-value ())) ;indeed () is a list of 0 regroupment, ; while (()) is a list of 1 void regroupment. and also (defun cycled (period &key (offset 0)) "A closure predicating whether it is invoked for a multiple of PERIOD times. OFFSET sets off the initially 0 number of calls. Pre : OFFSET < PERIOD. " (check-type period (integer 1)) (assert (< offset period)) (let ((times offset)) ;; Invariant : times \in (1.. period) except ;; initially : times \in (offset .. period). (lambda (&rest ^) (declare (ignore ^)) (incf times) ;called once more. (when (> times period) (setf times 1)) ;invariant broken restored. (= times period)))) ;whether called period times. [which can be used as (let ((tricycled (cycled 3 :offset 2))) (loop for (x y z) on '(1 2 3 4 5 6 7 8 9 0) if (funcall tricycled) collect (list x y z)))] So, given REGROUP and CYCLED, (defun group (a_list n) (assert (divides n (length a_list))) (regroup a_list (complement (cycled n :offset -1)))) If this migth be nicer it would be because REGROUP solves a more general problem in an clearer way than using the same method for GROUP'ing, it does not mix the REGROUP'ing, the counting, the check. -- Take it Easy Don't Hurry Be Happy Thierry |