a Racket problem about the accumulator-style recursion - racket

Using accumulator-style recursion, write a function
one-long-string that consumes a ListOfString and produces the
concatenation of strings in the list in the order they appear in the list.
That is, (one-long-string (list "Alice" "Bob" "Eve")
returns "AliceBobEve"

Notes (added later):
Original question (quoted below) did not specify a particular Racket language, or provide an
attempted solution, or indicate what sort of issue prompted the question.
This answer will use Racket's Beginning Student
language (BSL), and develop (in exhaustive detail) a simple "natural recursion" solution, followed by
conversion to the requested "accumulator-style". BSL is used to focus attention on how using the design method
enables solution development without requiring "leaps of intuition", or familiarity with advanced language.
Readers may wonder how long it actually takes, meticulously following the design recipe with it's
signatures, check-expect tests, template copying and editing, etc, to produce the finished function.
The answer, for me, is about 10 minutes; for comparison, just "writing a function" (with signature and purpose)
and repl checking examples, takes about half that.
Using accumulator-style recursion, write a function one-long-string that consumes a ListOfString and produces the concatenation of strings in the list in the order they appear in the list. That is, (one-long-string (list "Alice" "Bob" "Eve") returns "AliceBobEve"
Get started
Using the design recipe for writing functions, one starts with a function signature and purpose; these can be copied from the question above and pasted into a Racket function definition stub in the DrRacket definitions area:
(define (one-long-string los) ;; ListOfString -> String ; *stub* ;; *signature*
;; produce the concatenation of los strings in order ; *purpose statement*
"") ; *stub body* (valid result)
The next step is to add a minimal example in the form of a check-expect:
(check-expect (one-long-string empty) "") ; *minimal example*
And then (with DrRacket's Language set to Beginning Student), Run:
The test passed!
>
Follow the recipe
Continue following the design recipe by selecting a template based on the argument type ListOfString -
copy it into the definitions area:
(define (fn lox) ;; ListOfX -> Y ; *template*
;; produce a Y from lox using natural recursion ;
(cond ;
[(empty? lox) ... ] ; ... = "base case value" ;; Y
[else (.... ; .... = "inventory fn(s)" ;; X Y -> Y
(first lox) (fn (rest lox))) ])) ;
(There is a template for "accumulator-style recursion", but this answer will start with the simplest
ListOf template. The solution will be modified to accumulator-style later.)
Edit the template, replacing the generic names with the appropriate ones for this problem, to get:
(define (one-long-string los) ;; ListOfString -> String
;; produce the concatenation of los strings in order
(cond
[(empty? los) "" ] ;; String
[else (.... ;; String String -> String
(first los) (one-long-string (rest los))) ]))
The placeholder ... has been replaced by "" by reference to the first example above.
Note that the signature of .... has been deduced from the signatures of its arguments and result.
Comment out the stub (prefix it with #;), and Run again to confirm that The test passed!.
(Always run after any change to confirm that everything still works, and fix any typos immediately.)
Add another example:
(check-expect (one-long-string (list "Alice")) "Alice")
and Run: the error message confirms that the placeholder .... needs to be replaced.
(This test could be made to pass by adding (define (arg1 x y) x) and using arg1 for ....,
but one can see that something better is likely to be needed.)
The replacement for .... will have signature String String -> String; we don't have such a
function, but checking Strings in Beginning Student
for suitable functions yields the following possibilities:
; format ;; String Any -> String ; *inventory* (all functions with
; string-append ;; String String -> String ; signature String String -> String)
Consider another example:
(check-expect (one-long-string (list "Alice" "Bob")) "AliceBob")
given "Alice" and "Bob", one can produce "AliceBob" with string-append, ie the example can be written:
(check-expect (one-long-string (list "Alice" "Bob")) (string-append "Alice" "Bob"))
This suggests that .... should be string-append; one can now add a final example:
(check-expect (one-long-string (list "Alice" "Bob" "Eve")) "AliceBobEve")
Run again, and the (non-accumulator) function is complete:
#;
(define (one-long-string los) ;; ListOfString -> String ; *stub* ;; *signature*
;; produce the concatenation of los strings in order ; *purpose statement*
"") ; *stub body* (valid result)
(check-expect (one-long-string empty) "") ; *minimal example*
(define (one-long-string los) ;; ListOfString -> String
;; produce the concatenation of los strings in order
(cond
[(empty? los) "" ]
[else (string-append
(first los) (one-long-string (rest los))) ]))
(check-expect (one-long-string (list "Alice")) "Alice")
(check-expect (one-long-string (list "Alice" "Bob")) (string-append "Alice" "Bob"))
(check-expect (one-long-string (list "Alice" "Bob" "Eve")) "AliceBobEve")
All 4 tests passed!
>
Accumulator style
As mentioned earlier, there is a template for "accumulator-style recursion", which uses
features of Advanced Student
language. Why would one use a version of the function incorporating an accumulator?
A common reason is to put the recursive call in tail position.
To explore this style, first try to edit the template to be tail-recursive:
(define (fn lox) ;; ListOfX -> Y ; *template*
;; produce a Y from lox (tail recursive) ;
(cond ;
[(empty? lox) ... ] ; result ;; Y
[else (fn (rest lox)) ; tail recursion
.... (first lox) ; (where do these go?)
])) ;
This can't be right (the placeholder .... and (first lox) don't fit) but continue by
replacing the generic names:
(define (one-long-string los) ;; ListOfString -> String
;; produce the concatenation of los strings in order
(cond
[(empty? los) ... ] ;; String
[else (one-long-string (rest los))
.... (first los) ; ?
]))
The recursive one-long-string call in the partially filled-in template is now in tail position,
with argument (rest los) so that it can deal with all the elements of los,
but to make progress in producing the result the function must do something with (first los).
Where can this be fitted in?
One way to resolve this question is to introduce an argument: with the additional argument,
one-long-string (now renamed to one-long-string-with-arg) has a place in the recursive
call to hold (first los):
(define (one-long-string-with-arg los arg) ;; ListOfString X -> String
;; produce the concatenation of los strings in order, using extra arg
(cond
[(empty? los) (... arg) ] ;; String
[else (one-long-string-with-arg (rest los) (.... arg (first los)))
]))
(define (one-long-string los) ;; ListOfString -> String
;; produce the concatenation of los strings in order
(one-long-string-with-arg los .....))
one-long-string now just calls one-long-string-with-arg, supplying ..... for arg.
Recalling the first two examples:
(check-expect (one-long-string empty) "")
(check-expect (one-long-string (list "Alice")) "Alice")
one can see that a simple replacement for ..... is "", and for (... arg)
just arg. As before, the other examples suggest string-append for .....
The rôle of arg in one-long-string-with-arg is to accumulate a "result so far" value,
so it is renamed rsf, and the complete accumulator style solution is:
#;
(define (one-long-string los) ;; ListOfString -> String ; *stub* ;; *signature*
;; produce the concatenation of los strings in order ; *purpose statement*
"") ; *stub body* (valid result)
(check-expect (one-long-string empty) "") ; *minimal example*
(define (one-long-string-acc los rsf) ;; ListOfString String -> String
;; produce the concatenation of los strings in order using rsf accumulator
(cond
[(empty? los) rsf ]
[else (one-long-string-acc (rest los)
(string-append rsf (first los))) ]))
(define (one-long-string los) ;; ListOfString -> String
;; produce the concatenation of los strings in order, using accumulator
(one-long-string-acc los ""))
(check-expect (one-long-string (list "Alice")) "Alice")
(check-expect (one-long-string (list "Alice" "Bob")) (string-append "Alice" "Bob"))
(check-expect (one-long-string (list "Alice" "Bob" "Eve")) "AliceBobEve")
All 4 tests passed!
>
(to be continued)

Related

How to concatenate all the elements of the argument lists into a single list

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?
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:
When is the output empty?
If the output isn’t empty, what is its head?
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
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.

Racket - Remove a key from association list

I'm trying to write a function which consumes a number, key, and an association list, al, and produces the association list resulting from removing key from al.
For example:
(check-expect (remove-al 5 (list (list 4 "A") (list 5 "B") (list 1 "C")))
(list (list 4 "A") (list 1 "C")))
I'm not permitted to use certain functions, like map, filter or remove, but have managed to write this so far. Below is my code:
define (remove-al key al)
(cond
[(empty? al) empty]
[(= key (first (first al))) empty]
[else (cons (first al)
(remove-al key (rest al)))]))
Which part of my code is wrong?
The concept behind this algorithm is to think of it in terms of an extended append implementation. If you are to look up the implementation of append in Racket, it is a recursive algorithm where the base case checks if the rhs list is empty, but if not it cons the first value of the lhs list with the result of the recursive call with the rest of lhs list and rhs. Here is a link to the implementation. The algorithm is simply the append implementation with an extra check to make sure to ignore the cons recursive call if the first element of the list matches the key.
SPOILERS
---->
(define (remove-al key lhs rhs)
(cond [(empty? lhs) rhs]
[else (cond [(not (=(first (first lhs)) key))
(cons (first lhs) (remove-al key (rest lhs) rhs))]
[else (remove-al key (rest lhs) rhs)])]))
(check-expect (remove-al 5 (list (list 4 "A") (list 5 "B") (list 1 "C")) '() )
(list (list 4 "A") (list 1 "C")))

Racket function recursion

hello I'm a student currently trying to make a function in Dr racket that's purpose is
when a letter is selected it turns it into underscore
string(word) string(letter) -> string(answer/underscore)
I've only been able to make this happen with one letter as _ which makes the second check true and I can't figure out how to do multiple letters
(check-expect(underscore "william""li")"_illi__"))
(check-expect(underscore "william" "l")"__ll___))
My code:
(define (anti-omit word letter)
(cond[(string=? word letter)letter]
[(= 1 (string-length word))"_"]
[else
(string-append
(anti-omit (substring word 0 1)letter)
(anti-omit (substring word 1)letter))]))
Here's a stub with a purpose statement, signature, and your tests:
;; String String -> String
;; keeps all letters that occur in l, replaces with "_" otherwise
(check-expect (anti-omit "william" "li") "_illi__")
(check-expect (anti-omit "william" "l") "__ll___")
(define (anti-omit w l)
"")
Since you're using a student language, and as you suggest in the title of the question that you want to recur on the data... you'll need a recursive data-definition:
;; String is one of:
;; - ""
;; - (string-append 1String String)
Complete the definition of this function:
;; String -> String
;; is str equal to "" ?
(define (string-empty? str)
...)
a more restrictive data-definition for other two functions (which correspond to rest and first of a list):
;; NEString is one of:
;; - (string-append 1String "")
;; - (string-append 1String NEString)
Some other functions for you to complete:
;; NEString -> String
;; remove the first 1String from str
(define (string-rest str)
...)
;; NEString -> String
;; the first 1String form str
(define (string-first str)
...)
Now you can make functions like this on Strings:
;; [1String -> 1String] String -> String
;; Applies the function f on each 1String in s
(define (map-str f s)
(cond [(string-empty? s) ""]
[else (string-append (f (string-first s)) (map-str f (string-rest s)))]))
Similarly, complete this definition:
;; 1String String -> Boolean
;; is does 1String occur in String?
(define (member-str e l)
...)
Our "wish list" is complete, and we can compose our helpers to finish the final function:
(define (anti-omit w l)
(map-str (λ (x) (if (member-str x l) x "_")) w))
A similar version that uses explode and implode and doesn't need any of the functions we defined (use this as a reference implementation):
(define (anti-omit-ref w l)
(implode (map (λ (x) (if (member? x (explode l)) x "_")) (explode w))))

Can I divide list into multiple parts in BSL?

I have the list of values and want to take first x values from it and create (list (listof first x values) (listof next x values) and so on until this list gets empty...).
For example, given this list: (list "a" "b" "c" "d" "e" "f" "g" "h" "t")
return this: (list (list a" "b" "c") (list "d" "e" "f") (list "g" "h" "t"))
Thanks in advance :)
Remember what a datatype for a list is. Your class is probably doing something like:
;; A IntegerList is one of:
;; - '()
;; - (cons Integer IntegerList)
Given that, your template should reflect this structure. I will solve the base case (where we want to turn a list of integers into lists of one integers.
First I will define a 1List datatype as:
;; a 1List is:
;; - (cons Integer '())
Next, the purpose statement and signature for the function will be:
;; Takes a list of integers and returns a list of 1Lists of the same integers
;; IntegerList -> 1List
(define (make-1list lst)
...)
Okay cool. Now we need test cases:
(check-expect (make-1list (list 1 2 3)) (list (list 1) (list 2) (list 3)))
(check-expect (make-1list (list)) (list))
(check-expect (make-1list (list 42)) (list (list 42)))
Finally, I can make my template:
(define (make-1list lst)
(cond [(null? lst) ...]
[else ... (first lst) ... (rest lst) ...]))
(Note that it sometimes makes sense to make some of the template first, to help you guide what tests you need.)
Finally, we can fill in our code:
(define (make-1list lst)
(cond [(null? lst) '()]
[else (cons (list (first lst)) (make-1list (rest lst)))]))
And finally, are examples are also tests so we just need to run them to make sure everything works.
Now, since you want to make 3Lists instead of 1Lists, do you see how you can follow this recipe to solve the problem?
Write down your data definition.
Make your purpose statement and signature.
Make your examples.
Make your template.
Write the actual function.
Turn your existing examples into tests.
Following this pattern should help you break the problem down into smaller steps. Good luck.
Better way to accomplish this task is to use accumulators & recursion.

How to transform this function into macro?

I have difficulties understanding the new macro system of Scheme. Somewhere along the path I began to write my "macro" as a function first, and then later apply it as a macro.
So my mission is to turn the following structure:
;; highlight-rules: rule id, color and the regexp matches
(define highlight-rules
`((important ,(with-esc "[1;33m") ("foo"
"bob"))
(unimportant ,(with-esc "[1;30m") ("case of unimport"))
(urgent ,(with-esc "[1;31m") ("urgents"))))
Into this kind of cond series with match strings compiled to regexpes:
;; just an example. `line` is an argument bound by the function application
(cond
((string-match (regexp ".*sudo:session.*") line)
(with-color *important* line))
(else line))
I have written a function that seems to do the trick:
;; (cdar highlight-rules) -> (colorstring list-of-rules)
(define (parse-highlight-rules rules)
;; aux function to do one 'class' of patterns
(define (class-of-rules colorstr rulelist)
(map (lambda (rule)
`((string-match ,(regexp rule)) (with-color ,colorstr line)))
rulelist))
(define (do-loop accumulator rules)
(let* ((highlight-group (cdar rules))
(colorstr (car highlight-group))
(grouprules (cadr highlight-group))
(acc* (append (class-of-rules colorstr grouprules) accumulator))
(rest (cdr rules)))
(if (null? rest)
acc*
(do-loop acc* rest))))
; wrap the list in cond.
`(apply cond ,(do-loop '() rules)))
With given highlight-rules the function returns correct-looking list (well apart from applying the apply -- in clojure one would use splicing):
CSI> (parse-highlight-rules highlight-rules)
(apply cond (((string-match #<regexp>) (with-color "\x1b[1;31m" line))
((string-match #<regexp>) (with-color "\x1b[1;30m" line))
((string-match #<regexp>) (with-color #0="\x1b[1;33m" line))
((string-match #<regexp>) (with-color #0# line))))
But how to proceed with this? I've been stuck with this for a while. Chicken Scheme is my dialect.
The easiest way of transforming your function into a macro is by using Chicken's explicit-renaming macro facility, which works similarly to Clojure's defmacro (except that an explicit-renaming macro takes some additional arguments that can be used to preserve hygiene).
Splicing works basically the same way as it does in Clojure. The syntax is ,#. Therefore, the following should work:
(define-for-syntax (parse-highlight-rules rules)
;; ... insert missing code here ...
`(cond ,#(do-loop '() rules)))
(define-syntax highlight
(er-macro-transformer
(lambda (form rename compare)
(parse-highlight-rules (cdr form)))))