/** * 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') { let words = mkPrettyPrinter(line_size - level)(param); words = words.map(e => ' '.repeat(level) + e); state.push(...words); } else if (Array.isArray(param)) { for (const e of param) { if (typeof e !== 'string') { return; } const words = mkPrettyPrinter(line_size - level - 2)(e); state.push(' '.repeat(level) + '* ' + words[0]); for (let i = 1; i < words.length; i++) { state.push(' '.repeat(level + 2) + 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) { if (typeof s !== 'string') { return; } const a = []; Array.from(s.toLowerCase()).forEach(c => a[c] = !a[c] ? 1 : a[c] + 1); return a; } /** * 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) { if (typeof a !== 'object' || typeof dom !== 'object' || !dom) { return; } const t = document.createElement('table'); for (const x in a) { const r = document.createElement('tr'); const letter = document.createElement('td'); const element = document.createElement('td'); r.appendChild(letter); r.appendChild(element); letter.innerText = x; element.innerText = '' + a[x]; t.appendChild(r); } dom.innerHTML = t.outerHTML; } /** * 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) { const a = letter_frequency(inputEl.value); display_letter_frequency(a, document.getElementById('frequency_table')); } /** * JavaScript Exercise 3 */ function clock() { const SECOND = 1000; const MINUTE = SECOND * 60; const HOUR = MINUTE * 60; const digit = d => (d < 10 ? '0' : '') + d; const format = (h, m, s) => digit(h) + ':' + digit(m) + ':' + digit(s); function dateString(date) { const epoch = Number.isInteger(date); const h = epoch ? Math.floor(date / HOUR) : date.getHours(); const m = epoch ? Math.floor((date % HOUR) / MINUTE) : date.getMinutes(); const s = epoch ? Math.floor((date % MINUTE) / SECOND) : date.getSeconds(); return format(h, m, s); } const c = document.getElementById('clock'); let timer = false; setInterval(() => { d = new Date(); c.innerHTML = dateString(timer ? d - timer : d); }, SECOND / 10); return _ => timer = timer ? false : new Date(); }