0

how do i append (1 2 3) to the end of () to make ((1 2 3))
how do i append (4 5 6) to the end of that to make ((1 2 3) (4 5 6))
how do i append "|" to the end of that to make ((1 2 3) (4 5 6) "|")

with NO dotted pairs.

I'm working with Chicken Scheme but I'll take an answer from any scheme at this point. Note that any of these lists could also be nested lists of who knows what... i'm just writing a trivial example.

note: @sjamaan shows a solution using append that involves wrapping everything in another list to compensate for append doing things OTHER than what the name says.

(append (list 1 2 3) "|" ) ;=> (1 2 3 . "|") ;^^ didn't actually append, created a dotted pair (append '(1 2 3) (list 4 5 6)) ;=> (1 2 3 4 5 6) ; don't want unwrapped list ;^^ didn't actually append the list i gave it but appended the contents of the list.

Basically I'm hoping for an append method that actually appends what you give it, not appends the contents of it, or takes it and makes a dotted pair. Maybe i'm just a dreamer... I can write a "no really append" method that just takes whatever params you give it and wraps them in an outer list to compensate but that's just silly... Surely scheme has some way to append without this crazyness.

masukomi
  • 10,313
  • 10
  • 40
  • 49
  • Possible duplicate of [what is the 'cons' to add an item to the end of the list?](https://stackoverflow.com/questions/6439972/what-is-the-cons-to-add-an-item-to-the-end-of-the-list) – qfwfq Jun 23 '17 at 04:46
  • @jcolemang it's similar but not same. the other question is rather ambiguous, and doesn't cover the same ground. It and its answers fail to address the fact that appending a list doesn't append a list but the contents, and doesn't address appending a single item. – masukomi Jun 25 '17 at 02:41

4 Answers4

4

Here is how append is made:

(define (append2 lst1 lst2)
  (if (null? lst1)
      lst2                               ; the second list is unaltered
      (cons (car lst1)
            (append2 (cdr lst1) lst2))))

makes a pair chain consisting of all the elements in lst1 and lst2. It does not make a pair where there is nont in lst2 so:

(append2 '(1 2 3) '(4 5)) ; ==> (1 2 3 4 5)
(append2 '(1 2 3) '())    ; ==> (1 2 3) and not (1 2 3 ())
(append2 '(1 2 3) '5)     ; ==> (1 2 3 . 5)

Note that every list like (1 2 3) actually is (1 2 3 . ()) or even more correctly (1 . (2 . (3 . ())))

how do i append (1 2 3) to the end of () to make ((1 2 3))

(define (insert-last e lst)
  (let helper ((lst lst))
    (if (pair? lst)
        (cons (car lst)
              (helper (cdr lst)))
        (cons e '()))))

(insert-last '(1 2 3) '())                    
; ==> ((1 2 3))

how do i append (4 5 6) to the end of that to make ((1 2 3) (4 5 6))

(insert-last '(4 5 6) '((1 2 3)))  
; ==> ((1 2 3) (4 5 6))

how do i append "|" to the end of that to make ((1 2 3) (4 5 6) "|")

(insert-last "|" '((1 2 3) (4 5 6)))  
; ==> ((1 2 3) (4 5 6) "|")

Know that this is very much like append. These are the worst way to make that list since you are making a new list every time. It's O(n) for each insert and O(n^2) for n elements. If you could do this in reverse order you get something that do this O(1) instead of O(n) for each insert. Instead of insert-last you use cons:

(cons '"|" '())               ; ==> ("|")
(cons '(4 5 6) '("|"))        ; ==> ((4 5 6) "|")
(cons '(1 2 3) '((4 5 6) "|") ; ==> ((1 2 3) (4 5 6) "|")

This is O(1), O(n) for n elements processed. If you need to do it in the original order you can accumulate, then reverse..

(cons '(1 2 3) '())                ; ==> ((1 2 3))
(cons '(4 5 6) '((1 2 3)))         ; ==> ((4 5 6) (1 2 3))
(cons '"|" '((4 5 6) (1 2 3)))   ; ==> ("|" (4 5 6) (1 2 3))
(reverse '("|" (4 5 6) (1 2 3))  ; ==> ((1 2 3) (4 5 6) "|")

This is O(1), then O(n) for the reverse but it still is O(1) amortized. O(n) for n elements you process.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • thank you for the time and effort this answer took. I'll note that the appending `"|"` answer wasn't _quite_ right, but you've definitely answered the heart of the question. – masukomi Jun 23 '17 at 22:51
  • 1
    I've heard `insert-last` called `snoc` before, incidentally. – Dietrich Epp Jun 24 '17 at 01:51
3

append doesn't append atoms to lists. It concatenates lists. You have to lift the atom up to a list before concatenation makes sense.

(append xs (list y))

But it makes sense to point out (reverse (cons y (reverse xs))) which has the same result. reverse suggests that you might be building up your list backwards if you need to append atoms to the end.

Dan D.
  • 73,243
  • 15
  • 104
  • 123
2

The procedure you're looking for is unsurprisingly called append (from SRFI-1). It appends a list of things onto another list. This does mean that if you want to add just one item, you'll need to make a list out of it:

(append '() '((1 2 3))) => ((1 2 3))
(append '((1 2 3)) '((4 5 6))) => ((1 2 3) (4 5 6))
(append '((1 2 3) (4 5 6)) '("|") ) => ((1 2 3) (4 5 6) "|")

It accepts multiple arguments, which will all be appended to eachother in that order, so you can also do:

(append '() '((1 2 3)) '((4 5 6)) '("|")) => ((1 2 3) (4 5 6) "|")

Hope this helps!

sjamaan
  • 2,282
  • 10
  • 19
  • 1
    I get what you're doing, but it's not quite the same. The difference is that, you're working around append's stupidity by wrapping everything in another list. Adding the items of one list ot another would be something like `(map (labmda(x)(append destination-list (list x)) source-list)`. I'm hoping for a solution that isn't "Hey, nest everything in another list because append is stupid and then do it. " Is there no more direct version? – masukomi Jun 23 '17 at 11:41
  • The answer really is to wrap everything in a list. The reason is that append already **has** to reconstruct the list cell structure of all of lists except for the last one (containing the atom). For lists it makes a lot of sense to occasionally do that, but continually adding an atom at the end is extremely inefficient, therefore it doesn't make sense to have a specialised procedure just for that. So, if you're doing that, look at @dan-d's suggestion to maintain the reverse list, cons onto the front continually and reverse it once at the end. That's a typical usage pattern in Scheme. – sjamaan Jun 23 '17 at 15:17
  • Alternatively, you can hold onto the last cdr in the list and call `set-cdr!` with (again) a list containing just the atom. I would only recommend doing that in performance-critical code, as it obscures your algorithm and if the list is passed in it messes with the caller's data. Note that this is the typical way you'd do this with singly linked lists in C or other imperative languages. – sjamaan Jun 23 '17 at 15:20
  • thank you. I'm accepting @Sylwester 's as the answer because it's going to be the most useful to folks, but i really appreciate the extra explanation. – masukomi Jun 23 '17 at 22:50
  • I agree, @Sylwester's reply is the most thorough. – sjamaan Jun 24 '17 at 10:41
0

Whether you want it or not, cons cells will be created, since lists consist of cons cells.

how do i append (1 2 3) to the end of () to make ((1 2 3))

CL-USER 24 > (list '(1 2 3))
((1 2 3))

how do i append (4 5 6) to the end of that to make ((1 2 3) (4 5 6))

CL-USER 25 > (append '((1 2 3)) (list '(4 5 6)))
((1 2 3) (4 5 6))

how do i append "|" to the end of that to make ((1 2 3) (4 5 6) "|")

CL-USER 26 > (append '((1 2 3) (4 5 6)) (list "|"))
((1 2 3) (4 5 6) "|")
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346