Hellseher
Hellseher

Reputation: 286

How to transform list into sub lists?

((1 2 3)
(2 3 4)
(3 4 5)
(4 5 6))

from

(1 2 3 4 5 6)

And what is the type of such operation?

What I tried:

(loop
   :with l2 = '()
   :with l1 = '(1 2 3 4 5 6)
   :for i :in l1
   :do (push (subseq l1 0 3) l2))

Upvotes: 0

Views: 346

Answers (5)

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 10010

Alternatively use map:

(let ((l '(1 2 3 4 5 6)))
  (map 'list #'list l (cdr l) (cddr l)))

;; ((1 2 3) (2 3 4) (3 4 5) (4 5 6))

You can read it as:

  • for list l with values (1 2 3 4 5 6)
  • map over the list and its two successive cdrs
  • by applying #'list on the elements of the lists map is looping through in parallel
  • (stopping when shortest list is used up)
  • and collecting the results as/into a 'list

@WillNess suggested even simpler:

(let ((l '(1 2 3 4 5 6)))
  (mapcar #'list l (cdr l) (cddr l)))

thanks! So then we could generalize using only map variants:

(defun subseqs-of-n (l n)
  (apply #'mapcar #'list (subseq (maplist #'identity l) 0 n)))

(maplist #'identity l) is equivalent to (loop for sl on l collect sl). However,

(loop for sl on l
      for i from 0 to n
      collect sl)

is better because it stops at n-th round of looping ...

Upvotes: 4

Barmar
Barmar

Reputation: 782407

You're pushing the same sublist every time through the loop.

You can use :for sublist on to loop over successive tails of a list.

And use :collect to make a list of all the results, rather than pushing onto your own list

(loop
   :for l1 on '(1 2 3 4 5 6)
   :if (>= (length l1) 3)
      :collect (subseq l1 0 3)
   :else 
      :do (loop-finish))

Upvotes: 4

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 10010

Or the classical more C-like for-loop-ing with indexes to solve it. But use it more on strings/vectors but less on lists, because its performance is

  • for lists quadratic
  • for vectors (strings!) linear, so preferably to be used with them!

credits and thanks to @WillNess who pointed both points out (see comments below).

(defun subseqs-of-n (ls n) ;; works on strings, too!
  (loop :for i :from 0 :to (- (length ls) n)
        :collect (subseq ls i (+ i n))))

So on vectors/strings use:

(subseqs-of-n "gattaca" 5)
;; ("gatta" "attac" "ttaca")

Upvotes: 1

Rainer Joswig
Rainer Joswig

Reputation: 139401

First let's define a function take-n, which either returns n items or an empty list, if there are not enough items. It will not scan the whole list.

(defun take-n (n list)
  (loop repeat n
        when (null list) return (values nil nil)
        collect (pop list)))

Then we move this function take-n over the list until it returns NIL.

(defun moving-slice (n list)
  (loop for l on list
        for p = (take-n n l)
        while p
        collect p))

Example:

CL-USER 207 > (moving-slice 3 '(1 2))
NIL

CL-USER 208 > (moving-slice 3 '(1 2 3))
((1 2 3))

CL-USER 209 > (moving-slice 3 '(1 2 3 4 5 6 7))
((1 2 3) (2 3 4) (3 4 5) (4 5 6) (5 6 7))

Upvotes: 3

user5920214
user5920214

Reputation:

Here's a version of Barmar's answer (which should be the accepted one) which is a bit more general and only calls length once.

(defun successive-leading-parts (l n)
  (loop repeat (1+ (- (length l) n))
        for lt on l
        collect (subseq lt 0 n)))
> (successive-leading-parts '(1 2 3 4) 3)
((1 2 3) (2 3 4))

> (successive-leading-parts '(1 2 3 4) 2)
((1 2) (2 3) (3 4))

Upvotes: 2

Related Questions