1

If I have an s expression, for example '(1 2 (3) (4 (5)) 6 7), how would I convert that into a list like (1 2 3 4 5 6 7)? I basically need to extract all of the atoms from the s expression. Is there a built in function that would help me do it?

(define (convert-to-list s) ... )

My algorithm so far is, if the first element is an atom append it onto a list. If the first element is a list then get the car of that element and then call the function (convert-to-list) with that function so it catches the base case of the recursion. And append the cdr of that list being invoked on convert-to-list to car of it. I'm trying to teach myself scheme from Structure and Interpretation of Computer Programs and I'm just trying out random things. Doing this recursively is proving to be more difficult than I anticipated.

tony
  • 23
  • 1
  • 5
  • 6
    Possible duplicate of [Flatten a list using only the forms in "The Little Schemer"](http://stackoverflow.com/questions/7313563/flatten-a-list-using-only-the-forms-in-the-little-schemer) and [Racket/Scheme Flatten Explanations](http://stackoverflow.com/questions/13547965/racket-scheme-flatten-explanations). (Both of those posts feature quality implementations of `flatten` that do not use `append`. Disclosure: I wrote one of those implementations.) – C. K. Young May 09 '13 at 02:58

5 Answers5

4

To literally answer your question, "Is there a built in function to help me do this?", in Racket yes there is. flatten does exactly this: "Flattens an arbitrary S-expression structure of pairs into a list."

Examples:
> (flatten '((a) b (c (d) . e) ()))
'(a b c d e)
> (flatten 'a)
'(a)

However it's a great exercise to think about how you would write flatten yourself.

Chris Jester-Young's comment has a link to an elegant way. If she'd posted that as an answer, instead of as a comment, I'd suggest marking her answer as accepted, not mine. :)

Óscar López
  • 232,561
  • 37
  • 312
  • 386
Greg Hendershott
  • 16,100
  • 6
  • 36
  • 53
1

Your algorithm doesn't look bad, it's just missing a step or two.

(define (flatten lst) ; 'flatten' is a better name for this function IMO
  (cond
    ((null lst) nil)
    ;; Don't worry that I'm calling (flatten (cdr lst)) without any checking;
    ;;  the above case handles it
    ((atom (car lst)) ; The car's okay
     (cons (car lst) (flatten (cdr lst))))
    ((cons? (car lst)) ; The car still needs flattening; note the use of
                       ;  'append' here (the car-list may have any number of elements)
     (append (flatten (car lst)) (flatten (cdr lst))))))

Between the (flatten (car lst)) calls dealing with the first element and the (flatten (cdr lst)) calls recursively dealing with the rest of the list, the input list ends up a flat list (i.e. no elements are conses).

(Warning: I'm not a Scheme guru; the above code may contain errors.)

1

Your cases should cover the empty list, an atom, (car s) being an atom, and (car s) being a list.

This works, though I bashed out a list append function because I didn't remember what the built-in one was. Works in Racket Advanced Student.

(define (list-glue left-list right-list)
  (cond
    ((null? left-list) right-list)
    (else (cons (car left-list) (list-glue (cdr left-list) (right-list))))))

(define (convert-to-list s)
  (cond 
    ((null? s) '())
    ((not (list? s)) (cons s (quote ())))
    ((not (list? (car s))) (cons (car s) (convert-to-list (cdr s))))
    (else 
      (list-glue 
        (convert-to-list (car s))
        (convert-to-list (cdr s))))))
pcurry
  • 1,374
  • 11
  • 23
1

Now, if you want a faster implementation, you don't need append at all.

The idea is to pass around what you would append onto as a parameter. I call this tail. If you have an empty s-exp, you just return the tail, since there is nothing to add to it.

I've got the code, flat and flat2, where flat uses a match statement, things in racket, and flat2 just uses a cond, which I find a little harder to read, but I provide it in case you haven't seen match yet.

#lang racket


(define (flat s-exp tail)
  (match s-exp
         ['() tail]
         [(cons fst rst)
          (let ([new-tail (flat rst tail)])
            (flat fst new-tail))]
         [atom
          (cons atom tail)]))

(define (flat
  (cond
    [(empty? s-exp) tail]
    [(list? s-exp)
     (let* ([fst (first s-exp)]
            [rst (rest  s-exp)]
            [new-tail (flat])
       (flat fst new-tail))]
    [#t 
     (cons s-exp tail)]))

To use them, call them like so (flat '(1 () (2 (3)) 4) '()) ===> '(1 2 3 4). You need to supply the empty list for them to start off on.

Theo Belaire
  • 2,980
  • 2
  • 22
  • 33
0

This can be done simply by recursing on sublists and rest-lists. You can see how easily this code reads. Like such:

(define (convert-to-list list)
  (if (null? list)
      '()
      (let ((next (car list))
            (rest (cdr list)))
        (if (list? next)
            (append (convert-to-list next) (convert-to-list rest))
            (cons next (convert-to-list rest))))))
> (convert-to-list '(a b c))
(a b c)
> (convert-to-list '((a b) (((c d) e f) g h) i j))
(a b c d e f g h i j)
> 
GoZoner
  • 67,920
  • 20
  • 95
  • 145