VansFannel
VansFannel

Reputation: 45911

Return a list without the last element

I've just started to learn Racket.

I have this code:

#lang racket

(define l1 '(1 2 3 4))
(car l1)
(cdr l1)

(car l1) returns 1.
(cdr l1) returns '(2 3 4)

Is there a function that returns '(1 2 3)?

I've tried this:

#lang racket

(define l1 '(1 2 3 4))
(map
 (lambda (l i)
   (if (not (= i (sub1 (length l1)))) l '()))
 l1 (range 0 (length l1)))

But, it returns: '(1 2 3 ())

And I have also tried:

#lang racket

(define l1 '(1 2 3 4))
(map
 (lambda (l i)
   (cond ((not (= i (sub1 (length l1)))) l )))
 l1 (range 0 (length l1)))

But, it returns: '(1 2 3 #<void>)

Upvotes: 1

Views: 2362

Answers (7)

user5920214
user5920214

Reputation:

Here is an equivalent of Will Ness's beautiful but-last-zip which does not rely on srfi/1 in Racket: without srfi/1 Racket's map insists that all its arguments are the same length (as does the R5RS version in fact) but it is common in other Lisps to have the function terminate at the end of the shortest list.

This function uses Racket's for/list and also wires in the assumption that the result for the empty list is the empty list.

#lang racket

(define (but-last-zip xs)
  (for/list ([x xs] [y (if (null? xs) xs (rest xs))])
    x))

I think Will's version is purer: mapping functions over things is a very Lisp thing to do I think, while for/list feels less Lispy to me. This version's only advantage is that it does not require a module.

Upvotes: 1

Will Ness
Will Ness

Reputation: 71070

Here's one more, via zipping:

#lang racket

(require srfi/1)

(define (but-last-zip xs)
  (if (null xs)
      xs                      ; or error, you choose
      (map (lambda (x y) x)
           xs
           (cdr xs))))

Here's another, emulating filtering via lists with appending, where empty lists disappear by themselves:

(define (but-last-app xs)
  (if (null? xs)
      xs
      (let ((n (length xs)))
        (apply append                  ; the magic
               (map (lambda (x i)
                      (if (= i (- n 1)) '() (list x)))
                    xs
                    (range n))))))

Or we could use the decorate--filter--undecorate directly, it's even more code!

(define (but-last-fil xs)
  (if (null? xs)
      xs
      (let ((n (length xs)))
        (map car
             (filter (lambda (x) (not (null? x)))
                     (map (lambda (x i)
                            (if (= i (- n 1)) '() (list x)))
                          xs
                          (range n)))))))

Upvotes: 2

Mulan
Mulan

Reputation: 135197

length is generally an anti-pattern in Scheme because the entire list needs to be read in order to get the result. W. Ness remarks that map does not alter the structure of the list, and the behavior of filter is based on the list's values, neither of which suit your needs.

Instead of making potentially expensive computations first or awkwardly applying the library functions, you can compute the init of a list using direct recursion -

(define (init l)
  (cond ((null? l)
         (error 'init "cannot get init of empty list"))
        ((null? (cdr l))
         null)
        (else
         (cons (car l)
               (init (cdr l))))))

(init '(a b c d e)) ;; '(a b c d)
(init '(a))         ;; '(a)
(init '())          ;; init: cannot get init of empty list

Or a tail-recursive form that only uses one reverse -

(define (init l)
  (let loop ((acc null)
             (l l))
    (cond ((null? l)
           (error 'init "cannot get init of empty list"))
          ((null? (cdr l))
           (reverse acc))
          (else
           (loop (cons (car l) acc)
                 (cdr l))))))

(init '(a b c d e)) ;; '(a b c d)
(init '(a))         ;; '(a)
(init '())          ;; init: cannot get init of empty list

And lastly a tail-recursive form that does not use length or reverse. For more intuition on how this works, see "How do collector functions work in Scheme?" -

(define (init l (return identity))
  (cond ((null? l)
         (error 'init "cannot get init of empty list"))
        ((null? (cdr l))
         (return null))
        (else
         (init (cdr l)
               (lambda (r)
                 (return (cons (car l) r)))))))

(init '(a b c d e)) ;; '(a b c d)
(init '(a))         ;; '(a)
(init '())          ;; init: cannot get init of empty list

Upvotes: 2

VansFannel
VansFannel

Reputation: 45911

My own solution using recursion:

#lang racket

(define but-last
  (lambda (l)
    (cond ((null? (cdr l)) '())
          (else (cons (car l) (but-last (cdr l)))))))

And another solution using filter-not and map:

#lang racket

(define l1 '(1 2 3 4))

(filter-not empty? (map
 (lambda (l i)
   (if (not (= i (sub1 (length l1)))) l empty))
 l1 (range 0 (length l1))))

Upvotes: 0

&#211;scar L&#243;pez
&#211;scar L&#243;pez

Reputation: 235984

Here's yet another alternative, assuming that the list is non-empty. It's efficient (it performs a single pass over the list), and it doesn't get any simpler than this!

(define (delete-last lst)
  (drop-right lst 1))

(delete-last '(1 2 3 4))
=> '(1 2 3)

Upvotes: 1

Eric Pitz
Eric Pitz

Reputation: 364

What about something like this ?

(define (myCdr l)
    (if (not (pair? (cdr l)))
        '()
        (cons (car l) (myCdr (cdr l)))
    )
)

Upvotes: 2

user448810
user448810

Reputation: 17851

The map function always returns a list the same length as its input. You want an output list that is shorter than its input. The function you are looking for is traditionally called but-last:

(define (but-last xs) (reverse (cdr (reverse xs))))

Upvotes: 4

Related Questions