JI VB
JI VB

Reputation: 45

Replacing a certain number of repeated elements in a list in Racket

I am trying to define the rule 3 of "MIU System" of "Gödel, Escher, Bach" (Douglas Hofstadter), which says:

Replace any III with a U

Example:

MIIIIU → MUIU and MIIIIU → MIUU enter image description here

Main code:


    (define (rule-tree lst)
    (if (<= 3 (counter lst #\I))
        (append (delete #\I lst) (list #\U))
        (append lst empty)))
    
    (define (delete x lst)
      (cond [(empty? lst) lst]
            [(eq? (first lst) x) (delete x (rest lst))]
            [else (append (list (first lst)) (delete x (rest lst)))]))
    
    (define (counter lst target)
      (if (empty? lst)
          0
          (+ (counter (rest lst) target) 
             (let ((x (first lst)))
               (if (list? x)
                   (counter x target) 
                   (if (eqv? x target) 1 0))))))

With this expression there is no problem:

>(rule-tree '(#\M #\I #\I #\I))
'(#\M #\U)

But I don't know how to determine the position that the "U" should take when finding the 3 "I". Any suggestion will be very helpful :)

Upvotes: 0

Views: 113

Answers (1)

Renzo
Renzo

Reputation: 27414

Here is an alternative recursive version, where repl2 encodes the information “we have just encountered one #\I”, while repl3 encodes the information “we have just encountered two #\I”:

(define (repl lst)
  (cond ((empty? lst) lst)
        ((eqv? (first lst) #\I) (repl2 (rest lst)))
        (else (cons (first lst) (repl (rest lst))))))

(define (repl2 lst)
  (cond ((empty? lst) (list #\I))
        ((eqv? (first lst) #\I) (repl3 (rest lst)))
        (else (cons #\I (cons (first lst) (repl (rest lst)))))))

(define (repl3 lst)
  (cond ((empty? lst) (list #\I #\I))
        ((eqv? (first lst) #\I) (cons #\U (repl (rest lst))))
        (else (cons #\I (cons #\I (cons (first lst) (repl (rest lst))))))))

Of course this solution is some kind of hack and cannot scale to a greater number of repetitions. But looking at the structure of this solution and simply generalizing the three functions we can produce a general solution:

(define (repl lst n from to)
  (define (helper lst k)
    (cond ((empty? lst) (repeat from (- n k)))
          ((eqv? (first lst) from)
           (if (= k 1)
               (cons to (helper (rest lst) n))
               (helper (rest lst) (- k 1))))
          (else (append (repeat from (- n k))
                        (cons (first lst) (helper (rest lst) n))))))    

(define (repeat x n)
  (if (= n 0)
      '()
      (cons x (repeat x (- n 1)))))

We define a function repl that takes a list, the number of copies to replace (n), the element to replace (from) and the element that must be substituted (to). Then we define a helper function to do all the work, and that has as parameters the list to be processed and the number of copies that must be still found (k).

Each time the function encounters a copy it checks if we have finished with the number of copies and substitutes the element, restarting its work, otherwise it decrements the number of copies to find and continues.

If it founds an element different from from it recreates the list with the elements “consumed” until this point (maybe 0) with repeat and then continues its work.

Note that the previous version of the helper function had an error in the final case, when lst is null. Instead of returning simply the empty list, we must return the possibly skipped from elements.

Upvotes: 1

Related Questions