From: Thierry Pirot on
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