2011-10-02

SICP Exercise 2.32: Power Sets

We can represent a set as a list of distinct elements, and we can represent the set of all subsets of the set as a list of lists. For example, if the set is (1 2 3), then the set of all subsets is (() (3) (2) (2 3) (1) (1 3) (1 2) (1 2 3)). Complete the following definition of a procedure that generates the set of subsets of a set and give a clear explanation of why it works:
(define (subsets s)
  (if (null? s)
      (list nil)
      (let ((rest (subsets (cdr s))))
        (append rest (map <??> rest)))))
We have a recursive procedure here:
  • If we have the empty set then the complete set of subsets is the empty set.
  • Otherwise, in order to build the complete set of subsets for the set we:
    1. First build the complete set of subsets for the set with its first item removed.
    2. Then we map this list of subsets in some way.
    3. Finally we join these two sets together (via append) to produce the complete set of subsets.
So what's the mapping step? Well, we can define the complete set of subsets of any non-empty set s as being the union of two sets:
  1. The set of all subsets of s that do not include the head of s.
  2. The set of all subsets of s that do include the head of s.
Now if we have a complete implementation of subsets, then the local variable rest will contain the first of these sets. We can produce the second of these sets by noting that the first and second sets have the same number of elements, and that each element in the second set can be produced by taking an element from the first set and adding the head of s to it. Note that this is a pretty common algorithm for producing power sets, which is what this procedure is meant to be doing.

Okay, so here's how we complete the procedure:
(define (subsets s)
  (if (null? s)
      (list nil)
      (let ((rest (subsets (cdr s))))
        (append rest (map (lambda (x) (cons (car s) x)) rest)))))
And let's give it a spin:
> (subsets (list 1 2))
'(() (2) (1) (1 2))
> (subsets (list 1 2 3))
'(() (3) (2) (2 3) (1) (1 3) (1 2) (1 2 3))
> (subsets (list 1 2 3 4))
'(() (4) (3) (3 4) (2) (2 4) (2 3) (2 3 4) (1) (1 4) (1 3) (1 3 4) (1 2) (1 2 4)
 (1 2 3) (1 2 3 4))
The discussion above also indicates why this is a correct approach. Let's go a step further and give a proof by induction for the algorithm.

First we show that the base case is correct: that the power set of the empty set contains only one set, the empty set:
  (subsets nil)
= (if (null? nil)
      (list nil)
      (let ((rest (subsets (cdr nil))))
        (append rest (map (lambda (x) (cons (car nil) x)) rest)))))
= (if #t
      (list nil)
      (let ((rest (subsets (cdr nil))))
        (append rest (map (lambda (x) (cons (car nil) x)) rest)))))
= (list nil)
= (())
Before we move onto the inductive step, let's make the observation that, given that rest is the set of all subsets of s that do not include the head of s, then (map (lambda (x) (cons (car s) x)) rest) will append the head of s onto each of these subsets and so produce the set of all subsets of s that do include the head of s.

Now onto the inductive step. Given the assumption that for any arbitrary set s, (subsets s) correctly produces the power set of s, we need to prove that (subsets (cons h s)) produces the power set of (cons h s):
  (subsets (cons h s))
= (if (null? (cons h s))
      (list nil)
      (let ((rest (subsets (cdr (cons h s)))))
        (append rest (map (lambda (x) (cons (car (cons h s)) x)) rest)))))
= (if #f
      (list nil)
      (let ((rest (subsets (cdr (cons h s)))))
        (append rest (map (lambda (x) (cons (car (cons h s)) x)) rest)))))
= (let ((rest (subsets (cdr (cons h s)))))
    (append rest (map (lambda (x) (cons (car (cons h s)) x)) rest)))
= (let ((rest (subsets s)))
    (append rest (map (lambda (x) (cons (car (cons h s)) x)) rest)))
= (append (subsets s) (map (lambda (x) (cons h x)) (subsets s)))
Now we know that (subsets s) is the set of all subsets of (cons h s) that do not include h. We also know from our previous observation that (map (lambda (x) (cons h x)) (subsets s)) produces the set of all subsets of (cons h s) that do include h. Therefore appending these two sets together produces the set of all subsets of (cons h s).

SICP Exercise 2.31: Tree Map

Abstract your answer to exercise 2.30 to produce a procedure tree-map with the property that square-tree could be defined as
(define (square-tree tree) (tree-map square tree))
To do this, let's look at the two map-based procedures, scale-tree and square-tree:
(define (scale-tree tree factor)
  (map (lambda (sub-tree)
         (if (pair? sub-tree)
             (scale-tree sub-tree factor)
             (* sub-tree factor)))
       tree))

(define (square-tree tree)
  (map (lambda (sub-tree)
         (if (pair? sub-tree)
             (square-tree sub-tree)
             (square sub-tree)))
       tree))
They're pretty similar. Sure, scale-tree takes an extra parameter, and they apply different operations when a non-pair element is being mapped, but the general pattern is the same. I.e.:
(define (doSomethingToA-tree tree possiblyWithSomeParameters)
  (map (lambda (sub-tree)
         (if (pair? sub-tree)
             (doSomethingToA sub-tree possiblyWithSomeParameters)
             (applyOperationTo sub-tree possiblyWithSomeParameters)))
       tree))
If we encapsulate the operation to apply to non-pair elements and the optional parameters into a procedure then we'd be able to generalize this process as follows:
(define (tree-map proc tree)
  (map (lambda (sub-tree)
         (if (pair? sub-tree)
             (tree-map proc sub-tree)
             (proc sub-tree)))
       tree))
Then we can define both scale-tree and square-tree in terms of tree-map:
(define (scale-tree tree factor)
  (tree-map (lambda (x) (* x factor)) tree))

(define (square-tree tree)
  (tree-map square tree))
And then put them to the test:
> (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7))
              10)
'(10 (20 (30 40) 50) (60 70))
> (scale-tree (list 1
                    2
                    (list 3 (list 4 5) 6)
                    (list 7 (list 8 (list 9)) 10 (list 11))
                    (list 12))
              2)
'(2 4 (6 (8 10) 12) (14 (16 (18)) 20 (22)) (24))
> (square-tree
    (list 1
          (list 2 (list 3 4) 5)
          (list 6 7)))
'(1 (4 (9 16) 25) (36 49))
> (square-tree
    (list 1
          2
          (list 3 (list 4 5) 6)
          (list 7 (list 8 (list 9)) 10 (list 11))
          (list 12)))
'(1 4 (9 (16 25) 36) (49 (64 (81)) 100 (121)) (144))

SICP Exercise 2.30: Square Trees

Define a procedure square-tree analogous to the square-list procedure of exercise 2.21. That is, square-list should behave as follows:
> (square-tree
    (list 1
          (list 2 (list 3 4) 5)
          (list 6 7)))
(1 (4 (9 16) 25) (36 49))
Define square-tree both directly (i.e., without using any higher-order procedures) and also by using map and recursion.

Now square-list, regardless of version, just iterates through a source list, building a new list of identical length containing the values generated by applying the procedure square to each element of the source list. So we want square-tree to iterate through a source tree, building a new tree of identical structure, but containing the values generated by applying the procedure square to each element of the source tree.

In the section "Mapping over lists", the authors give us a procedure, scale-list which provided us with the basis for our square-list procedure. Similarly in the section "Mapping over trees", the authors give us the analogous procedure, scale-tree, which we can use as the basis of our square-tree procedure. Here's the version that scales a tree directly:
(define (scale-tree tree factor)
  (cond ((null? tree) nil)
        ((not (pair? tree)) (* tree factor))
        (else (cons (scale-tree (car tree) factor)
                    (scale-tree (cdr tree) factor)))))
To produce square-tree we simply need to rename the procedure and its recursive calls, remove factor and replace the scaling of the non-pair elements of the tree with squaring the of the non-pair elements:
(define (square-tree tree)
  (cond ((null? tree) nil)
        ((not (pair? tree)) (square tree))
        (else (cons (square-tree (car tree))
                    (square-tree (cdr tree))))))
Let's check it works:
> (square-tree
    (list 1
          (list 2 (list 3 4) 5)
          (list 6 7)))
'(1 (4 (9 16) 25) (36 49))
> (square-tree
    (list 1
          2
          (list 3 (list 4 5) 6)
          (list 7 (list 8 (list 9)) 10 (list 11))
          (list 12)))
'(1 4 (9 (16 25) 36) (49 (64 (81)) 100 (121)) (144))
We can do the same for the map-based version. Here's the map-based version of scale-tree:
(define (scale-tree tree factor)
  (map (lambda (sub-tree)
         (if (pair? sub-tree)
             (scale-tree sub-tree factor)
             (* sub-tree factor)))
       tree))
So again, we simply rename the procedure and its recursive calls, remove factor and replace the scaling of the non-pair elements of the tree with squaring the of the non-pair elements:
(define (square-tree tree)
  (map (lambda (sub-tree)
         (if (pair? sub-tree)
             (square-tree sub-tree)
             (square sub-tree)))
       tree))
And let's check this one works too:
> (square-tree
    (list 1
          (list 2 (list 3 4) 5)
          (list 6 7)))
'(1 (4 (9 16) 25) (36 49))
> (square-tree
    (list 1
          2
          (list 3 (list 4 5) 6)
          (list 7 (list 8 (list 9)) 10 (list 11))
          (list 12)))
'(1 4 (9 (16 25) 36) (49 (64 (81)) 100 (121)) (144))