1

I am trying to concatenate all elements in the list argument into a single list. I have this code:

(define (concatenate . lsts)
 (let rec ([l lsts]
          [acc '()])
   (if (empty? l)
       acc
       (rec (cons (list* l)
                 acc)))))

An example of output is here:

 > (concatenate '(1 2 3) '(hi bye) '(4 5 6))
 '(1 2 3 hi bye 4 5 6)

But I keep getting this error:

 rec: arity mismatch;
 the expected number of arguments does not match the given number
  expected: 2
  given: 1

Can someone please explain this?

Lol Andy
  • 11
  • 2
  • Your named let needs two arguments, you're only calling it with one – Shawn Sep 26 '22 at 04:40
  • You forgot to recurse on `l`. Note that if you fix this you will end up with `'(((4 5 6)) ((hi bye) (4 5 6)) ((1 2 3) (hi bye) (4 5 6)))` so you need to think a little bit more. You might want to look at what `(cons (list* '(1 2 3)) '(a b c))` is first of all. – molbdnilo Sep 26 '22 at 08:24
  • @molbdnilo When I use (cons 1 (cons 2 empty)), I get '(1 2) and when i use (cons '(1 2 3) (cons '( 4 5 6) empty)), i get '((1 2 3) (4 5 6)). I understand the output is based on the data type. The first one is int and second one is list. But how do I get an output without brackets. – Lol Andy Sep 26 '22 at 22:17
  • @Shawn how possibly can I call one more argument there ? Can you please give an example? – Lol Andy Sep 26 '22 at 22:19
  • It's not based on the types; `(cons a b)` always creates a pair with `a` as the first element and `b` as the second. `'(1 2)` and `'((1 2 3) (4 5 6))` are both lists with two elements. Look for `append` in the Racket documentation and experiment a little. – molbdnilo Sep 27 '22 at 07:56
  • You pass arguments to a named `let` in the same way as you do with a procedure. Read about it in the Racket guide (search for "named let"). – molbdnilo Sep 27 '22 at 07:58

2 Answers2

1

Another answer explains the OP error, and shows how the code can be fixed using append. But there could be reasons for append to be disallowed in this assignment (of course, it could be replaced with, for example, an inner "named let" iteration).

This answer will present an alternative approach and describe how it can be derived.

#lang racket
(require test-engine/racket-tests)

(define (conc . lols) ;; ("List of Lists" -> List)
  ;; produce (in order) the elements of the list elements of lols as one list
  ;; example: (conc '(1 2 3) '(hi bye) '(4 5 6)) => '(1 2 3 hi bye 4 5 6)
  (cond
    [(andmap null? lols) empty ]   ;(1) => empty result
    [else
     (cons (if (null? (car lols))  ;(2) => head of result
               (car (apply conc (cdr lols)))
               (caar lols))
           (apply conc             ;(3) => tail of result
                  (cond
                    [(null? (car lols))
                     (list (cdr (apply conc (cdr lols)))) ]
                    [(null? (cdar lols))
                     (cdr lols) ]
                    [else
                     (cons (cdar lols) (cdr lols)) ]))) ]))

(check-expect (conc '() )           '())
(check-expect (conc '() '() )       '())
(check-expect (conc '(1) )          '(1))
(check-expect (conc '() '(1) )      '(1))
(check-expect (conc '() '(1 2) )    '(1 2))
(check-expect (conc '(1) '() )      '(1))
(check-expect (conc '(1) '(2) )     '(1 2))
(check-expect (conc '(1 2) '(3 4) ) '(1 2 3 4))
(check-expect (conc '(1 2 3) '(hi bye) '(4 5 6)) '(1 2 3 hi bye 4 5 6))

(test)
Welcome to DrRacket, version 8.6 [cs].
Language: racket, with debugging; memory limit: 128 MB.
All 8 tests passed!
> 

How was this code derived?

"The observation that program structure follows data structure is a key lesson in introductory programming" [1]

A systematic program design method can be used to derive function code from the structure of arguments. For a List argument, a simple template (natural recursion) is often appropriate:

(define (fn lox) ;; (Listof X) -> Y                  ; *template*
  ;; produce a Y from lox using natural recursion    ;
  (cond                                              ;
    [(empty? lox) ... ]  #|base case|# ;; Y          ;
    [else (...           #|something|# ;; X Y -> Y   ;
            (first lox) (fn (rest lox))) ]))         ;

(Here the ...s are placeholders to be replaced by code to create a particular list-argumented function; eg with 0 and + the result is (sum list-of-numbers), with empty and cons it's list-copy; many list functions follow this pattern. Racket's "Student Languages" support placeholders.)

Gibbons [1] points out that corecursion, a design recipe based on result structure, can also be helpful, and says:

For a structurally corecursive program towards lists, there are three questions to ask:

  1. When is the output empty?
  2. If the output isn’t empty, what is its head?
  3. And from what data is its tail recursively constructed?

So for simple corecursion producing a List result, a template could be:

(define (fn x) ;; X -> ListOfY
  ;; produce list of y from x using natural corecursion
  (cond
    [... empty]           ;(1) ... => empty
    [else (cons ...       ;(2) ... => head
           (fn ...)) ]))  ;(3) ... => tail data

Examples are useful to work out what should replace the placeholders:

the design recipe for structural recursion calls for examples that cover all possible input variants, examples for co-programs should cover all possible output variants.

The check-expect examples above can be worked through to derive (1), (2), and (3).

[1] Gibbons 2021 How to design co-programs

Will Ness
  • 70,110
  • 9
  • 98
  • 181
mnemenaut
  • 684
  • 2
  • 11
  • regarding your first version, it reminds me of these [two](https://stackoverflow.com/a/71620886/849891) [answers](https://stackoverflow.com/a/19536834/849891) of mine, with their remarkable computational inefficiency. :) :) this can of course be easily fixed with a few `let`'s, but the other thing is, I read this Q as asking for an accumulator-based tail-recursive solution. (of course my using `append` isn't quite right in this respect, but it at least follows it by the code structure). – Will Ness Sep 29 '22 at 17:26
  • re corecursion, unfortunately strict cons expresses recursion as far as I understand, only _lazy_ cons expresses corecursion. my personal opinion is that another way to expresses corecursion is through iterative set-cdr!-ing of the growing list's tail, as can be seen in [this answer](https://stackoverflow.com/a/13256555/849891) of mine. I didn't use it in my answer here as I wasn't sure whether set-cdr! would be appropriate here. – Will Ness Sep 29 '22 at 17:36
  • putting the lazy/strict distinction aside, your corecursion recipe is "anamorphism" I think. there's also "apomorphism", `(define (fn x) (cond [... empty] [... new-tail] [else (cons head (fn tail-data)) ]))`, which I think will allow to avoid that re-calculation to which I referred above. except of course `new-tail` would actually have to call `fn` too, `(fn (cdr lols))`, so I'm not sure if it's strictly apomorphism or not. probably not. that call would be considered recursive, I think. it's like `filter` can't be corecursive, since there's no way to define a step guaranteed to be productive. – Will Ness Sep 29 '22 at 18:49
  • googling "filter can't be corecursive" brings up e.g. [this](https://stackoverflow.com/questions/18421926/list-filter-using-an-anamorphism#comment27068361_18421926). so I guess your code doesn't qualify as an anamorphism either, because of the same recursive call in calculating the head. of course with finite lists it's still working, but with the infinite ones you can see that your code is nonproductive for the `[[], [], ...]` input, i.e. infinite list of empty lists. so I guess if we allow yours, we can allow mine as well (hinted to in the comments above, I mean) – Will Ness Sep 29 '22 at 22:23
  • 1
    @Will-Ness: Wow thanks for that fix and your v interesting comments - hope to be able to respond next week. My acquaintance with corecursion comes entirely from the referenced Gibbons paper ... I was indeed inspired by your answer to "[reverse with restrictions](https://stackoverflow.com/q/71615049/16981884)", which led me to make up the corecursion template. – mnemenaut Oct 01 '22 at 09:16
  • Thanks! (I've also added more stuff to my answer, thanks to the above discussion). "corecursion template" you mean it's not in the paper?! It, specifically, together with its type signature, was illuminating to me actually, rethinking it from the Scheme/Racket perspective. – Will Ness Oct 01 '22 at 10:59
0

Assuming you are allowed to call append, for simplicity. You have

(define (concatenate . lsts)
 (let rec ([l lsts]
          [acc '()])
   (if (empty? l)
       acc
       (rec (cons (list* l)  ; only ONE
                  acc)       ; argument
            ))))

calling rec with only one argument. I have added a newline there so it becomes more self-evident.

But your definition says it needs two. One way to fix this is

(define (conc . lsts)
 (let rec ([ls lsts]
           [acc '()])
   (if (empty? ls)
       acc
       (rec (cdr ls)               ; first argument
            (append acc (car ls))  ; second argument
            ))))

Now e.g.

(conc (list 1 2) (list 3 4))

; => '(1 2 3 4)

I used append. Calling list* doesn't seem to do anything useful here, to me.


(edit:)

Using append that way was done for simplicity. Repeatedly appending on the right is actually an anti-pattern, because it leads to quadratic code (referring to its time complexity).

Appending on the left with consequent reversing of the final result is the usual remedy applied to that problem, to get the linear behavior back:

(define (conc2 . lsts)
 (let rec ([ls lsts]
           [acc '()])
   (if (empty? ls)
       (reverse acc)
       (rec (cdr ls)
            (append (reverse (car ls))
                    acc)))))

This assumes that append reuses its second argument and only creates new list structure for the copy of its first.

The repeated reverses pattern is a bit grating. Trying to make it yet more linear, we get this simple recursive code:

(define (conc3 . lols)
  (cond
    [(null? lols) empty ]  
    [(null? (car lols)) 
       (apply conc3 (cdr lols)) ] 
    [else
       (cons (caar lols)
             (apply conc3
                    (cons (cdar lols) (cdr lols))))]))

This would be even better if the "tail recursive modulo cons" optimization was applied by a compiler, or if cons were evaluated lazily.

But we can build the result in the top-down manner ourselves, explicitly, set-cdr!-ing the growing list's last cell. This can be seen in this answer.

Will Ness
  • 70,110
  • 9
  • 98
  • 181