Andy's Cafe

SICP JS Chapter 2 Exercise 2.2 and 2.3

Last updated :

Here’s my solution to exercise 2.2 and 2.3.

I wrote them in this online editor, source academy, because they provide the functions that the book considers primatives.

Exercise 2.2

/**
 * @param {Point} start
 * @param {Point} end
 */
function make_segment (start, end) {
    return pair(start, end);
}

/**
 * @returns {Point}
 */
function start_segment (s) {
    return head(s);
}

/**
 * @returns {Point}
 */
function end_segment (s) {
    return tail(s);
}

/**
 * @param {int} x
 * @param {int} y
 */
function make_point (x, y) {
    return pair(x, y);
}

/**
 * @param {Point} s
 * @returns {int}
 */
function x_point (s) {
    return head(s);
}

/**
 * @param {Point} s
 * @returns {int}
 */
function y_point (s) {
    return tail(s);
}

/**
 * @param {Point} s
 * @returns {int}
 */
function midpoint_segment (s) {
    const start = start_segment(s);
    const end = end_segment(s);
    const x1 = x_point(start);
    const y1 = y_point(start);
    
    const x2 = x_point(end);
    const y2 = y_point(end);
    
    return make_point( (x2-x1)/2 , 
                       (y2-y1)/2 );
}

function print_point(p) {
    return display("(" + stringify(x_point(p)) + ", "
                       + stringify(y_point(p)) + ")");
}

const origin = make_point(0, 0);
const unit_x = make_point(1, 0);
const unit_y = make_point(0, 1);

const two_x = make_point(2, 0);
const two_y = make_point(0, 2);

print_point( midpoint_segment( make_segment( origin, two_x ) ) );
// "(1, 0)"
print_point( midpoint_segment( make_segment( origin, two_y ) ) );
// "(0, 1)"

Adding to this code, here’s what I wrote for Exercise 2.3

/**
 * @param {Point} a
 * @param {Point} b 
 * @param {Point} c
 * @param {Point} d
 */
function make_rectangle (a, b, c, d) {
    return pair(pair(make_segment(a, b),
                     make_segment(b, c)),
                pair(make_segment(c, d),
                     make_segment(d, a))
    );
}

/**
 * @param {Rectangle} r
 * @returns {Segment}
 */
function left_rectangle (r) {
    return head(head(r));
}

/**
 * @param {Rectangle} r
 * @returns {Segment}
 */
function top_rectangle (r) {
    return head(tail(r));
}

/**
 * @param {Rectangle} r
 * @returns {Segment}
 */
function right_rectangle (r) {
    return tail(head(r));
}

/**
 * @param {Rectangle} r
 * @returns {Segment}
 */
function bottom_rectangle (r) {
    return tail(tail(r));
}

/**
 * @param {Segment} s
 * @returns {int}
 */
function length_segment (s) {
    const start = start_segment(s);
    const end = end_segment(s);
    
    const x1 = x_point(start);
    const y1 = y_point(start);

    const x2 = x_point(end);
    const y2 = y_point(end);
    
    const delta_x = math_pow(x2-x1, 2);
    const delta_y = math_pow(y2-y1, 2);
    
    return math_sqrt(delta_x + delta_y);
}

/**
 * @param {Rectangle} r
 * @returns {int}
 */
function perimeter_rectangle (r) {
    const left_length = length_segment(left_rectangle(r));
    const top_length = length_segment(top_rectangle(r));
    const right_length = length_segment(right_rectangle(r));
    const bottom_length = length_segment(bottom_rectangle(r));
    
    return left_length
         + top_length
         + right_length
         + bottom_length;
}

/**
 * @param {Rectangle} r
 * @returns {int}
 */
function area_rectangle (r) {
    return length_segment( left_rectangle(r)   )
         * length_segment( bottom_rectangle(r) );
}


const two_rect = make_rectangle(make_point(0,0), 
                                make_point(0,2),
                                make_point(2,2),
                                make_point(2,0));
display( two_rect );
display( perimeter_rectangle(two_rect), "perimeter:");
display( area_rectangle(two_rect), "area:" );

To finish out the second half of exercise 2.3, I wrote another implementation for the rectangle functions

/**
 * @param {Point} a
 * @param {Point} b
 * @param {Point} c
 */
function make_angle (a, b, c) {
    return pair(a,
                pair(b,
                     pair(c,
                          null)));
}

/**
 * @param {Angle} abc
 * @returns {Point}
 */
function a_angle(abc) {
    return head(abc);
}

/**
 * @param {Angle} abc
 * @returns {Point}
 */
function b_angle(abc) {
    return head(tail(abc));
}

/**
 * @param {Angle} abc
 * @returns {Point}
 */
function c_angle(abc) {
    return head(tail(tail(abc)));
}

/**
 * @param {Angle} abc
 * @returns {Segment}
 */
function left_angle(abc) {
    return make_segment(a_angle(abc),
                        b_angle(abc));
}

/**
 * @param {Angle} abc
 * @returns {Segment}
 */
function right_angle(abc) {
    return make_segment(b_angle(abc),
                        c_angle(abc));
}

/**
 * @param {Point} p
 * @param {Point} q 
 */
function make_rectangle (p, q) {
    const x1 = x_point(p);
    const y1 = y_point(p);
    const x2 = x_point(q);
    const y2 = y_point(q);
    
    const corner_2 = make_point(x1, y2);
    const corner_4 = make_point(x2, y1);
    
    return pair(make_angle(p, corner_2, q),
                make_angle(q, corner_4, p));
}

const two_rect_again = make_rectangle_2(make_point(0,0),
                                        make_point(2,2));
display( two_rect_again );
// [[[0, 0], [[0, 2], [[2, 2], null]]], [[2, 2], [[2, 0], [[0, 0], null]]]]
display( perimeter_rectangle(two_rect_again), "perimeter 2:");
// perimeter 2: 8
display( area_rectangle(two_rect_again), "area 2:" );
// area 2: 4

Good luck if you try to run this. The playground was picky that I had already defined some functions and couldn’t shadow the name.

Thoughts

It’s intersting building in these layers of functions so that higher layers are independent of the implementation of the lower layers.

My first approach (a pair of pairs of segments) is pretty naive and coupled too tightly to the idea of a rectangle.

I was pretty proud of the second approach (a pair of angles) because it felt easy enough to extend the implementation to other polygons if needed. Looking back on the code now after stepping away from it, I would tweak the definition of a rectangle to be more cons-cell like instead of just being a single pair. This would let you write more general functions that given a list of angles, returns the name of the polygon or sums the interior angles or decides if the shape closes or whatever.

I left the null in the angle constructor to be future slots for other data. Maybe that was a dumb decision. But it felt right and in the same spirit as the rest of the code to have this base level “make an angle object” and then let functions that take an angle object tell you things about it.

Reply via email

Tags