Skip to main content

Assignment 6: Exploring recursion

Due
by 10:30pm on Tuesday, 13 March 2018.
Summary
For this assignment, you will apply your knowledge of recursion to solve a variety of problems that require different kinds of general repetition.
Collaboration
You must work with your assigned partner(s) on this assignment. You may discuss this assignment with anyone, provided you credit such discussions when you submit the assignment.
Submitting
Email your answers to csc151-01-grader@grinnell.edu. The subject of your email should be [CSC151 01] Assignment 6 and should contain your answers to all parts of the assignment. Scheme code should be in the body of the message, not in an attachment.
Warning
So that this assignment is a learning experience for everyone, we may spend class time publicly critiquing your work.

Problem 1: Tracing two types of tally

Topics: code reading, recursion

One way to really understand what is happening in a recursive procedure is to trace the steps as Scheme evaluates the portions of the procedure in its traditional “inside-out” form. For example, given the non-helper version of sum, we might write the following trace.

   (sum (list 38 12 83))
     ; Not null
=> (+ (car (list 38 12 83) (sum (cdr (list 38 12 83)))
     ; Simplify car/cdr
=> (+ 38 (sum (list 12 83)))
     ; Not null
=> (+ 38 (+ (car (list 12 83)) (sum (cdr (list 12 83)))))
     ; Simplify car/cdr
=> (+ 38 (+ 12 (sum (list 83))))
     ; Not null
=> (+ 38 (+ 12 (+ (car (list 83)) (sum (cdr (list 83)))))))
     ; Simplify car/cdr
=> (+ 38 (+ 12 (+ 83 (sum null))))
     ; Null case
=> (+ 38 (+ 12 (+ 83 0)))
     ; Evaluate innermost expression
=> (+ 38 (+ 12 83))
     ; Evaluate innermost expression
=> (+ 38 95)
     ; Evaluate innermost expression
=> 133

Consider the two following versions of tally-odd.

;;; Procedure:
;;;   tally-odd
;;; Parameters:
;;;   nums, a list of integers
;;; Purpose:
;;;   Count how many values in nums are odd
;;; Produces:
;;;   number-odd, a count of how many values are odd.
(define tally-odd-a
  (lambda (nums)
    (cond
      [(null? nums)
       0]
      [(not (integer? (car nums)))
       (error "tally-odd expects a list of integers.  Your list contains "
              (car nums))]
      [(odd? (car nums))
       (+ 1 (tally-odd-a (cdr nums)))]
      [else
       (tally-odd-a (cdr nums))])))
   
(define tally-odd-b
  (lambda (nums)
    (tally-odd-b-kernel nums 0)))

(define tally-odd-b-kernel
  (lambda (remaining tally-so-far)
    (cond
      [(null? remaining)
       tally-so-far]
      [(not (integer? (car remaining)))
       (error "tally-odd expects a list of integers.  Your list contains "
              (car remaining))]
      [(odd? (car remaining))
       (tally-odd-b-kernel (cdr remaining) (+ 1 tally-so-far))]
      [else
       (tally-odd-b-kernel (cdr remaining) tally-so-far)])))

Write traces of the evaluation of tally-odd-a and tally-odd-b (and tally-odd-b-kernel, since that’s where the real work happens) on the input '(5 2 7 3 6).

   (tally-odd-a '(5 2 7 3 6))
       ; Not empty, car is odd
=> (+ 1 (tally-odd-a (cdr '(5 2 7 3 6))))
...


   (tally-odd-b '(5 2 7 3 6))
=> (kernel '(5 2 7 3 6) 0)
       ; Not empty, car is odd
=> (kernel (cdr '(5 2 7 3 6)) (+ 1 0))
...

Problem 2: List containment

Topics: recursion, conditionals

Document and write a recursive procedure, (contains? lst val), that returns true (#t) if val appears in lst and false otherwise.

> (contains? (list 'a 'b 'c) 'a)
#t
> (contains? (list 'a 'b 'c) 'b)
#t
> (contains? (list 'a 'b 'c) 'c)
#t
> (contains? (list 'a 'b 'c) 'd)
#f

Problem 3: Finding the index of an element

Topics: list recursion

As you know, the (index-of val lst) procedure finds the index of the first instances of val in lst. If val is not in lst, it returns the value -1.

Suppose index-of did not exist. We could write it ourselves.

Document and write a recursive procedure, (my-index-of val lst), that provides the first index of val within lst. If the value does not appear, my-index-of should return #f.

> (my-index-of 'skip (list 'hop 'skip 'and 'jump))
1
> (index-of 5 (list 5 4 3 2 1 2 3 4 5))
0
> (my-index-of "eraser" (list "pencils" "paper" "index cards" "markers" "ball-point pens"))
#f

Note: You may not use list-ref (or index-of) in implementing my-index-of.

Problem 4: Finding the indices of an element

Topics: list recursion

As you know, the (index-of val lst) procedure finds the index of the first instances of val in lst. If val is not in lst, it returns the value -1.

What if we wanted all of the indices, not just the first one? We’d probably have to write our own procedure.

Document and write a recursive procedure, (indices-of val lst), that creates a list of all the position in lst where val appears. If the value does not appear, indices-of should return the empty list.

> (indices-of 'skip (list 'hop 'skip 'and 'jump))
'(1)
> (indices-of 'skip (list 'hop 'skip 'jump 'and 'skip 'again))
'(1 4)
> (indices-of 5 (list 5 4 3 2 1 2 3 4 5))
'(0 8)
> (indices-of "eraser" (list "pencils" "paper" "index cards" "markers" "ball-point pens"))
'()

Note: You may not use list-ref (or index-of) in implementing indices-of.

Problem 5: Riffling lists

Topics: list recursion

Document and write a procedure, (riffle2 first second), that produces a new list containing alternating elements from the lists first and second. If one list runs out before the other, then the remaining elements should appear at the end of the new list.

> (riffle2 (list 'a 'b 'c) (list 'x 'y 'z))
'(a x b y c z)
> (riffle2 (list 'a 'b 'c) (iota 10))
'(a 0 b 1 c 2 3 4 5 6 7 8 9)
> (riffle2 (iota 10) (list 'a 'b 'c)
'(0 a 1 b 2 c 3 4 5 6 7 8 9)
> (riffle2 null (list 'a 'b 'c))
'(a b c)

Problem 6: Riffling lists, revisited

Topics: list recursion

Document and write a procedure, (riffle3 first second third), that produces a new list containing alternating elements from the lists first, second, and third. If one list runs out before the others, continue riffling the remaining two lists. (You may choose to use riffle2 when you reach that point.)

> (riffle3 (list 'a 'b 'c) (list 'p 'q 'r) (list 'x 'y 'z))
'(a p x b q y c r z)
> (riffle3 (list 'a 'b 'c 'd) (list 'e 'f) (list 'g 'h 'i))
'(a e g b f h c i d)
> (riffle3 (list 'a 'b 'c) null (list 'd 'e 'f))
'(a d b e c f)
> (riffle3 (list 'c 'b 'a) (list 'd 'd 'd) (list 'e 'f 'g))
'(c d e b d f a d g)

Problem 7: Generating permutations

Topics: recursion

Given a list of items, it can be useful to know all possible ways the list can be uniquely ordered. A specific ordering of items is called a permutation and the notion appears frequently in mathematics.

Write, but do not document, a procedure called (permutations lst) that takes a list as a parameter returns a list of all possible permutations of the elements of lst. Here are a few example executions of the procedure.

> (permutations (list 1 2))
'((1 2) (2 1))
> (permutations (list 0 1 2))
'((0 1 2) (1 0 2) (1 2 0) (0 2 1) (2 0 1) (2 1 0))
> (permutations (list 'a 'b 'c))
'((a b c) (a c b) (b a c) (b c a) (c a b) (c b a))
> (permutations (list "a" 'b 7))
'(("a" b 7) ("a" 7 b) (b "a" 7) (b 7 "a") (7 "a" b) (7 b "a"))
> (permutations (list "hello"))
'(("hello"))
> (permutations null)
'(())

You will find the following procedure helpful. (You should understand what the procedure does. You need not understand how it achieves its goal.)

;;; Procedure:
;;;   insert-everywhere
;;; Parameters:
;;;   val, a Scheme value
;;;   lst, a list
;;; Purpose:
;;;   Create a new list consisting of the result of inserting `val`
;;;   at every possible location in `lst`.
;;; Produces:
;;;   inserted, a list of lists
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   * (length inserted) = (+ 1 (length lst))
;;;   * (list-ref inserted i) = is the result of inserting val before
;;;     the value at position i in lst.
;;; Practica:
;;;    > (insert-everywhere 'a (list 'b))
;;;    '((a b) (b a))
;;;    > (insert-everywhere 'a (list 1 2 3))
;;;    '((a 1 2 3) (1 a 2 3) (1 2 a 3) (1 2 3 a))
;;;    > (insert-everywhere 'a null)
;;;    '((a))
(define insert-everywhere
  (lambda (val lst)
    (if (null? lst)
        (list (list val))
        (cons (cons val lst)
              (map (section cons (car lst) <>)
                   (insert-everywhere val (cdr lst)))))))

How will this procedure help? To compute the permutations of a non-empty list, you make all of the permutations of the cdr of the list, insert the car of the list at each position in each permutation, and then join them all together.

> (insert-everywhere 1 '(2))
'((1 2) (2 1))
> (insert-everywhere 0 '(1 2))
'((0 1 2) (1 0 2) (1 2 0))
> (insert-everywhere 0 '(2 1))
'((0 2 1) (2 0 1) (2 1 0))
> (append (insert-everywhere 0 '(1 2)) (insert-everywhere 0 '(2 1)))
'((0 1 2) (1 0 2) (1 2 0) (0 2 1) (2 0 1) (2 1 0))

Problem 8: Joining sorted lists

Topics: list recursion, sorting

Write but do not document a recursive procedure, (merge-sorted-lists lst1 lst2), that takes as input two lists of real numbers that are ordered from smallest to largest and returns the combination of those two lists, still in sorted order.

You may not use sort in your solution.

> (merge-sorted-lists '(1 5 6 7 22) '(0 2 3 4 7 18 19))
'(0 1 2 3 4 5 6 7 7 18 19 22)
> (merge-sorted-lists '(1 3 5 7) '(2 3 4 5 6))
'(1 2 3 3 4 5 5 6 7)

Evaluation

We will primarily evaluate your work on correctness (does your code compute what it’s supposed to and are your procedure descriptions accurate); clarity (is it easy to tell what your code does and how it achieves its results; is your writing clear and free of jargon); and concision (have you kept your work short and clean, rather than long and rambly).