Work on HW2
This commit is contained in:
parent
9c1c7ad12c
commit
0af248a3d6
10 changed files with 29212 additions and 0 deletions
4
hw1/claudio_maggioni/README.txt
Normal file
4
hw1/claudio_maggioni/README.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Implemented bonuses:
|
||||||
|
- Gallery
|
||||||
|
- one column mobile form layout
|
||||||
|
|
226
hw2/claudio_maggioni/jsverify-util.js
Normal file
226
hw2/claudio_maggioni/jsverify-util.js
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
function jsc_utils(jsc) {
|
||||||
|
function copyArgs(f) {
|
||||||
|
function f_(...args) {
|
||||||
|
let copies = args.map(a => {
|
||||||
|
if (typeof a === "function") return a;
|
||||||
|
else return _.cloneDeep(a);
|
||||||
|
});
|
||||||
|
return f.apply(this, copies);
|
||||||
|
};
|
||||||
|
return f_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch the function to use copied arbitrary values
|
||||||
|
// jsverify goes crazy if arbitrary arguments are mutated
|
||||||
|
function patchArgs(args) {
|
||||||
|
let args_ = _.initial(args);
|
||||||
|
let f = _.last(args);
|
||||||
|
args_.push(copyArgs(f));
|
||||||
|
return args_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mocha_property(name, ...args) {
|
||||||
|
let args_ = patchArgs(args);
|
||||||
|
args_.unshift(name);
|
||||||
|
return jsc.property.apply(this, args_);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pretty_function(data) {
|
||||||
|
function display(pair) {
|
||||||
|
let [as, r] = pair;
|
||||||
|
if (!_.isArray(as)) as = [as];
|
||||||
|
return '\t\tcase ' + JSON.stringify(as) + ': return ' +
|
||||||
|
JSON.stringify(r) + ';';
|
||||||
|
}
|
||||||
|
return 'function(...args) {\n\tswitch(args) {\n' + data.map(display).join('\n') + '}\n}';
|
||||||
|
}
|
||||||
|
function pretty(v) {
|
||||||
|
if (typeof v === 'function') {
|
||||||
|
if (v.original_fun !== undefined) v = v.original_fun;
|
||||||
|
return pretty_function(v.internalMap.data);
|
||||||
|
return JSON.stringify(v.internalMap.data, undefined, 2);
|
||||||
|
}
|
||||||
|
return JSON.stringify(v, undefined, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function qunit_property_result(name, ...args) {
|
||||||
|
let args_ = patchArgs(args);
|
||||||
|
let res = jsc.check(jsc.forall.apply(this, args_));
|
||||||
|
if (res == true)
|
||||||
|
return QUnit.assert.pushResult({result: true, message: name});
|
||||||
|
let ce = '(' + _.take(res.counterexample, args.length - 1).map(pretty).join(',\n ') + ')\n';
|
||||||
|
return QUnit.assert.pushResult(
|
||||||
|
{result: false,
|
||||||
|
actual: res.exc.actual,
|
||||||
|
expected: res.exc.expected,
|
||||||
|
message: name + ' failed after ' +
|
||||||
|
res.tests + ' tests, counterexample:\n' + ce,
|
||||||
|
expectedLog: res.exc.expectedLog,
|
||||||
|
resultLog: res.exc.resultLog,
|
||||||
|
outputLog: res.exc.outputLog});
|
||||||
|
}
|
||||||
|
|
||||||
|
function qunit_property(name, ...args) {
|
||||||
|
let args_ = patchArgs(args);
|
||||||
|
return ok(jsc.assert(jsc.forall.apply(this,args_)) || true, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function propEqual(x,y,rl,el,ol) {
|
||||||
|
if (_.isEqual(x,y)) return true;
|
||||||
|
throw { actual: x, expected: y, expectedLog: el, resultLog: rl, outputLog: ol};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mocha_property: mocha_property,
|
||||||
|
qunit_property: qunit_property,
|
||||||
|
qunit_property_result: qunit_property_result,
|
||||||
|
propEqual: propEqual
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function arbitrary_utils(jsc) {
|
||||||
|
/* custom arbitraries */
|
||||||
|
function set(arb) {
|
||||||
|
return arb.smap(_.uniq, _.identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
function oneOfWeight(...args) {
|
||||||
|
return jsc.oneof(_.flatMap(args, x => _.fill(Array(x[1]), x[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
function equalLengthArrays(...args) {
|
||||||
|
// generate an array of tuples
|
||||||
|
let arbA = jsc.array(jsc.tuple(args));
|
||||||
|
return arbA.smap(
|
||||||
|
x => {
|
||||||
|
if (x.length == 0) return _.map(args, x => []);
|
||||||
|
return _.unzip(x);
|
||||||
|
},
|
||||||
|
y => _.zip.apply(null, y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortedArray(t) {
|
||||||
|
return jsc.array(t).smap(_.sortBy,_.identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
function vect(t, n) {
|
||||||
|
return jsc.tuple(_.fill(Array(n), t));
|
||||||
|
}
|
||||||
|
|
||||||
|
function roseTree(t, max_level) {
|
||||||
|
if (max_level <= 0) return jsc.array(t);
|
||||||
|
return jsc.array(jsc.oneof(t, roseTree(t, max_level - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function charRange(min,max) {
|
||||||
|
return jsc.nonshrink(jsc.integer(min.charCodeAt(0), max.charCodeAt(0)).smap(
|
||||||
|
String.fromCharCode,
|
||||||
|
c => c.charCodeAt(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lLetter = charRange('a', 'z');
|
||||||
|
let uLetter = charRange('A', 'Z');
|
||||||
|
let letter = jsc.oneof(lLetter, uLetter);
|
||||||
|
|
||||||
|
let whitespace = oneOfWeight([jsc.constant(' '), 5],
|
||||||
|
[jsc.constant(' '), 4],
|
||||||
|
[jsc.constant(' '), 1],
|
||||||
|
[jsc.constant('\t'), 1]);
|
||||||
|
|
||||||
|
let punctuation = oneOfWeight([jsc.constant('.'), 3],
|
||||||
|
[jsc.constant('...'),1],
|
||||||
|
[jsc.constant('!'), 1],
|
||||||
|
[jsc.constant('?'), 1]);
|
||||||
|
|
||||||
|
function permutation(n) {
|
||||||
|
return jsc.tuple(_.times(n-1, i => jsc.nat(n-i-1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function some(arb,min,max) {
|
||||||
|
return jsc.bless({
|
||||||
|
generator: jsc.integer(min,max).generator
|
||||||
|
.flatmap(n => vect(arb,n).generator)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function word(l,min,max) {
|
||||||
|
return some(l,min,max).smap(a => a.join(''), s => s.split(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sentence(w,wSp,initWSp,trailWSp,p,min,max) {
|
||||||
|
return jsc.bless({
|
||||||
|
generator: jsc.integer(min,max).generator.flatmap(
|
||||||
|
n => jsc.generator.tuple([vect(w,n).generator,
|
||||||
|
vect(wSp, n-1).generator,
|
||||||
|
initWSp.generator,
|
||||||
|
trailWSp.generator,
|
||||||
|
p.generator]).map(a => {
|
||||||
|
let [ws,wSps,iWs,tWs,p] = a;
|
||||||
|
wSps.push(p + tWs);
|
||||||
|
return iWs +
|
||||||
|
_.zipWith(ws, wSps, _.add).join('');
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let posInteger = jsc.nat.smap(x => x+1, x => x-1);
|
||||||
|
|
||||||
|
// jsverify doesn't have arbitrary multi-argument functions
|
||||||
|
// but it has curried functions and functions accepting tuples
|
||||||
|
// to get usual multi-argument functions we can either uncurry
|
||||||
|
// the generated function (in case we choose to generated
|
||||||
|
// curried function) or grab all functions arguments as an
|
||||||
|
// array and pass it to the generated function accepting tuples.
|
||||||
|
// Here we choose to use tuple-based version to get better counterexamples.
|
||||||
|
// There is a small catch: we have to ensure the right length of the tuple,
|
||||||
|
// otherwise f(1,2,ununsed_arg) won't be the same as f(1,2).
|
||||||
|
function fixManyArgFun(n,f) {
|
||||||
|
let fixed_f = function(...args) {
|
||||||
|
let as = _.take(args,n);
|
||||||
|
let d = n - as.length;
|
||||||
|
if (d) as.concat(_.fill(Array(d), undefined));
|
||||||
|
return f(as);
|
||||||
|
};
|
||||||
|
fixed_f.original_fun = f;
|
||||||
|
return fixed_f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// arbitrary function taking n arguments
|
||||||
|
function fnN(n, returnArb) {
|
||||||
|
let funArb = jsc.fn(returnArb);
|
||||||
|
let oldShow = funArb.show;
|
||||||
|
return funArb.smap(f => fixManyArgFun(n,f),
|
||||||
|
f => f.original_fun,
|
||||||
|
f => oldShow(f.original_fun));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fn2(returnArb) { return fnN(2, returnArb); }
|
||||||
|
function fn3(returnArb) { return fnN(3, returnArb); }
|
||||||
|
|
||||||
|
let anything = jsc.oneof(jsc.json, jsc.fn(jsc.json), jsc.falsy);
|
||||||
|
|
||||||
|
return {
|
||||||
|
set: set,
|
||||||
|
oneOfWeight: oneOfWeight,
|
||||||
|
vect: vect,
|
||||||
|
equalLengthArrays: equalLengthArrays,
|
||||||
|
sortedArray: sortedArray,
|
||||||
|
roseTree: roseTree,
|
||||||
|
charRange: charRange,
|
||||||
|
permutation: permutation,
|
||||||
|
uLetter: uLetter,
|
||||||
|
lLetter: lLetter,
|
||||||
|
letter: letter,
|
||||||
|
whitespace: whitespace,
|
||||||
|
punctuation: punctuation,
|
||||||
|
some: some,
|
||||||
|
word: word,
|
||||||
|
sentence: sentence,
|
||||||
|
posInteger: posInteger,
|
||||||
|
fnN : fnN,
|
||||||
|
fn2 : fn2,
|
||||||
|
fn3 : fn3,
|
||||||
|
anything
|
||||||
|
};
|
||||||
|
}
|
5
hw2/claudio_maggioni/qunit-compat.js
Normal file
5
hw2/claudio_maggioni/qunit-compat.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
let test = QUnit.test;
|
||||||
|
let equal = QUnit.assert.equal.bind(QUnit.assert);
|
||||||
|
let notEqual = QUnit.assert.notEqual.bind(QUnit.assert);
|
||||||
|
let deepEqual = QUnit.assert.deepEqual.bind(QUnit.assert);
|
||||||
|
let notDeepEqual = QUnit.assert.notDeepEqual.bind(QUnit.assert);
|
5588
hw2/claudio_maggioni/resources/jsverify.standalone.js
Normal file
5588
hw2/claudio_maggioni/resources/jsverify.standalone.js
Normal file
File diff suppressed because it is too large
Load diff
17084
hw2/claudio_maggioni/resources/lodash.js
Normal file
17084
hw2/claudio_maggioni/resources/lodash.js
Normal file
File diff suppressed because it is too large
Load diff
5062
hw2/claudio_maggioni/resources/qunit-2.4.0.js
Normal file
5062
hw2/claudio_maggioni/resources/qunit-2.4.0.js
Normal file
File diff suppressed because it is too large
Load diff
235
hw2/claudio_maggioni/resources/qunit.css
Normal file
235
hw2/claudio_maggioni/resources/qunit.css
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
/**
|
||||||
|
* QUnit v1.10.0 - A JavaScript Unit Testing Framework
|
||||||
|
*
|
||||||
|
* http://qunitjs.com
|
||||||
|
*
|
||||||
|
* Copyright 2012 jQuery Foundation and other contributors
|
||||||
|
* Released under the MIT license.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Font Family and Sizes */
|
||||||
|
|
||||||
|
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
|
||||||
|
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
||||||
|
#qunit-tests { font-size: smaller; }
|
||||||
|
|
||||||
|
|
||||||
|
/** Resets */
|
||||||
|
|
||||||
|
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Header */
|
||||||
|
|
||||||
|
#qunit-header {
|
||||||
|
padding: 0.5em 0 0.5em 1em;
|
||||||
|
|
||||||
|
color: #8699a4;
|
||||||
|
background-color: #0d3349;
|
||||||
|
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 1em;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
-moz-border-radius: 5px 5px 0 0;
|
||||||
|
-webkit-border-top-right-radius: 5px;
|
||||||
|
-webkit-border-top-left-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-header a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #c2ccd1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-header a:hover,
|
||||||
|
#qunit-header a:focus {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-testrunner-toolbar label {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .5em 0 .1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-banner {
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-testrunner-toolbar {
|
||||||
|
padding: 0.5em 0 0.5em 2em;
|
||||||
|
color: #5E740B;
|
||||||
|
background-color: #eee;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-userAgent {
|
||||||
|
padding: 0.5em 0 0.5em 2.5em;
|
||||||
|
background-color: #2b81af;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-modulefilter-container {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests: Pass/Fail */
|
||||||
|
|
||||||
|
#qunit-tests {
|
||||||
|
list-style-position: inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests li {
|
||||||
|
padding: 0.4em 0.5em 0.4em 2.5em;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
list-style-position: inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests li strong {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests li a {
|
||||||
|
padding: 0.5em;
|
||||||
|
color: #c2ccd1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
#qunit-tests li a:hover,
|
||||||
|
#qunit-tests li a:focus {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests ol {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: .2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests th {
|
||||||
|
text-align: right;
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 0 .5em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests pre {
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests del {
|
||||||
|
background-color: #e0f2be;
|
||||||
|
color: #374e0c;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests ins {
|
||||||
|
background-color: #ffcaca;
|
||||||
|
color: #500;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Test Counts */
|
||||||
|
|
||||||
|
#qunit-tests b.counts { color: black; }
|
||||||
|
#qunit-tests b.passed { color: #5E740B; }
|
||||||
|
#qunit-tests b.failed { color: #710909; }
|
||||||
|
|
||||||
|
#qunit-tests li li {
|
||||||
|
padding: 5px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: none;
|
||||||
|
list-style-position: inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Passing Styles */
|
||||||
|
|
||||||
|
#qunit-tests li li.pass {
|
||||||
|
color: #3c510c;
|
||||||
|
background-color: #fff;
|
||||||
|
border-left: 10px solid #C6E746;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
||||||
|
#qunit-tests .pass .test-name { color: #366097; }
|
||||||
|
|
||||||
|
#qunit-tests .pass .test-actual,
|
||||||
|
#qunit-tests .pass .test-expected { color: #999999; }
|
||||||
|
|
||||||
|
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
||||||
|
|
||||||
|
/*** Failing Styles */
|
||||||
|
|
||||||
|
#qunit-tests li li.fail {
|
||||||
|
color: #710909;
|
||||||
|
background-color: #fff;
|
||||||
|
border-left: 10px solid #EE5757;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests > li:last-child {
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
-moz-border-radius: 0 0 5px 5px;
|
||||||
|
-webkit-border-bottom-right-radius: 5px;
|
||||||
|
-webkit-border-bottom-left-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
|
||||||
|
#qunit-tests .fail .test-name,
|
||||||
|
#qunit-tests .fail .module-name { color: #000000; }
|
||||||
|
|
||||||
|
#qunit-tests .fail .test-actual { color: #EE5757; }
|
||||||
|
#qunit-tests .fail .test-expected { color: green; }
|
||||||
|
|
||||||
|
#qunit-banner.qunit-fail { background-color: #EE5757; }
|
||||||
|
|
||||||
|
|
||||||
|
/** Result */
|
||||||
|
|
||||||
|
#qunit-testresult {
|
||||||
|
padding: 0.5em 0.5em 0.5em 2.5em;
|
||||||
|
|
||||||
|
color: #2b81af;
|
||||||
|
background-color: #D2E0E6;
|
||||||
|
|
||||||
|
border-bottom: 1px solid white;
|
||||||
|
}
|
||||||
|
#qunit-testresult .module-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fixture */
|
||||||
|
|
||||||
|
#qunit-fixture {
|
||||||
|
position: absolute;
|
||||||
|
top: -10000px;
|
||||||
|
left: -10000px;
|
||||||
|
width: 1000px;
|
||||||
|
height: 1000px;
|
||||||
|
}
|
239
hw2/claudio_maggioni/script.js
Normal file
239
hw2/claudio_maggioni/script.js
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
/**
|
||||||
|
* JavaScript Exercise 1
|
||||||
|
* vim: set ts=2 sw=2 et tw=80:
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} a - The array of numbers.
|
||||||
|
* @param {number} c - The scalar multiplier.
|
||||||
|
* @return {number[]} An array computed by multiplying each element of the
|
||||||
|
* input array `a` with the input scalar value `c`.
|
||||||
|
*/
|
||||||
|
function scalar_product(a, c) {
|
||||||
|
if (!Array.isArray(a)) return undefined;
|
||||||
|
if (!c && c != 0) return a;
|
||||||
|
return a.map(e => e * c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} a - The first array of numbers.
|
||||||
|
* @param {number[]} b - The second array of numbers.
|
||||||
|
* @return {number} A value computed by summing the products of each pair
|
||||||
|
* of elements of its input arrays `a`, `b` in the same position.
|
||||||
|
*/
|
||||||
|
function inner_product(a, b) {
|
||||||
|
if (a.length != b.length) return undefined;
|
||||||
|
return a.map((e, i) => e * b[i]).reduce((a, e) => a + e, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*[]} a - The array.
|
||||||
|
* @param {function} mapfn - The function for the map step.
|
||||||
|
* @param {function} [reducefn= function(x,y) { return x+y; }] - The
|
||||||
|
* function for the reduce step.
|
||||||
|
* @param {string} [seed=""] - The accumulator for the reduce step.
|
||||||
|
* @return {*} The reduced value after the map and reduce steps.
|
||||||
|
*/
|
||||||
|
function mapReduce(a, mapfn, reducefn, seed = "") {
|
||||||
|
if (!Array.isArray(a)) return undefined;
|
||||||
|
if (typeof mapfn != 'function') return undefined;
|
||||||
|
reducefn = reducefn || ((x, y) => x + y);
|
||||||
|
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
seed = reducefn(seed, mapfn(a[i]));
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} a - The first sorted array of numbers.
|
||||||
|
* @param {number[]} b - The second sorted array of numbers.
|
||||||
|
* @return {number[]} A sorted array with all the elements from
|
||||||
|
* both `a` and `b`.
|
||||||
|
*/
|
||||||
|
function mergeSortedArrays(a, b) {
|
||||||
|
const m = new Array(a.length + b.length);
|
||||||
|
let ac = a.length - 1, bc = b.length - 1, i = ac + bc + 1;
|
||||||
|
|
||||||
|
while (i >= 0) {
|
||||||
|
if (ac < 0 || b[bc] > a[ac]) m[i--] = b[bc--];
|
||||||
|
else m[i--] = a[ac--];
|
||||||
|
}
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {integer} x - The first integer.
|
||||||
|
* @param {integer} y - The second integer.
|
||||||
|
* @param {integer} [step=1] - The value to add at each step.
|
||||||
|
* @return {integer[]} An array containing numbers x, x+step, … last, where:
|
||||||
|
* - last equals x + n*step for some n,
|
||||||
|
* - last <= y < last + step if step > 0 and
|
||||||
|
* - last + step < y <= last if step < 0.
|
||||||
|
*/
|
||||||
|
function range(x, y, step = 1) {
|
||||||
|
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(step)
|
||||||
|
|| step == 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(x, y, step);
|
||||||
|
if ((x > y && step > 0) || (x < y && step < 0)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = [];
|
||||||
|
|
||||||
|
while (x < y) {
|
||||||
|
a.push(x);
|
||||||
|
x += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*[]} a - The array to flatten.
|
||||||
|
* @return {*[]} A flattened array.
|
||||||
|
*/
|
||||||
|
function flatten(arr) {
|
||||||
|
if (!Array.isArray(arr)) {
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = [];
|
||||||
|
arr.forEach(e => {
|
||||||
|
e = flatten(e); Array.isArray(e) ? a.push(...e) : a.push(e); });
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {integer} [line_size=72] - The line size.
|
||||||
|
* @return {function} A function that takes a string as an argument
|
||||||
|
* and returns an array of strings where:
|
||||||
|
* a. each string length is no more than `line_size` and
|
||||||
|
* b. doesn't contain line breaks, tabs, double spaces and initial/trailing
|
||||||
|
* white spaces.
|
||||||
|
*/
|
||||||
|
function mkPrettyPrinter(line_size = 72) {
|
||||||
|
if (!Number.isFinite(line_size)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (s) => {
|
||||||
|
if (typeof s !== 'string') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
s = s.replace('\n', ' ').trim().replace(/\s+/g, ' ');
|
||||||
|
|
||||||
|
const a = [];
|
||||||
|
let i = -1;
|
||||||
|
const words = s.split(' ');
|
||||||
|
for (const word of words) {
|
||||||
|
if (i == -1 || a[i].length + 1 + word.length > line_size) {
|
||||||
|
a[++i] = word;
|
||||||
|
} else {
|
||||||
|
a[i] += ' ' + word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {integer} line_size - The line size.
|
||||||
|
* @param {integer} [level=0] level - The indentation level.
|
||||||
|
* @return {function} A function twith the following behavior:
|
||||||
|
* - If called with an integer `n`, change the indentation level by
|
||||||
|
* adding `n`to the current indentation level.
|
||||||
|
* - If called with `true`, return the current indentation level.
|
||||||
|
* - If called with a string:
|
||||||
|
* - break it into lines with length (after adding the indentation)
|
||||||
|
* no more than `line_size`,
|
||||||
|
* - add spaces in front of each line according to the current
|
||||||
|
* indentation level and
|
||||||
|
* - store the resulting lines internally.
|
||||||
|
* - [optional] If called with an array of strings, create an
|
||||||
|
* bullet list (using `*`) taking current indentation level into
|
||||||
|
* account. Also, each element should be properly broken into lines and indented.
|
||||||
|
* - If called with no arguments, produce an array with the lines stored so far.
|
||||||
|
* Internal storage must be emptied. Indentation level must not be changed.
|
||||||
|
*/
|
||||||
|
function mkIndenter(line_size, level=0) {
|
||||||
|
if (!Number.isFinite(line_size) || !Number.isFinite(level)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = [];
|
||||||
|
return (param) => {
|
||||||
|
if (Number.isInteger(param)) {
|
||||||
|
level += param;
|
||||||
|
} else if (param === true) {
|
||||||
|
return level;
|
||||||
|
} else if (typeof param === 'string') {
|
||||||
|
console.log(param);
|
||||||
|
|
||||||
|
let words = mkPrettyPrinter(line_size - level)(param);
|
||||||
|
words = words.map(e => ' ' * level + e);
|
||||||
|
state.push(...words);
|
||||||
|
|
||||||
|
console.log(line_size, level, param, state);
|
||||||
|
} else if (Array.isArray(param)) {
|
||||||
|
for (const e of param) {
|
||||||
|
if (typeof e !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const words = mkPrettyPrinter(line_size - level - 2)(param);
|
||||||
|
state.push('* ' + words[0]);
|
||||||
|
|
||||||
|
for (let i = 1; i < words.length; i++) {
|
||||||
|
state.push(' ' + words[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (param === void(0)) {
|
||||||
|
const a = state;
|
||||||
|
state = [];
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaScript Exercise 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of occurrences of letters in a given string
|
||||||
|
* @param {string} a - The input string.
|
||||||
|
* @return {number[]} An array indexed by the letter characters found in the string.
|
||||||
|
*/
|
||||||
|
function letter_frequency(s) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the output of the letter frequency analysis in an HTML table
|
||||||
|
* generated within the `dom` element passed as parameter
|
||||||
|
* @param {number[]} a - The array indexed by the letter characters as returned
|
||||||
|
* from `letter_frequency()`.
|
||||||
|
* @param {Object} dom - The DOM element that will contain the resulting table.
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function display_letter_frequency(a, dom) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links the provided input text field in the test page with the table.
|
||||||
|
* @param {Object} inputEl - The DOM object of the input element in test.html.
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function online_frequency_analysis(inputEl) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaScript Exercise 3
|
||||||
|
*/
|
||||||
|
function clock(){
|
||||||
|
|
||||||
|
}
|
39
hw2/claudio_maggioni/test.html
Normal file
39
hw2/claudio_maggioni/test.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Exercise 2 - Javascript</title>
|
||||||
|
<link rel="stylesheet" href="resources/qunit.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="qunit"></div>
|
||||||
|
<div id="qunit-fixture"></div>
|
||||||
|
<script src="script.js" type="text/javascript"></script>
|
||||||
|
<script src="resources/qunit-2.4.0.js"></script>
|
||||||
|
<script src="resources/lodash.js"></script>
|
||||||
|
<script src="resources/jsverify.standalone.js"></script>
|
||||||
|
<script src="jsverify-util.js"></script>
|
||||||
|
<script src="qunit-compat.js"></script>
|
||||||
|
<script src="test.js"></script>
|
||||||
|
<script>
|
||||||
|
let jsc = window.jsc;
|
||||||
|
let xmlParser = new window.DOMParser();
|
||||||
|
let utils = jsc_utils(jsc);
|
||||||
|
let arbs = arbitrary_utils(jsc);
|
||||||
|
|
||||||
|
tests(utils.qunit_property_result,
|
||||||
|
utils.propEqual,
|
||||||
|
jsc,
|
||||||
|
arbs,
|
||||||
|
xmlParser);
|
||||||
|
</script>
|
||||||
|
<div>
|
||||||
|
Frequency Analysis Input: <input type="text" onkeyup="online_frequency_analysis(this);"/>
|
||||||
|
<div id="frequency_table"></div>
|
||||||
|
<div id="clock"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById("clock").addEventListener('click', clock() );
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
730
hw2/claudio_maggioni/test.js
Normal file
730
hw2/claudio_maggioni/test.js
Normal file
|
@ -0,0 +1,730 @@
|
||||||
|
function tests(property,
|
||||||
|
propEqual,
|
||||||
|
jsc,
|
||||||
|
arbs,
|
||||||
|
xmlParser) {
|
||||||
|
|
||||||
|
// simple utility functions
|
||||||
|
function words(s) { return _.words(s, /[^\s]+/g); }
|
||||||
|
function compose(f, g) { return x => f(g(x)); }
|
||||||
|
function vectorSum(arr1,arr2) { return _.zipWith(arr1, arr2, _.add); }
|
||||||
|
|
||||||
|
window.vectorSum = vectorSum
|
||||||
|
|
||||||
|
// how to generate sentences (for mkPrettyPrinter & mkIndenter)
|
||||||
|
let sentence =
|
||||||
|
// words are 2-12 lower letters
|
||||||
|
arbs.sentence(arbs.word(arbs.lLetter,2,12),
|
||||||
|
// prefer white space between words
|
||||||
|
arbs.oneOfWeight([arbs.whitespace, 8],
|
||||||
|
// but rarely put '\n'
|
||||||
|
[jsc.constant('\n'), 1]),
|
||||||
|
// prefer no leading whitespace
|
||||||
|
arbs.oneOfWeight([jsc.constant(''), 3],
|
||||||
|
// but sometimes add some
|
||||||
|
[arbs.whitespace, 1]),
|
||||||
|
// slightly prefer adding trailing whitespace
|
||||||
|
arbs.oneOfWeight([arbs.whitespace, 2],
|
||||||
|
// but sometimes don't
|
||||||
|
[jsc.constant(''), 1]),
|
||||||
|
// use '.', '!', '?' or '...' at the end
|
||||||
|
arbs.punctuation,
|
||||||
|
// sentence has 5 to 30 words
|
||||||
|
5,
|
||||||
|
30);
|
||||||
|
|
||||||
|
test("Scalar product", function() {
|
||||||
|
property(
|
||||||
|
"doesn't change the source array",
|
||||||
|
"array number", "number",
|
||||||
|
function (arr,num) {
|
||||||
|
let copy = _.cloneDeep(arr);
|
||||||
|
let t = scalar_product(copy,num);
|
||||||
|
return propEqual(copy, arr, `scalar_product([${copy}],${num})`, `[${arr}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"preserves the length of the array",
|
||||||
|
"array number", "number",
|
||||||
|
function (arr,num) {
|
||||||
|
return propEqual(scalar_product(arr,num).length, arr.length, `scalar_product([${arr}],${num}).length`, `${arr}.length`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"returns zero array if the factor is zero",
|
||||||
|
"array number",
|
||||||
|
function (arr) {
|
||||||
|
let z = _.cloneDeep(arr);
|
||||||
|
_.fill(z,0);
|
||||||
|
return propEqual(scalar_product(arr,0),z, `scalar_product([${arr}],0)`, `[${z}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"preserves the array if the factor is one",
|
||||||
|
"array number",
|
||||||
|
function (arr) {
|
||||||
|
let c = _.cloneDeep(arr);
|
||||||
|
return propEqual(scalar_product(arr,1),c, `scalar_product([${arr}],1))`, `[${c}]`);
|
||||||
|
});
|
||||||
|
// have to restrict it to integers because of the rounding errors
|
||||||
|
// better to compare up to rounding error
|
||||||
|
property(
|
||||||
|
"allows factors to be combined",
|
||||||
|
"array integer", "integer", "integer",
|
||||||
|
function (arr, n1, n2) {
|
||||||
|
return propEqual(scalar_product(scalar_product(arr,n1),n2),
|
||||||
|
scalar_product(arr, n1*n2), `scalar_product(scalar_product([${arr}],${n1}),${n2})`, `scalar_product([${arr}], ${n1}*${n2})`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"returns undefined if first argument is not an array",
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isArray(x)), arbs.anything,
|
||||||
|
function(notArr, x) {
|
||||||
|
return propEqual(scalar_product(notArr, x), undefined, `scalar_product(${notArr}, ${x})`, `undefined`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"if factor is not passed, array is returned as is",
|
||||||
|
"array number",
|
||||||
|
function(arr) {
|
||||||
|
return propEqual(scalar_product(arr), arr, `scalar_product([${arr}])`, `[${arr}]`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Inner product", function() {
|
||||||
|
property(
|
||||||
|
"doesn't change the source arrays",
|
||||||
|
arbs.equalLengthArrays(jsc.number, jsc.number),
|
||||||
|
function (arrs) {
|
||||||
|
let [a1,a2] = arrs;
|
||||||
|
let copy1 = _.cloneDeep(a1);
|
||||||
|
let copy2 = _.cloneDeep(a2);
|
||||||
|
var t = inner_product(a1,a2);
|
||||||
|
return propEqual(a1,copy1) && propEqual(a2,copy2);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"is symmetric",
|
||||||
|
arbs.equalLengthArrays(jsc.number, jsc.number),
|
||||||
|
function (arrs) {
|
||||||
|
let [a1,a2] = arrs;
|
||||||
|
return propEqual(inner_product(a1,a2), inner_product(a2,a1), `inner_product([${a1}],[${a2}])`, `inner_product([${a2}],[${a1}])`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"returns non-negative number when multiplying vector to itself",
|
||||||
|
"array number",
|
||||||
|
function (arr) {
|
||||||
|
return inner_product(arr,arr) >= 0;
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"returns 0 if one of the vectors is a zero-filled [0,...,0] vector",
|
||||||
|
"array number",
|
||||||
|
function (arr) {
|
||||||
|
let z = new Array(arr.length);
|
||||||
|
_.fill(z,0);
|
||||||
|
return propEqual(inner_product(arr,z), 0, `inner_product([${arr}],[${z}])`, `0`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"if argument arrays sizes don't match, returns undefined",
|
||||||
|
jsc.array(arbs.anything),
|
||||||
|
function(arr1) {
|
||||||
|
return jsc.forall(
|
||||||
|
jsc.suchthat(jsc.array(arbs.anything),
|
||||||
|
x => x.length !== arr1.length),
|
||||||
|
function(arr2) {
|
||||||
|
return propEqual(inner_product(arr1,arr2), undefined, `inner_product([${arr1}],[${arr2}])` , `undefined`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mapReduce", function() {
|
||||||
|
// mapReduce is pretty agnostic to the array contents
|
||||||
|
// so we could use "json" arbitrary instead of "number"
|
||||||
|
// to get better coverage
|
||||||
|
// Unfortunately, it is also quite slower
|
||||||
|
property(
|
||||||
|
"doesn't change the source array",
|
||||||
|
"array integer", "integer -> integer",
|
||||||
|
function (arr, f) {
|
||||||
|
let copy = _.cloneDeep(arr);
|
||||||
|
let t = mapReduce(copy, f);
|
||||||
|
return propEqual(copy, arr, `[${copy}]`,`[${arr}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"mapReduce([],f,g,seed) == seed",
|
||||||
|
"integer -> integer", arbs.fn2(jsc.integer), "integer",
|
||||||
|
function(f, combine, seed) {
|
||||||
|
return propEqual(mapReduce([],f,combine,seed), seed, `mapReduce([],${f},${combine},${seed})`, `${seed}`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"mapReduce(arr,identity,combine,seed) == reduce(arr,combine,seed)",
|
||||||
|
"array integer", arbs.fn2(jsc.integer), "integer",
|
||||||
|
function (arr, combine, seed) {
|
||||||
|
return propEqual(mapReduce(arr,_.identity,combine,seed),
|
||||||
|
_.reduce(arr,combine,seed), `mapReduce([${arr}],${_.identity},${combine},${seed})`, `_.reduce([${arr}],${combine},${seed})`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"mapReduce(arr,f,combine,seed) == " +
|
||||||
|
"mapReduce(arr,identity,(a,x) => combine(a,f(x)),f(seed))",
|
||||||
|
"array integer", "integer -> integer", arbs.fn2(jsc.integer),
|
||||||
|
"integer",
|
||||||
|
function (arr, f, combine, seed) {
|
||||||
|
return propEqual(
|
||||||
|
mapReduce(arr,f,combine,seed),
|
||||||
|
mapReduce(arr,_.identity,(a,x) => combine(a,f(x)),seed), `mapReduce([${arr}],${f},${combine},${seed})`, `mapReduce([${arr}],${_.identity},(a,x) => ${combine}(a,${f}(x)),${seed})`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"mapReduce(arr1.concat(arr2),f) == mapReduce(arr1,f) + mapReduce(arr2,f)",
|
||||||
|
"array integer", "array integer", "integer -> integer",
|
||||||
|
function (arr1, arr2, f) {
|
||||||
|
return propEqual(
|
||||||
|
mapReduce(arr1.concat(arr2),f),
|
||||||
|
mapReduce(arr1,f) + mapReduce(arr2,f), `mapReduce([${arr1}].concat([${arr2}]),${f})`, `mapReduce([${arr1}],${f}) + mapReduce([${arr2}],${f})`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"mapReduce(arr1.concat(arr2),f,+,0) == " +
|
||||||
|
"mapReduce(arr1,f,+,0) + mapReduce(arr2,f,+,0)",
|
||||||
|
"array integer", "array integer", "integer -> integer",
|
||||||
|
function (arr1, arr2, f) {
|
||||||
|
return propEqual(
|
||||||
|
mapReduce(arr1.concat(arr2),f, _.add, 0),
|
||||||
|
mapReduce(arr1,f,_.add,0) + mapReduce(arr2,f,_.add,0), `mapReduce([${arr1}].concat([${arr2}]),${f}, ${_.add}, 0)`, `mapReduce([${arr1}],${f},${_.add},0) + mapReduce([${arr2}],${f},${_.add},0)`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"mapReduce(arr,f,(a,x) => a.concat([x]),[]) == map(arr,f)",
|
||||||
|
"array integer", "integer -> integer",
|
||||||
|
function (arr, f) {
|
||||||
|
return propEqual(mapReduce(arr,f,(a,x) => a.concat([x]), []),
|
||||||
|
_.map(arr,f), `mapReduce([${arr}],${f},(a,x) => a.concat([x]), [])`, `${_.map(arr,f)}`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"mapReduce(arr,f,(a,x) => a.concat(x),[]) == flatMap(arr,f)",
|
||||||
|
"array integer", "integer -> array integer",
|
||||||
|
function (arr,f) {
|
||||||
|
return propEqual(mapReduce(arr,f,(a,x) => a.concat(x),[]),
|
||||||
|
_.flatMap(arr,f), `mapReduce([${arr}],${f},(a,x) => a.concat(x),[])`,
|
||||||
|
`[${_.flatMap(arr,f)}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"mapReduce([1,2,3],f,g,0) == g(g(g(0,f(1)),f(2)),f(3))",
|
||||||
|
"integer -> integer", arbs.fn2(jsc.integer),
|
||||||
|
function (f, g) {
|
||||||
|
return propEqual(mapReduce([1,2,3],f,g,0),
|
||||||
|
g(g(g(0,f(1)),f(2)),f(3)), `mapReduce([1,2,3],${f},${g},0)`, `${g(g(g(0,f(1)),f(2)),f(3))}`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"if the first argument is not an array, returns undefined",
|
||||||
|
jsc.suchthat(arbs.anything, x => !Array.isArray(x)),
|
||||||
|
"number -> number",
|
||||||
|
function(notArr, f) {
|
||||||
|
return propEqual(mapReduce(notArr,f), undefined);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"if the second argument is not a function, returns undefined",
|
||||||
|
"array integer", jsc.suchthat(arbs.anything, x => typeof x !== 'function'),
|
||||||
|
function(arr, notF) {
|
||||||
|
return propEqual(mapReduce(arr,notF), undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mergeSortedArrays", function() {
|
||||||
|
property(
|
||||||
|
"doesn't change the source arrays",
|
||||||
|
arbs.sortedArray(jsc.integer), arbs.sortedArray(jsc.integer),
|
||||||
|
function(a, b) {
|
||||||
|
let copy1 = _.cloneDeep(a);
|
||||||
|
let copy2 = _.cloneDeep(b);
|
||||||
|
let t = mergeSortedArrays(copy1,copy2);
|
||||||
|
return propEqual(copy1,a) && propEqual(copy2,b);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"produces sorted results",
|
||||||
|
arbs.sortedArray(jsc.integer), arbs.sortedArray(jsc.integer),
|
||||||
|
function(a, b) {
|
||||||
|
let t = mergeSortedArrays(a,b);
|
||||||
|
return propEqual(t, _.sortBy(t), `[${t}]`,`mergeSortedArrays([${a}],[${b}])`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"has length equal to the sum of individual lengths",
|
||||||
|
arbs.sortedArray(jsc.integer), arbs.sortedArray(jsc.integer),
|
||||||
|
function(a, b) {
|
||||||
|
return propEqual(mergeSortedArrays(a,b).length, a.length + b.length, `mergeSortedArrays([${a}],[${b}]).length`, `[${a}].length + [${b}].length`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("range", function() {
|
||||||
|
property(
|
||||||
|
"results in undefined if step is zero",
|
||||||
|
"integer", "integer",
|
||||||
|
function(x,y) {
|
||||||
|
return propEqual(range(x,y,0), undefined, `range(${x},${y},0)`, undefined, `[${range(x,y,0)}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"results in empty array if x > y and step > 0 or x < y and step < 0",
|
||||||
|
"integer",
|
||||||
|
function(x) {
|
||||||
|
return jsc.forall(
|
||||||
|
jsc.suchthat(jsc.integer, y => y != x), arbs.posInteger,
|
||||||
|
function(y,stepMod) {
|
||||||
|
let s = x>y ? stepMod : -stepMod
|
||||||
|
return propEqual(range(x,y,s), [], `range(${x},${y},${s})`, undefined, `[${range(x,y,s)}]`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"produces array without repetitions",
|
||||||
|
"integer", "integer", jsc.suchthat(jsc.integer, x => x != 0),
|
||||||
|
function(x,y,step) {
|
||||||
|
let r = range(x,y,step);
|
||||||
|
return propEqual(r.length, _.uniq(r).length, `range(${x},${y},${step})`, undefined, `[${range(x,y,step)}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"produces array with all elements >= min(x,y)",
|
||||||
|
"integer", "integer", jsc.suchthat(jsc.integer, x => x != 0),
|
||||||
|
function(x,y,step) {
|
||||||
|
let r = range(x,y,step);
|
||||||
|
let l = _.min([x,y]);
|
||||||
|
return propEqual(_.every(r,a => a >= l), true, `range(${x},${y},${step})`, undefined, `[${range(x,y,step)}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"produces array with all elements <= max(x,y)",
|
||||||
|
"integer", "integer", jsc.suchthat(jsc.integer, x => x != 0),
|
||||||
|
function(x,y,step) {
|
||||||
|
let r = range(x,y,step);
|
||||||
|
let h = _.max([x,y]);
|
||||||
|
return propEqual(_.every(r,a => a <= h), true, `range(${x},${y},${step})`, undefined, `[${range(x,y,step)}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"returns undefined, if either argument is not a number",
|
||||||
|
"number", "number", jsc.suchthat(jsc.number, x => x != 0),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isNumber(x) || _.isNaN(x)),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isNumber(x) || _.isNaN(x)),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isNumber(x) || _.isNaN(x)),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isUndefined(x) &&
|
||||||
|
(!_.isNumber(x) || _.isNaN(x))),
|
||||||
|
function(x,y,step,nN1,nN2,nN3,nN4) {
|
||||||
|
return propEqual(range(nN1,nN2,step), undefined) &&
|
||||||
|
propEqual(range(nN1,y,step), undefined) &&
|
||||||
|
propEqual(range(x,nN2,step), undefined) &&
|
||||||
|
propEqual(range(nN1,nN2,nN3), undefined) &&
|
||||||
|
propEqual(range(x,y,nN4), undefined) &&
|
||||||
|
propEqual(range(nN1,y,nN3), undefined) &&
|
||||||
|
propEqual(range(x,nN2,nN3), undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("flatten", function() {
|
||||||
|
property(
|
||||||
|
"doesn't change the input array",
|
||||||
|
"array json",
|
||||||
|
function(a) {
|
||||||
|
let copy = _.cloneDeep(a);
|
||||||
|
let t = flatten(copy);
|
||||||
|
return propEqual(copy, a, `flatten([${a}])`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"simply returns the input, if it is not an array",
|
||||||
|
jsc.oneof(jsc.number, jsc.bool, jsc.falsy, jsc.string),
|
||||||
|
function(inp) {
|
||||||
|
return propEqual(flatten(inp), inp, `flatten(${inp})`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"simply returns the input, if it is already flattened",
|
||||||
|
jsc.array(jsc.oneof(jsc.number, jsc.bool, jsc.falsy, jsc.string)),
|
||||||
|
function(a) {
|
||||||
|
return propEqual(flatten(a), a, `flatten([${a}])`, `[${a}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"flatten([arr]) == flatten(arr)",
|
||||||
|
"array json",
|
||||||
|
function(a) {
|
||||||
|
return propEqual(flatten([a]), flatten(a), `flatten([[${a}]])`, `flatten([${a}])`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mkPrettyPrinter", function() {
|
||||||
|
property(
|
||||||
|
"output has no too long lines",
|
||||||
|
jsc.integer(20,80), jsc.array(sentence),
|
||||||
|
function(ls, input) {
|
||||||
|
let inp = input.join('\n');
|
||||||
|
let pp = mkPrettyPrinter(ls);
|
||||||
|
let r = pp(inp);
|
||||||
|
return propEqual(_.every(r, l => l.length <= ls), true, `mkPrettyPrinter(${ls})("${inp.replace(/\n/, '\\n')}")`, undefined, `[${mkPrettyPrinter(ls)(inp)}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"output has all input words in the same order",
|
||||||
|
jsc.integer(20,80), jsc.array(sentence),
|
||||||
|
function(ls, input) {
|
||||||
|
let inp = input.join('\n');
|
||||||
|
let ws = words(inp);
|
||||||
|
let pp = mkPrettyPrinter(ls);
|
||||||
|
let r = pp(inp);
|
||||||
|
return propEqual(words(r.join('\n')), ws, `mkPrettyPrinter(${ls})("${inp.replace(/\n/, '\\n')}")`, undefined);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"output has no leading or trailing white spaces",
|
||||||
|
jsc.integer(20,80), jsc.array(sentence),
|
||||||
|
function(ls, input) {
|
||||||
|
let inp = input.join('\n');
|
||||||
|
let pp = mkPrettyPrinter(ls);
|
||||||
|
let r = pp(inp);
|
||||||
|
return propEqual(_.every(r, l => (l == _.trim(l))), true, `mkPrettyPrinter(${ls})("${inp.replace(/\n/, '\\n')}")`, undefined, `[${mkPrettyPrinter(ls)(inp)}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"does not use global variables",
|
||||||
|
arbs.vect(jsc.integer(20,80),5),
|
||||||
|
arbs.permutation(5), jsc.array(sentence),
|
||||||
|
function(lss, perm, input) {
|
||||||
|
let inp = input.join('\n');
|
||||||
|
let pps = lss.map(mkPrettyPrinter);
|
||||||
|
for (let i in perm) {
|
||||||
|
let j = perm[i];
|
||||||
|
let r = pps[j](inp);
|
||||||
|
_.every(r, l => propEqual(l.length <= lss[j], true));
|
||||||
|
pps.splice(j,1);
|
||||||
|
lss.splice(j,1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"returns undefined if called with not a number",
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isUndefined(x) &&
|
||||||
|
(!_.isNumber(x) || _.isNaN(x))),
|
||||||
|
function(nN) {
|
||||||
|
return propEqual(mkPrettyPrinter(nN), undefined, `mkPrettyPrinter(${nN})`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"if pretty-printer is called with non-string, it returns undefined",
|
||||||
|
jsc.integer(50,80),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isString(x)),
|
||||||
|
function(ls, nStr) {
|
||||||
|
return propEqual(mkPrettyPrinter(ls)(nStr), undefined, `mkPrettyPrinter(${ls})(${nStr})`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mkIndenter", function() {
|
||||||
|
let indenterInput = jsc.oneof(jsc.nat(20), sentence, jsc.array(sentence));
|
||||||
|
// it's easier to generate absolute indentation levels
|
||||||
|
// and then turn them into relative ones
|
||||||
|
function fixIndentationLevels(arr,curLvl) {
|
||||||
|
for (let i in arr) {
|
||||||
|
if (typeof arr[i] == "number") {
|
||||||
|
let t = arr[i];
|
||||||
|
arr[i] -= curLvl;
|
||||||
|
curLvl = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curLvl;
|
||||||
|
}
|
||||||
|
function properlyIndented(il) {
|
||||||
|
return l => {
|
||||||
|
let b = l.substr(0,il);
|
||||||
|
let e = l.slice(il);
|
||||||
|
return propEqual(b, ' '.repeat(il)) &&
|
||||||
|
propEqual(e, _.trim(e)) &&
|
||||||
|
! _.includes(e, '\n') &&
|
||||||
|
! _.includes(e, '\t') &&
|
||||||
|
! _.includes(e, ' ');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function properlyIndentedLI(il) {
|
||||||
|
return (l,ix) => {
|
||||||
|
let b = l.substr(0,il+2);
|
||||||
|
let e = l.slice(il+2);
|
||||||
|
let expectedIndent = i ? ' '.repeat(il+2) : ' '.repeat(il) + '* ';
|
||||||
|
return propEqual(b, expectedIndent) &&
|
||||||
|
propEqual(e, _.trim(e)) &&
|
||||||
|
! _.includes(e, '\n') &&
|
||||||
|
! _.includes(e, '\t') &&
|
||||||
|
! _.includes(e, ' ');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function noEarlyWraps(ls, ss, rl, el, ol) {
|
||||||
|
let lss = _.initial(ss).map(x => x.length);
|
||||||
|
let nexts = _.tail(ss).map(x => words(x)[0].length);
|
||||||
|
return propEqual(_.every(_.zipWith(lss,nexts,_.add), s => s >= ls),true,rl,el,ol);
|
||||||
|
}
|
||||||
|
|
||||||
|
property(
|
||||||
|
"returned function doesn't change its argument",
|
||||||
|
jsc.integer(50,80), jsc.nat(12), jsc.array(sentence),
|
||||||
|
function(ls, il, arr) {
|
||||||
|
let copy = _.cloneDeep(arr);
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
let t = i(copy);
|
||||||
|
return propEqual(copy,arr, `mkIndenter(${ls}, ${il})([${arr}])`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"freshly created indenter returns indentation level passed " +
|
||||||
|
"during creation",
|
||||||
|
jsc.integer(50,80), jsc.nat(20),
|
||||||
|
function(ls, il) {
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
return propEqual(i(true), il, `mkIndenter(${ls}, ${il})(true)`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"freshly created indenter returns an empty array",
|
||||||
|
jsc.integer(50,80), jsc.nat(20),
|
||||||
|
function(ls, il) {
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
return propEqual(i(), [], `mkIndenter(${ls}, ${il})()`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"content request doesn't change the indentation level",
|
||||||
|
jsc.integer(50,80), jsc.nat(20), jsc.array(indenterInput),
|
||||||
|
function(ls, il, arr) {
|
||||||
|
fixIndentationLevels(arr,il);
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
arr.forEach(i);
|
||||||
|
let il2 = i(true);
|
||||||
|
i();
|
||||||
|
return propEqual(i(true), il2, `var i = mkIndenter(${ls}, ${il});i(true);i(true)`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"output doesn't have too long lines",
|
||||||
|
jsc.integer(50,80), jsc.nat(20), jsc.array(indenterInput),
|
||||||
|
function(ls, il, arr) {
|
||||||
|
fixIndentationLevels(arr,il);
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
arr.forEach(i);
|
||||||
|
let r = i();
|
||||||
|
return propEqual(_.every(r, l => l.length <= ls), true, `mkIndenter(${ls}, ${il})()`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"integer input doesn't produce any output and changes the " +
|
||||||
|
"indentation level",
|
||||||
|
jsc.integer(50,80), jsc.nat(12), jsc.integer(-12,12),
|
||||||
|
function(ls, il, int) {
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
i(int);
|
||||||
|
return propEqual(i(true), il + int, `var i = mkIndenter(${ls}, ${il}); i(${int}); i(true); i()`) &&
|
||||||
|
propEqual(i(), [], `var i = mkIndenter(${ls}, ${il}); i(${int}); i(true); i()`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"string input produces properly indented results " +
|
||||||
|
"and doesn't change the indentation level",
|
||||||
|
jsc.integer(50,80), jsc.nat(20), sentence,
|
||||||
|
function(ls, il, s) {
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
i(s);
|
||||||
|
let r = i();
|
||||||
|
return propEqual(_.every(r, properlyIndented(il)), true, `var i = mkIndenter(${ls}, ${il}); i("${s}"); i(); i(true)`) &&
|
||||||
|
propEqual(i(true), il, `var i = mkIndenter(${ls}, ${il}); i("${s}"); i(); i(true)`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"string input doesn't wrap lines too early",
|
||||||
|
jsc.integer(50,80), jsc.nat(20), sentence,
|
||||||
|
function(ls, il, s) {
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
i(s);
|
||||||
|
let r = i();
|
||||||
|
return noEarlyWraps(ls, r, `var i = mkIndenter(${ls}, ${il}); i("${s.replace(/\n/, '\\n')}"); i()`, undefined, `[${i()}]`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"empty array input does nothing",
|
||||||
|
jsc.integer(50,80), jsc.nat(20),
|
||||||
|
function(ls, il) {
|
||||||
|
let i = mkIndenter(ls, il);
|
||||||
|
i([]);
|
||||||
|
return propEqual(i(), [], `var i = mkIndenter(${ls}, ${il}); i([]); i(); i(true)`) && propEqual(i(true), il, `var i = mkIndenter(${ls}, ${il}); i([]); i(); i(true)`);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"it doesn't use global variables",
|
||||||
|
jsc.integer(2,5),
|
||||||
|
function(n) {
|
||||||
|
let inputsArb = arbs.vect(jsc.tuple([jsc.integer(50,80),
|
||||||
|
jsc.nat(20),
|
||||||
|
jsc.array(sentence)]),
|
||||||
|
n);
|
||||||
|
return jsc.forall(
|
||||||
|
inputsArb,
|
||||||
|
function(inputs) {
|
||||||
|
let seqOuts = _.map(inputs, inp => {
|
||||||
|
let i = mkIndenter(inp[0],inp[1]);
|
||||||
|
inp[2].forEach(i);
|
||||||
|
return i();
|
||||||
|
});
|
||||||
|
let sz = _.sum(_.map(inputs, x => x[2].length)) + 2*n;
|
||||||
|
return jsc.forall(
|
||||||
|
arbs.vect(jsc.nat(n-1), sz),
|
||||||
|
function(schedule) {
|
||||||
|
let cnts = _.fill(Array(n), -1);
|
||||||
|
let outs = _.fill(Array(n), undefined);
|
||||||
|
let is = [];
|
||||||
|
schedule.forEach(i_ => {
|
||||||
|
let i = i_;
|
||||||
|
for (; cnts[i] > inputs[i][2].length; i++, i %= n);
|
||||||
|
let inp = inputs[i];
|
||||||
|
if (cnts[i] == -1)
|
||||||
|
is[i] = mkIndenter(inp[0], inp[1]);
|
||||||
|
else if (cnts[i] == inp[2].length)
|
||||||
|
outs[i] = is[i]();
|
||||||
|
else
|
||||||
|
is[i](inp[2][cnts[i]]);
|
||||||
|
cnts[i]++;
|
||||||
|
});
|
||||||
|
let os = _.zip(seqOuts, outs);
|
||||||
|
return _.every(os, o => propEqual(o[0], o[1]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"returns undefined if either argument is not a number",
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isNumber(x) || _.isNaN(x)),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isUndefined(x) &&
|
||||||
|
(!_.isNumber(x) || _.isNaN(x))),
|
||||||
|
jsc.integer(50,80), jsc.nat(20),
|
||||||
|
function(nN1, nN2, ls, il) {
|
||||||
|
return propEqual(mkIndenter(nN1), undefined) &&
|
||||||
|
propEqual(mkIndenter(nN1,il), undefined) &&
|
||||||
|
propEqual(mkIndenter(ls,nN2), undefined) &&
|
||||||
|
propEqual(mkIndenter(nN1,nN2), undefined);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"sample input works",
|
||||||
|
"unit",
|
||||||
|
function(u) {
|
||||||
|
let i = mkIndenter(20);
|
||||||
|
i("Short line");
|
||||||
|
i("This line is longer than 20 characters");
|
||||||
|
i(8);
|
||||||
|
i("Big indent");
|
||||||
|
i("Not so long line");
|
||||||
|
i(-6);
|
||||||
|
i("Small indent");
|
||||||
|
i(["Item 1", "Item 2", "Really really long Item 3"]);
|
||||||
|
return propEqual(i(),
|
||||||
|
[ 'Short line',
|
||||||
|
'This line is longer',
|
||||||
|
'than 20 characters',
|
||||||
|
' Big indent',
|
||||||
|
' Not so long',
|
||||||
|
' line',
|
||||||
|
' Small indent',
|
||||||
|
' * Item 1',
|
||||||
|
' * Item 2',
|
||||||
|
' * Really really',
|
||||||
|
' long Item 3' ]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("letter_frequency", function() {
|
||||||
|
property(
|
||||||
|
"Concatenation of strings produces merge of maps",
|
||||||
|
jsc.array(arbs.letter), jsc.array(arbs.letter),
|
||||||
|
function(arr1, arr2) {
|
||||||
|
let s1 = arr1.join('');
|
||||||
|
let s2 = arr2.join('');
|
||||||
|
let expected = _.assignWith(letter_frequency(s1),
|
||||||
|
letter_frequency(s2),
|
||||||
|
_.add);
|
||||||
|
return propEqual(letter_frequency(s1 + s2), expected);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"Map values are always positive",
|
||||||
|
jsc.array(arbs.letter),
|
||||||
|
function(arr) {
|
||||||
|
let s = arr.join('');
|
||||||
|
let r = letter_frequency(s);
|
||||||
|
return _.every(r, x => x > 0);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"Hello example works",
|
||||||
|
"unit",
|
||||||
|
function(u) {
|
||||||
|
let a = []; a.H = 1; a.E = 1; a.L = 2; a.O = 1;
|
||||||
|
let expected = {'H' : 1,'E' : 1,'L' : 2,'O' : 1}
|
||||||
|
|
||||||
|
try {
|
||||||
|
propEqual(letter_frequency('Hello'), expected)
|
||||||
|
} catch(e) {
|
||||||
|
return propEqual(letter_frequency('Hello'),a)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
propEqual(letter_frequency('Hello'),a)
|
||||||
|
} catch(e) {
|
||||||
|
return propEqual(letter_frequency('Hello'), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("display_letter_frequency", function() {
|
||||||
|
// how to generate frequency table
|
||||||
|
let fTable = jsc.array(jsc.tuple([arbs.uLetter, arbs.posInteger])).smap(
|
||||||
|
_.fromPairs,
|
||||||
|
_.toPairs);
|
||||||
|
|
||||||
|
property(
|
||||||
|
"first arguments is not modified",
|
||||||
|
fTable,
|
||||||
|
function(ft) {
|
||||||
|
let copy = _.cloneDeep(ft);
|
||||||
|
let dom = {};
|
||||||
|
display_letter_frequency(copy, dom);
|
||||||
|
return propEqual(copy, ft);
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"Output is a table with right number of rows and 2 columns",
|
||||||
|
fTable,
|
||||||
|
function(ft) {
|
||||||
|
let dom = {};
|
||||||
|
display_letter_frequency(ft, dom);
|
||||||
|
let out = xmlParser.parseFromString(dom.innerHTML, 'text/xml');
|
||||||
|
let d = out.documentElement;
|
||||||
|
let cs = d.children;
|
||||||
|
return propEqual(d.tagName, 'table') &&
|
||||||
|
propEqual(cs.length, _.size(ft)) &&
|
||||||
|
_.every(_.range(cs.length).map(i => {
|
||||||
|
let c = cs.item(i);
|
||||||
|
let subcs = c.children;
|
||||||
|
return propEqual(c.tagName, 'tr') &&
|
||||||
|
propEqual(subcs.length, 2) &&
|
||||||
|
_.every(_.range(subcs.length).map(j => {
|
||||||
|
let subc = subcs.item(j);
|
||||||
|
return propEqual(subc.tagName, 'td') &&
|
||||||
|
propEqual(subc.children.length, 0);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"Every row has key/value pair from the frequency table",
|
||||||
|
fTable,
|
||||||
|
function(ft) {
|
||||||
|
function isInMap(k,v) {
|
||||||
|
let i = parseInt(v);
|
||||||
|
return propEqual(i + '', v) &&
|
||||||
|
propEqual(ft[k], i);
|
||||||
|
}
|
||||||
|
let dom = {};
|
||||||
|
display_letter_frequency(ft, dom);
|
||||||
|
let out = xmlParser.parseFromString(dom.innerHTML, 'text/xml');
|
||||||
|
let d = out.documentElement;
|
||||||
|
let cs = d.children;
|
||||||
|
return propEqual(d.tagName, 'table') &&
|
||||||
|
_.every(_.range(cs.length).map(i => {
|
||||||
|
let c = cs.item(i);
|
||||||
|
let subcs = c.children;
|
||||||
|
return propEqual(c.tagName, 'tr') &&
|
||||||
|
propEqual(subcs.length,2) &&
|
||||||
|
propEqual(subcs.item(0).tagName, 'td') &&
|
||||||
|
propEqual(subcs.item(1).tagName, 'td') &&
|
||||||
|
isInMap(subcs.item(0).innerHTML, subcs.item(1).innerHTML);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
property(
|
||||||
|
"Does nothing if either argument is not an object",
|
||||||
|
fTable,
|
||||||
|
jsc.dict(arbs.anything),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isObject(x)),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isObject(x)),
|
||||||
|
jsc.suchthat(arbs.anything, x => !_.isObject(x)),
|
||||||
|
function(ft, nO1, nO2, nO3, o) {
|
||||||
|
let copyO = _.cloneDeep(o);
|
||||||
|
let copyNO2 = _.cloneDeep(nO2);
|
||||||
|
let copyNO3 = _.cloneDeep(nO3);
|
||||||
|
display_letter_frequency(nO1, copyNO2);
|
||||||
|
display_letter_frequency(nO1, copyO);
|
||||||
|
display_letter_frequency(ft, copyNO3);
|
||||||
|
return propEqual(copyNO2, nO2) &&
|
||||||
|
propEqual(copyO, o) &&
|
||||||
|
propEqual(copyNO3, nO3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Reference in a new issue