- The Ruby module
# Funky list utilties.
module ListUtil
# Rotate array a k times (a negative number of times
# is the same as zero times)
def rotate(k, a)
return Array.new(a) if k < 0 or a.empty?
k %= a.length;
a[-k..-1] + a[0...-k]
end
# Return p(a0, ..., p(an-2, an-1)...)
def right_reduce(p, a)
raise ArgumentError if a.empty?
b = a[-1]
a[0..-2].reverse_each {|x| b = p.call(x, b)}
b
end
# Return the array of the squares of the numbers in a, sorted
# in descending order
def descending_numeric_squares(a)
((a.select {|x| Numeric === x}).map {|x| x * x}).sort.reverse
end
# Return the number of items y in a such that p(x)(y). This
# solution is inefficient because it actually constructs the
# list containing the elements satisfying p(x)(y) and then
# returns the size of the list. It would have been better
# to write
#
# result = 0
# q = p.call(x)
# a.each {|y| result += 1 if q.call(y)}
# result
def partial_filter(p, x, a)
(a.select {|y| p.call(x).call(y)}).length
end
# Recursively flatten the array a
def flatten(a)
a.flatten
end
# Return a hash with the counts of each item in a
def histogram(a)
b = Hash.new 0
a.each {|x| b[x] += 1}
b
end
end
- The Ruby unit test
require 'test/unit'
require 'listutil.rb'
class ListUtilTest < Test::Unit::TestCase
include ListUtil
def test_rotate
a = [3, "dog", 4, true, 7.5, nil, 10];
b = Array.new(a)
assert_equal(rotate(3, a), [7.5, nil, 10, 3, "dog", 4, true])
# Test that rotate was non-destructive
assert_equal(a, b)
# Test negatives and 0
assert_equal(rotate(-2, a), b)
assert_equal(rotate(-4, a), b)
assert_equal(rotate(-20, a), b)
assert_equal(rotate(0, a), b)
# Test huge numbers
assert_equal(rotate(3452352342354326320, a[0..4]), a[0..4])
# Test rotations of the empty list
assert_equal(rotate(-4, []), [])
assert_equal(rotate(0, []), [])
assert_equal(rotate(15, []), [])
end
def test_partial_filter
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 partial_filter(less_than, 17, []), 0
end
def test_flatten
assert_equal flatten([[3,5],[],[3,1,1]]), [3,5,3,1,1]
assert_equal flatten([]), []
assert_equal flatten([[], [[],[[]]], []]), []
assert_equal flatten([1]), [1]
assert_equal flatten([[[[2,3]],[[[4]]]]]), [2,3,4]
a = []
a << a
assert_raise(ArgumentError) {flatten(a)}
a = [1]
b = [2]
c = [3]
a << b
b << c
c << a
assert_raise(ArgumentError) {flatten(a)}
end
def test_histogram
assert_equal histogram([2,6,5,5,2,3,5]), {2=>2,6=>1,5=>3,3=>1}
assert_equal histogram([]), {}
assert_equal histogram(["xyz", :xyz, "xyz", nil]),
{nil => 1, "xyz" => 2, :xyz => 1}
end
def test_right_reduce
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
end
def test_squares
assert_equal descending_numeric_squares([]), [];
assert_equal descending_numeric_squares([false]), [];
assert_equal descending_numeric_squares([99.0]), [9801];
assert_equal descending_numeric_squares([-1, 1.25, 8, "dog"]), [64, 1.5625, 1];
end
end
- The JavaScript object
// This object contains six funky list operations.
var ListUtil = {
/* partial_filter(p, x, a) returns the number of items y in a
* such that p(x)(y) is true.
*/
partial_filter: function(p, x, a) {
var total = 0;
for (var i = 0; i < a.length; i++) {
if (p(x, a[i])) {total++;}
}
return total;
},
/* flatten ([a0, a1, ..., an]) returns a list of all items in
* it input list, except that where any of the items are array
* references, these are flattened. For example
*
* flatten [ [3, 1, 2], [3, [1, 1]], [9] ]
*
* would return [3, 1, 2, 3, 1, 1, 9].
*/
flatten: function(a) {
function flatten_helper(a, seen) {
var result = [];
for (var i = 0; i < a.length; i++) {
if (a[i] && a[i].constructor && a[i].constructor == Array) {
for (var j = 0; j < seen.length; j++) {
if (a[i] === seen[j]) {
throw new Error("Can't flatten recursive array");
}
}
seen.push(a[i]);
result = result.concat(flatten_helper(a[i], seen));
seen.pop();
} else {
result.push(a[i]);
}
}
return result;
}
return flatten_helper(a, [a]);
},
/* Returns a hash telling how many times each item occurs in
* the list. Example: histogram("dog", 5.5, "dog") returns
* {"dog": 2, "5.5": 1}.
*/
histogram: function(a) {
var counts = {};
for (var i = 0; i < a.length; i++) {
counts[a[i]] = counts[a[i]] == undefined ? 1 : counts[a[i]] + 1;
}
return counts;
},
/* rotate(k, a) returns the list equivalent to rotating the list
* a to the right k times. Values of k less than 0 are treated
* as 0.
*/
rotate: function(k, a) {
if (k <= 0 || a.length === 0) return a.slice();
var k = a.length - k % a.length;
return a.slice(k).concat(a.slice(0, k));
},
/* right_reduce (f, [x0, x1, ..., xn]) returns
* f(x0, f(x1, ... f(xn-2, xn-1)))
*/
right_reduce: function(f, a) {
if (a.length == 0) {
throw new Error("Non empty array required");
} else {
var result = a[a.length - 1];
for (var i = a.length - 2; i >= 0; i--) {
result = f(a[i], result);
}
return result;
}
},
/* descending_integer_squares(a) returns an array containing
* the squares of all the integers in a, sorted in reverse
* order.
*/
descending_integer_squares: function (a) {
var result = [];
for (var i in a) {
var x = a[i]
if (typeof(x) == typeof(0)) {
if (x.toFixed() == x) {
result.push(x * x);
}
}
}
result.sort(function(x, y) {return y - x;});
return result;
}
}
- The JavaScript test
// Unit tests for the listutil object.
var assertions = 0;
var failures = 0;
function assert(condition) {
assertions++;
if (!eval(condition)) {
document.write("Assertion failure: " + condition + "<br />");
failures++;
}
}
function assert_something_thrown(expression) {
try {
eval(expression);
document.write(expression + " should have thrown an exception<br />");
assert("false");
} catch (e) {
assert("true");
}
}
Array.prototype.eq = function(that) {
if (!that || this.length != that.length) {
return false;
}
for (var i = 0; i < this.length; i++) {
if (this[i] != that[i]) return false;
}
return true;
}
/*** Tests for flatten ***/
assert( "ListUtil.flatten([[3,5],[],[3,1,1]]).eq([3,5,3,1,1])" );
assert( "ListUtil.flatten([]).eq([])" );
assert( "ListUtil.flatten([[], [[],[[]]], []]).eq([])" );
assert( "ListUtil.flatten([1]).eq([1])" );
assert( "ListUtil.flatten([[[[2,3]],[[[4]]]]]).eq([2,3,4])" );
var a = [];
a.push(a);
assert_something_thrown("ListUtil.flatten(a)");
var a = [1];
var b = [2];
var c = [3];
a.push(b);
b.push(c);
c.push(a);
assert_something_thrown("ListUtil.flatten(a)");
/*** Tests for rotate ***/
var a = [3, "dog", 4, true, 7.5, null, 10];
var b = a.slice();
assert("b.length == 7");
assert("ListUtil.rotate(3, a).eq([7.5, null, 10, 3, \"dog\", 4, true])");
// Test that rotate was non-destructive
assert("a.eq(b)");
assert( "ListUtil.rotate(-2, a).eq(b)" );
assert( "ListUtil.rotate(-4, a).eq(b)" );
assert( "ListUtil.rotate(-20, a).eq(b)" );
assert( "ListUtil.rotate(0, a).eq(b)" );
assert( "ListUtil.rotate(34523520, [1,2,3,4,5]).eq([1,2,3,4,5])" );
assert( "ListUtil.rotate(-5, []).eq([])" );
assert( "ListUtil.rotate(0, []).eq([])" );
assert( "ListUtil.rotate(100, []).eq([])" );
/*** Tests for descending_integer_squares ***/
assert( "ListUtil.descending_integer_squares([1, false, 5, -2]).eq([25, 4, 1])" );
assert( "ListUtil.descending_integer_squares([1]).eq([1])" );
assert( "ListUtil.descending_integer_squares([]).eq([])" );
assert( "!ListUtil.descending_integer_squares([0]).eq([])" );
assert( "ListUtil.descending_integer_squares([undefined, 2.2, 5.0]).eq([25])" );
assert( "ListUtil.descending_integer_squares([{x:0,y:2}, null]).eq([])" );
/*** Tests for count_items ***/
var a = ListUtil.histogram(["dog"]);
assert( "a[\"dog\"] == 1" );
assert( "a[\"cat\"] == undefined" );
a = ListUtil.histogram([3, 3, 4, 1, 2, 5, 6, 7, 7, 8, 7]);
assert( "a[3] == 2" );
assert( "a[1] == 1" );
assert( "a[7] == 3" );
/*** Report ***/
document.write(assertions + " assertions, " + failures + " failures");
- The C header file - NOT DONE YET
/*
* A silly little list module, containing a singly-linked list
* of integers type, and a number of utility functions assigned
* in a Programming Languages class for the purpose of showing
* how C compares to Ruby and JavaScript for list functions.
*
* This is a C99 program.
*/
#include <stdlib.h>
#include <stdbool.h>
// Types for some functional programming
typedef bool binaryPredicate(int x, int y);
typedef int binaryFunction(int x, int y);
// The linked list type
typedef struct node {int data; struct node* next;} *list;
// Something to help with the histogram function
typedef struct {int key; int value;} pair;
// Basic list operations
/*
* Creates a new list node, returning a pointer to it.
*/
list newNode(int data, list next);
/*
* 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);
/*
* Returns (a pointer to) the ith item in a (zero-based).
*/
list nodeAt(list a, int i);
/*
* Allocates and returns a copy of the chain of nodes starting at a.
*/
list copy(list a);
/*
* Returns whether a and b are equal by value (using == on the elements).
*/
bool equals(list a, list b);
/*
* Returns a copy of the first n nodes of a.
*/
list take(list a, int n);
/*
* Returns a copy of a with the first n nodes removed.
*/
list drop(list a, int n);
/*
* Returns the number of elements in a.
*/
int length(list a);
/*
* Returns the list containing nodes from a copy of a followed
* by the nodes in a copy of b.
*/
list append(list a, list b);
// Six sophisticated list utilities
/*
* rotate(k, a) returns the list equivalent to rotating the list
* a to the right k times. Values of k less than 0 are treated
* as 0.
*/
list rotate(int n, list a);
/* rightReduce (f, [x0, x1, ..., xn]) returns
* f(x0, f(x1, ... f(xn-2, xn-1)))
*/
int rightReduce(binaryFunction f, list a);
/*
* descendingNumericSquares(a) returns a list containing
* the squares of all the values in a, sorted in reverse
* order.
*/
list descendingNumericSquares(list a);
/*
* partialFilter(p, x, a) returns the number of items y in a
* such that p(x,y) is true.
*/
int partialFilter(binaryPredicate p, int x, list a);
/*
* flatten a, where a is an array of lists, appends each of the
* lists together.
*/
list flatten(list* a, int arraySize);
/*
* histogram a returns an association list telling how many times
* each item occurs in list a. Example: histogram [4, 3, 1, 3, 3, 1]
* ==> [(4,1),(3,3),(1,2)].
*/
pair* histogram(list a);
- The C implementation file - NOT DONE YET
/*
* Implementation of the list module, containing six functions assigned
* in a Programming Languages class for the purpose of showing how C
* compares to Ruby and JavaScript for list functions.
*/
#include "listutil.h"
#include <stdio.h>
#include <limits.h>
list newNode(int data, list next) {
list result = malloc(sizeof(struct node));
result->data = data;
result->next = next;
return result;
}
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;
}
}
list nodeAt(list a, int i) {
if (a == NULL) return NULL;
else if (i <= 0) return a;
else return nodeAt(a->next, i - 1);
}
list copy(list a) {
return a == NULL ? NULL : newNode(a->data, copy(a->next));
}
bool equals(list a, list b) {
if (a == NULL) return b == NULL;
else if (b == NULL) return false;
else return a->data == b->data && equals(a->next, b->next);
}
list take(list a, int k) {
if (a == NULL || k <= 0) return NULL;
else return newNode(a->data, take(a->next, k - 1));
}
list drop(list a, int k) {
return copy(nodeAt(a, k));
}
int length(list a) {
return a == NULL ? 0 : 1 + length(a->next);
}
list append(list a, list b) {
return a == NULL ? copy(b) : newNode(a->data, append(a->next, b));
}
list rotate(int n, list a) {
if (a == NULL) return NULL;
if (n <= 0) return copy(a);
int k = length(a) - (n % length(a));
return append(drop(a, k), take(a, k));
}
int rightReduce(binaryFunction f, list a) {
if (a == NULL) exit(-1);
else if (a->next == NULL) return a->data;
else return f(a->data, rightReduce(f, a->next));
}
// Here's a quadratic time, but simple to code, version.
list descendingNumericSquares(list a) {
// Set up a sentinel to make the coding much easier
list result = newNode(INT_MAX, NULL);
// For each element in the original list
for (list p = a; p; p = p->next) {
int value = p->data * p->data;
list q;
// Insert the square in the proper place
for (q = result; q->next && q->next->data > value; q = q->next);
q->next = newNode(value, q->next);
}
// Free the first node, and return the rest
list returnValue = result->next;
free(result);
return returnValue;
}
int partialFilter(binaryPredicate p, int x, list a) {
return (a == NULL) ? 0
: (p(x, a->data) ? 1 : 0) + partialFilter(p, x, a->next);
}
list flatten(list* a, int arraySize) {
list result = NULL;
list q;
for (int i = 0; i < arraySize; i++) {
for (list p = a[i]; p; p = p->next) {
if (!result) q = result = newNode(p->data, NULL);
else q = q->next = newNode(p->data, NULL);
}
}
return result;
}
pair* histogram(list a) {
// TODO
return 0;
}
- The C unit test - NOT DONE YET
/*
* Unit tests for the listutil module.
*/
#include "listutil.h"
#include <assert.h>
#include <stdio.h>
void print(list a) {
printf("[");
for (list p = a; p; p = p->next) {
if (p != a) printf(",");
printf("%d", p->data);
}
printf("]");
}
int main(int argc, char** argv) {
// Test rotate
list x = listOf("1 2 3 4 5 6 7");
assert(equals(x, rotate(-6, x)));
assert(equals(x, rotate(0, x)));
assert(equals(x, rotate(7, x)));
assert(equals(listOf("5 6 7 1 2 3 4"), rotate(3, x)));
assert(equals(listOf("6 7 1 2 3 4 5"), rotate(777779, x)));
// Test rightReduce
int twoXplusY(int x, int y) {return 2 * x + y;}
x = listOf("4 5 6 2");
assert(rightReduce(twoXplusY, x) == 32);
// Test partialFilter
x = listOf("4 5 6 2");
bool lessThan(int x, int y) {return y < x;}
assert(partialFilter(lessThan, 6, NULL) == 0);
assert(partialFilter(lessThan, 6, x) == 3);
// Test descendingNumericSquares
x = listOf("10");
assert(equals(descendingNumericSquares(x), listOf("100")));
x = listOf("10 4 -3 8 20 6 -4 0 4");
list y = listOf("400 100 64 36 16 16 16 9 0");
assert(equals(descendingNumericSquares(x), y));
x = listOf("4 5 6 2");
assert(equals(descendingNumericSquares(x), listOf("36 25 16 4")));
// Test flatten
list a[5];
a[0] = listOf("");
a[1] = listOf("7 5 4");
a[2] = listOf("6");
a[3] = listOf("");
a[4] = listOf("8 7 7 2 4");
assert(equals(flatten(a, 5), listOf("7 5 4 6 8 7 7 2 4")));
// Test histogram
x = listOf("");
x = listOf("2 9 9 8 4 8 2 8 8 2 2 9");
printf("All tests passed\n");
}