It is now time for some very intensive programming. (If you prefer the assignments where you just solve problems at the end of the chapters in the textbook, you'll have to wait for Assignment #4.) This assignment is due Thursday, October 18 for undergraduates, and Wednesday, October 22 for graduates.
Readings: Read as many on-line tutorials or book chapters on Ruby and JavaScript as is reasonable without seriously impacting your personal life. Also read a bit about C if you are rusty. If you have [Thomas], read Chapters 1-10, and 12.
Version Control (Undergraduates only): Make sure you've set up a version control repository. You can use the CVS repository that comes with your lab account, or a personal repository (CVS or SVN) hosted elsewhere. You should add at least the following files to your repository (you may add more):
/homework/cmsi386/src/main/ruby/listutil.rb /homework/cmsi386/src/test/ruby/listutiltest.rb /homework/cmsi386/src/main/js/listutil.js /homework/cmsi386/src/test/js/listutiltest.html /homework/cmsi386/src/main/c/listutil.h /homework/cmsi386/src/main/c/listutil.c /homework/cmsi386/src/test/c/listutiltest.c
For this project, you will be writing three versions of the same basic library: one in C, one in Ruby, and one in JavaScript. You will also write unit test suites for each library. This is a good assignment because trying to do the exact same thing in multiple languages helps to highlight similarities and differences between languages. You will also uncover cases where it really isn't practical, or doesn't even make sense, to do exactly the same thing in different languages.
The library will consist of six utility functions that:
None of these operations should modify any of their input arguments.
In this assignment, we will be investigating the best ways to carry out these tasks in three different languages: Ruby, JavaScript, and C. (In the next homework assignment we will carry out these tasks in still two more languages: Java and Standard ML.)
Ruby
Your Ruby library should be in the form of a Ruby module. I've started it for you:
module ListUtil def rotate(k, a) ... def right_reduce(p, a) ... def descending_numeric_squares(a) ... def partial_filter(p, x, a) ... def flatten(a) ... def histogram(a) ... end
The intent of each operation is best expressed by illustrating some ready-to-run unit tests (remember that Ruby has a unit test framework in its core library). Please note that a proper test suite will have many, many more assertions than are shown here.
assert_equal rotate(4, [1,2,3,4,5,6]), [3,4,5,6,1,2]
assert_equal rotate(0, ["A", "b"]), ["A", "b"]
assert_equal rotate(-7, ["A", "b"]), ["A", "b"]
max = Proc.new {|x, y| x > y ? x : y}
g = Proc.new {|x, y| 2 * x - y}
assert_equal right_reduce(max, [5, 7, 1]), 7
assert_equal right_reduce(g, [5, 7, 1]), -3
assert_raise right_reduce(g, []);
assert_equal
descending_numeric_squares([-1, 2.999, 8, "dog"]),
[64, 6.994001, 1];
less_than = Proc.new {|x| Proc.new {|y| y < x}}
assert_equal partial_filter(less_than, 7, [5,8,9,1,10,7,9,1]), 3
assert_equal flatten([[3,5],[],[3,[[1]],1]]), [3,5,3,1,1]
assert_equal histogram([2,6,5,5,2,3,5]), {2=>2,6=>1,5=>3,3=>1}
JavaScript
Your JavaScript functions should be gathered up into a single object, with this format:
var list_util = {
rotate: function(k, a) {...},
right_reduce: function(f, a) {...}
descending_numeric_squares: function (a) {...},
partial_filter: function(p, x, a) {...},
flatten: function(a) {...},
histogram: function(a) {...}
}
One difference between JavaScript and Ruby is that you can not use JavaScript objects for the results of your histogram. So you will need to return the histogram as an array of arrays. Sorry. (Another difference is that JavaScript has no built-in flatten method!)
You can use JSUnit for testing, or code up your own framwork with functions such as assert and assert_arrays_equal. Your tests will look something like this:
assert_arrays_equal(
list_util.rotate(4, [1,2,3,4,5,6]), [3,4,5,6,1,2]);
assert_arrays_equal(
list_util.rotate(0, ["A", "b"]), ["A", "b"]);
assert_arrays_equal(
list_util.rotate(-7, ["A", "b"]), ["A", "b"]);
var max = function(x, y) {return x > y ? x : y;}
var g = function(x, y) {return 2 * x - y;}
assert(list_util.right_reduce(max, [5, 7, 1]) == 7);
assert(list_util.right_reduce(g, [5, 7, 1]) == -3);
assert_arrays_equal(
list_util.descending_numeric_squares([-1, 8, "dog"]),
[64, 1]);
var less_than = function(x) {return function(y) {return y < x}}
assert(list_util.partial_filter(less_than, 7, [5,8,9,1,10,7,9,1]) == 3)
assert_arrays_equal(
list_util.flatten([[3,5],[],[3,1,1]]),
[3,5,3,1,1]);
assert_objects_equal(
list_util.histogram([2,6,5,5,2,3,5]),
[[2,2], [6,1], [5,3], [3,1]]);
C
C isn't a language particularly well suited to writing these six functions: there's no built-in list type and the static typing is annoying, requiring us to use macros or void* expressions to get around. We can give up and make our library work only on integers. Since this is only a one-semester class, let's do the giving up thing.
The C header file should look like this (note I am using C99):
#include <stdlib.h>
#include <stdbool.h>
typedef bool binaryPredicate(int x, int y);
typedef int binaryFunction(int x, int y);
typedef struct {int key; int value;} pair;
typedef struct node {int data; struct node* next} *list;
list rotate(int n, list a);
int rightReduce(binaryFunction f, list a);
list descendingNumericSquares(list a);
int partialFilter(binaryPredicate p, int x, list a);
list flatten(int** a, int arraySize);
pair* histogram(list a);
Again, here are a few unit test expressions to help you understand how each function is supposed to work.
list x = listOf("1 2 3 4 5 6 7");
assert(equalLists(x, rotate(-6, x));
assert(equalLists(x, rotate(0, x));
assert(equalLists(x, rotate(7, x));
assert(equalLists(listOf("5 6 7 1 2 3 4"), rotate(3, x));
assert(equalLists(listOf("6 7 1 2 3 4 5"), rotate(777779, x));
int twoXplusY(x, y) {return 2 * x + y;}
x = listOf("4 5 6 2");
assert(equalLists(rightReduce(twoXplusY, x), 32));
x = listOf("4 5 6 2");
assert(equalLists(descendingNumericSquares(x), listOf("36 25 16 4")));
x = listOf("4 5 6 2");
bool lessThan(x, y) {return y < x;}
assertEquals(partialFilter(lessThan, 6, x), 3);
/* TODO asserts for flatten */
x = listOf("20 20 1 6 5 1 1 1 1 1 1");
/* todo assert histogram */
Note these tests assume some list "helper" methods, such as
list newNode(int data, list next) {
list result = malloc(sizeof(struct node));
result->data = data;
result->next = next;
return result;
}
/*
* Returns a linked list made from a string by taking all clumps of digits.
* For example,
*
* "4 5 % 67675;;r34" ==> [4,5,67675,34]
*/
list listOf(char* s) {
list result = NULL;
list last = NULL;
int value = 0;
bool readingNumber = false;
for (char* p = s; true; p++) {
if ('0' <= *p && *p <= '9') {
/* Continue accumulating the number */
value = (value * 10) + (*p - '0');
readingNumber = true;
} else {
if (readingNumber) {
/* Done reading the number, add to end of list */
if (result == NULL) last = result = newNode(value, NULL);
else last = last->next = newNode(value, NULL);
}
readingNumber = false;
value = 0;
}
/* Finished off the string */
if (*p == 0) return result;
}
}
bool equals(list a, list b) {
if (a == NULL) return b == NULL;
if (b == NULL) return false;
return a->data == b->data && equals(a->next, b->next);
}