Getting the first n elements of a list in Common Lisp?
Asked Answered
W

8

25

How would I get the first n elements of a list?

CL-USER> (equal (some-function 2 '(1 20 300))
                '(1 20))
T

I am absolutely certain this is elementary, but help a brother newb out.

Wardle answered 12/11, 2009 at 2:39 Comment(0)
P
40

Check out the SUBSEQ function.

* (equal (subseq '(1 20 300) 0 2)
         '(1 20))
T

It may not be immediately obvious, but in Lisp, indexing starts from 0, and you're always taking half-open intervals, so this takes all the elements of the list with indices in the interval [0, 2).

Pyrimidine answered 12/11, 2009 at 3:30 Comment(3)
This has the unfortunate restriction that it will fail in runtime if the sequence is smaller than the ending index, which is a pity, because asking for the length before is highly inefficient. So how to proceed in this case?Custommade
Well, looking at Practical Common Lisp: gigamonkeys.com/book/loop-for-black-belts.html comes the right answer: (loop for item in list for i from 1 to 10 do (something)) where that something could be collecting item.Custommade
@DiegoSevilla Sometimes having the code fail at runtime when you don't have enough elements in your list is exactly the behavior you're looking for. A lot of idiomatic Common Lisp code builds fixed-size data structures out of conses instead of using DEFCLASS or DEFSTRUCT, and in those instances, if you don't have enough elements, getting tossed into the debugger immediately is the best thing that can happen to you.Pyrimidine
E
6

The above answer is of course perfectly correct, but note that if you're using this just to compare against another list, it would be more performance-efficient to walk both lists in-place, rather than consing up a new list just to compare.

For example, in the above case, you might say:

(every #'= '(1 20 300) '(1 20))
=> t
Exemplum answered 12/11, 2009 at 17:49 Comment(0)
V
1

Tinkering with the core CL functionalities:

(defun first-n (n list)
  "Returns the first N elements of the LIST."
  (butlast list (- (list-length list) n)))

(first-n 2 '(a s d f g))        ;(A S)
Verbal answered 29/12, 2019 at 15:45 Comment(1)
This has some unneeded overhead because it has to walk the entire list to determine the list length first. It will even fail if the list is circular.Orobanchaceous
U
0

Recursive:

(defun first-n (list n)
  "Returns the first n elements of the list."
  (when (not (zerop n))
    (cons (first list) (first-n (rest list) (1- n)))))

(first-n '(a b c d e) 3)        ;(A B C)

With loop:

(defun first-n-loop (list n)
  "Returns first n elements of the list."
  (loop for i below n
     collect (nth i list)))

(first-n-loop '(a b c d e) 3)       ;(A B C)
Ushas answered 16/7, 2019 at 12:25 Comment(2)
The recursive version will blow the stack on long lists, even in those implementations that do tail call optimization. The loop version has quadratic runtime because it walks the list for each element. Better: (loop :repeat n :for x :in list :collect x).Orobanchaceous
Note that the previous comments means it will blow the stack on long result lists, if n is large. And this also brings us to the bug: if n is larger than the list is long, it will return a list of length n padded with nil (well, I think CL is similar enough to Emacs Lisp in this regard; I didn't test it in a real CL :D)Hakan
O
0

@Pillsy has the answer. SUBSEQ.

But there is a difficulty with SUBSEQ that if you specify beyond the end of the list, you get an error. This is overcome by using LENGTH and MIN:

(defun first-n (n lst)
  (subseq lst 0 (min (length lst) n)))

For example:

CL-USER 11 > (first-n 3 (list 1 2 3 4 5 6 6))
(1 2 3)

CL-USER 16 > (first-n 10 '(1 2 3 4 5 6 6))
(1 2 3 4 5 6 6)
Oxygenate answered 27/2, 2023 at 22:8 Comment(0)
D
-1

Had to download a lisp command line... but:

(defun head-x (a b)
   (loop for x from 1 to a 
         for y = (car b) do 
            (setq b (cdr b)) 
         collect y))

so:

(head-x 2 '(a b c d))
  '(a b)
Dotdotage answered 12/11, 2009 at 3:39 Comment(2)
Just use (loop :repeat a :for x :in b :collect x) as your function body. Much simpler.Pyrimidine
Thanks... been years since I actually did any lisp. Thought it be fun to answer a question!Dotdotage
S
-1
(defun pncar (n L)
  (setq L_ (list (nth 0 L)))
  (setq i 0)
  (if (and (< n 1) (< n (length L)))
    (setq L_ '())
    (repeat (- n 1) (progn
                      (setq i (+ i 1))
                      (if (/= nil (nth i L))
                        (setq L_ (append (list (nth i L)) L_))
                        (setq L_ '())
                        )
                      )
      )
    )
  (setq L_ (reverse L_))
  )

Examples:

(pncar 0 '(0 1 2 3))
    nil
(pncar 1 '(0 1 2 3))
    (0)
(pncar 2 '(0 1 2 3))
    (0 1)
(pncar 3 '(0 1 2 3))
    (0 1 2)
(pncar 4 '(0 1 2 3))
    (0 1 2 3)
(pncar 5 '(0 1 2 3))
    nil
Signify answered 24/9, 2019 at 9:13 Comment(0)
A
-4

(butlast '(1 20 300) (- (list-length '(1 20 300)) 2))

Should be made into a function/macro.

P.S. This page might be useful. See function 'extrude'.

Airlee answered 16/12, 2009 at 18:41 Comment(1)
Definitely not a good idea to compute the length of a list for getting some elements from the front. Unlispy!Liripipe

© 2022 - 2024 — McMap. All rights reserved.