diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..89c281e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,21 @@ +module.exports = { + "extends": "airbnb-base", + "env": { + "node":true, + "mocha": true, + "es6": true, + }, + "globals": { + "expect": true, + "sinon": true, + }, + "plugins": [ + "import" + ], + "rules": { + "no-underscore-dangle": 0, + }, + "parserOptions": { + "ecmaVersion": 13, + }, + }; \ No newline at end of file diff --git a/Solutions/Bltzz_solution.js b/Solutions/Bltzz_solution.js new file mode 100644 index 0000000..0eae984 --- /dev/null +++ b/Solutions/Bltzz_solution.js @@ -0,0 +1,1385 @@ +/* + * @author Bltzz + * +*/ + +function _check(x, y) { + if (typeof x !== 'number') { throw new TypeError(`${x} is not a number`); } + if (typeof y !== 'number') { throw new TypeError(`${y} is not a number`); } +} + +function _checkSingle(x) { + if (typeof x !== 'number' || x === undefined) { throw new TypeError(`${x} is not a number`); } +} + +function _checkArray(...nums) { + if (nums.length === 0 || nums === undefined) { + throw new Error('No arguments provided.'); + } + nums.forEach(x => _checkSingle(x)); +} + +function _checkFunction(func) { + if (typeof func !== 'function') { throw new TypeError('Input must be a function'); } +} + +/** + * identity(x) ⇒ any + * @param {*} x - Any value + * @returns {*} - The same value + */ +function identity(x) { + return x; +} + +/** + * addb(a, b) ⇒ number + * @param {*} x - Any value + * @param {*} y - Any value + * @returns {*} - x plus y + */ +function addb(a, b) { + _check(a, b); + return a + b; +} + +/** + * subb(a, b) ⇒ number + * @param {*} x - Any value + * @param {*} y - Any value + * @returns {*} - x minus y + */ +function subb(a, b) { + _check(a, b); + return a - b; +} + +/** + * mulb(a, b) ⇒ number + * @param {*} x - Any value + * @param {*} y - Any value + * @returns {*} - x times y + */ +function mulb(a, b) { + _check(a, b); + return a * b; +} + +/** + * minb(a, b) ⇒ number + * @param {*} x - Any value + * @param {*} y - Any value + * @returns {*} - The smaller value + */ +function minb(a, b) { + _check(a, b); + return a < b ? a : b; +} + +/** + * maxb(a, b) ⇒ number + * @param {*} x - Any value + * @param {*} y - Any value + * @returns {*} - The bigger value + */ +function maxb(a, b) { + _check(a, b); + return a > b ? a : b; +} + +/** + * add(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The sum of all the numeric values + */ +function add(...nums) { + _checkArray(...nums); + // Use build-in reduce function + return nums.reduce((sum, num) => sum + num, 0); + + // Alternative: + // let sum = 0; + // for (let i = 0; i < nums.length; i++) sum += nums[i]; + // return sum; +} + +/** + * sub(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The result of subtracting all the numeric values + * - taking first element as starting point + */ +function sub(...nums) { + _checkArray(...nums); + // let result = nums[0]; + // for (let i = 1; i < nums.length; i++) result -= nums[i]; + // Alternative: + // Use built-in reduce function + return nums.reduce((result, num) => result - num); + // return result; +} + +/** + * mul(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The result of mulitplying all the numeric values + */ +function mul(...nums) { + _checkArray(...nums); + // let result = 1; + // for (let i = 0; i < nums.length; i++) result *= nums[i]; + // Alternative: + // Use built-in reduce function + return nums.reduce((result, num) => result * num); + // return result; +} + +/** + * min(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The smallest of the numeric values + */ +function min(...nums) { + _checkArray(...nums); + // let result = nums[0]; + // for (let i = 0; i < nums.length; i++) (result > nums[i + 1]) ? result = nums[i + 1] : result; + // Alternative: + // Use built-in Math.min function + return Math.min(...nums); + // return result; +} + +/** + * max(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The biggest of the numeric values + */ +function max(...nums) { + _checkArray(...nums); + // let result = nums[0]; + // for (let i = 0; i < nums.length; i++) (result < nums[i + 1]) ? result = nums[i + 1] : result; + // Alternative: + // Use built-in Math.max function + return Math.max(...nums); + // return result; +} + +/** + * addRecurse(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The sum of the numeric values + */ +function addRecurse(...nums) { + nums.forEach(x => _checkSingle(x)); + if (nums.length === 0) { return 0; } + return nums[0] + addRecurse(...nums.slice(1)); +} + +/** + * mulRecurse(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The product of the numeric values + */ +function mulRecurse(...nums) { + nums.forEach(x => _checkSingle(x)); + if (nums.length === 0) { return 1; } + return nums[0] * mulRecurse(...nums.slice(1)); +} + +/** + * minRecurse(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The smallest of the numeric values + */ +function minRecurse(...nums) { + nums.forEach(x => _checkSingle(x)); + // Base case: return -Infinity if no arguments are provided + if (nums.length === 0) return Infinity; + // Base case: return the single value if only one argument is provided + if (nums.length === 1) return nums[0]; + return nums[0] < minRecurse(...nums.slice(1)) ? nums[0] : minRecurse(...nums.slice(1)); +} + +/** + * maxRecurse(...nums) ⇒ number + * @param {...number} nums - Any number of numeric values + * @returns {number} - The biggest of the numeric values + */ +function maxRecurse(...nums) { + nums.forEach(x => _checkSingle(x)); + // Base case: return -Infinity if no arguments are provided + if (nums.length === 0) return -Infinity; + // Base case: return the single value if only one argument is provided + if (nums.length === 1) return nums[0]; + // Recursive case: compare the first number with the maximum of the rest + return nums[0] > maxRecurse(...nums.slice(1)) ? nums[0] : maxRecurse(...nums.slice(1)); +} + +/** + * not(func) ⇒ function + * @param {func} function - Any function + * @returns {func} - The inverse res of the function + */ +function not(func) { + _checkFunction(func); + return function inverseFunc(...args) { + return !func(...args); + }; +} + +/** + * acc(func, initial) ⇒ function + * @param {func} function - Any function + * @returns {func} - returns a number that runs the initial function + * on each argument, accumulating the result + */ +function acc(func, initial) { + _checkFunction(func); + _checkSingle(initial); + return function accumFunc(...args) { + return args.reduce((accumulator, current) => func(accumulator, current), initial); + }; + // return function accumFunc(...args) { + // let x = initial; + // for (const i of args) { + // x = func(x, i); + // } + // return x; + // }; +} + +/** + * accPartial(func, start, end) ⇒ function + * @param {func} function - Any function + * @returns {func} - returns a function that accumulates a subset of its arguments + * by applying the given function to + * all elements between start and end incl. the stuff before start and after end + */ +function accPartial(func, start, end) { + _checkFunction(func); + _checkArray(start, end); + return function rangeAppliedFunc(...args) { + const subset = args.slice(start, end); + // return args.slice(0, start) + func(...subset) + args.slice(end, args.length); + return [...args.slice(0, start), func(...subset), ...args.slice(end)]; + }; +} + +/** + * accRecurse(func, initial) ⇒ function + * @param {func} function - Any function + * @returns {func} - Same as acc, but recursively + */ +function accRecurse(func, initial) { + _checkFunction(func); + _checkArray(initial); + return function rangeAppliedRecurseFunc(...args) { + if (args.length === 0) { + return initial; + } + const first = args[0]; + const rest = args.slice(1); + // same as + // const [first, ...rest] = args; + const result = func(initial, first); + + return acc(func, result)(...rest); + }; +} + +/** + * fill(num) ⇒ array + * @param {num} function - Any number + * @returns {Array} - An num long array filled with num + */ +function fill(num) { + _checkSingle(num); + return Array(num).fill(num); +} + +/** + * fillRecurse(num) ⇒ array +* @param {num} function - Any number + * @returns {Array} - An num long array filled with num + */ +function fillRecurse(num, result = []) { + _checkSingle(num); + // Break Condition: if num is 0, return the result array + if (num === 0) { + return result; + } + // Add the given number to the result array + // as we decrease the number to make recursion possible, + // add length of the result to always add the same number. + result.push(num + result.length); + + // Call fillRecurse recursively with num - 1 + return fillRecurse(num - 1, result); +} + +/** + * set(...args) ⇒ array + * @param {num} function - Any number + * @returns {Array} - An num long array filled with num + */ +function set(...args) { + _checkArray(...args); + return Array.from(new Set(args)); +} + +/** + * identityf(x) ⇒ function + * @param {x} - Anything + * @returns {function(x)} - A function that returns x + */ +function identityf(x) { + return function innerIdentityf() { return x; }; +} + +/** + * addf(a) ⇒ function + * @param {a} - Any number + * @returns {function} - A function that adds from two invocations + */ +function addf(a) { + _checkSingle(a); + return function innerAdd(b) { + _checkSingle(b); + return a + b; + }; +} + +/** + * liftf(binary) ⇒ function + * @param {binary}function - Any binary function + * @returns {Array} - An num long array filled with num + */ +function liftf(binary) { + _checkFunction(binary); + return function firstOperandFunc(a) { + _checkSingle(a); + return function secondOperandFunc(b) { + _checkSingle(b); + return binary(a, b); + }; + }; +} + +/** + * pure(x, y) ⇒ array + * @param {x} - Any number + * @param {y} - Any number + * @returns {Array} - An num long array filled with num + */ +function pure(x, y) { + _check(x, y); + let z; + function impure(a) { + y += 1; // this generates a lint warning - but I cannot fix it as this was given! + z = a * y; + } + impure(x); + return [y, z]; +} + +/** + * curryb(binary, a) ⇒ function + * @param {binary} function - Any number + * @param {a} - Any number + * @returns {function} - returns a function that can take a second argument + * + * */ +function curryb(binary, a) { + _checkFunction(binary); + return function innercurryb(b) { + return binary(a, b); + }; +} + +/** + * curry(func, ...outer) ⇒ function + * @param {func} function - Any function + * @param {...outer} - Any list of args + * @returns {function} - returns a function that can take any list of args + * + * */ +function curry(func, ...outer) { + return function innerCurry(...other) { + return func(...outer.concat(other)); + }; +} + + +/** + * inc(x) ⇒ number + * Without writting any new functions, show multiple ways to create the inc function + * + * Other options: + * 1. + * const inc = x => x + 1; + * 2. + * const inc = function(x) { + * return x + 1; + * }; + * + * @param {x} - Any number + * @returns {x + 1} - the stuff they asked for (x + 1). On nested calls: x + #of nests + * + * */ +function inc(x) { + return x + 1; +} + +/** + * twiceUnary(binary) ⇒ function + * + * @param {binary} - Any binary function + * @returns {function} - a unary function that passes its argument to the binary function twice + * + * */ +function twiceUnary(binary) { + return function innerUnaryFunc(b) { + return binary(b, b); + }; +} + +/** + * doubl(x) ⇒ number + * + * @param {x} - Any number + * @returns {function} - Use the function twiceUnary to return the double of x + * + * */ +function doubl(x) { + _checkSingle(x); + return twiceUnary(add)(x); +} + +/** + * square(x) ⇒ number + * + * @param {x} - Any number + * @returns {function} - Use the function twiceUnary to return x square + * + * */ +function square(x) { + _checkSingle(x); + return twiceUnary(mul)(x); +} + +/** + * twice(x) ⇒ any + * + * @param {x} function - the add function + * @returns {function} - Use the function twiceUnary to return x square + * + * */ +function twice(x) { + return function innerDuplicate(...args) { + return 2 * x(...args); + }; +} + + +/** + * reverseb(binary) ⇒ function + * + * @param {binary} function - any binary function + * @returns {function} - Use the function twiceUnary to return x square + * + * */ +function reverseb(binary) { + return function innerReverseB(...args) { + return binary(...args.reverse()); + }; +} + +/** + * reverse(func) ⇒ function + * + * @param {func} function - any function + * @returns {function} - Use the function twiceUnary to return x square + * + * */ +function reverse(binary) { + return function innerReverse(...args) { + return reverseb(binary)(...args); + }; +} + +/** + * composeuTwo(unary1, unary2) ⇒ function + * + * @param {unary1} function - any unary function + * @param {unary2} function - any unary function + * @returns {function} - Use the function twiceUnary to return x square + * + * */ +function composeuTwo(unary1, unary2) { + return function composeArg(arg) { + return unary2(unary1(arg)); + }; +} + +/** + * composeu(...funcs) ⇒ any + * + * @param {...funcs} function - an array of functions + * @returns {function} - composes a generic amount of functions + * + * */ +function composeu(...funcs) { + return function composeArg(arg) { + return funcs.reduce((result, func) => func(result), arg); + }; +} + + +/** + * composeb(binary1, binary2) ⇒ function + * + * @param {binary1} function - any binary function + * @param {binary2} function - any binary function + * @returns {function} - composes two functions to a single one that calls both (binary) + * + * */ +function composeb(binary1, binary2) { + return function composeBArgs(...arg) { + return binary2(binary1(arg[0], arg[1]), arg[2]); + }; +} + +/** + * composeTwo(func1, func2) ⇒ function + * + * @param {func1} function - any function + * @param {func2} function - any function + * @returns {function} - composes two functions to a single one that calls both + * (independend of arg length) + * + * */ +function composeTwo(func1, func2) { + return function composeBArgs(...arg) { + return func2(func1(...arg)); + }; +} + +/** + * compose(...funcs) ⇒ function + * + * @param {...funcs} function - any list of function + * @returns {function} - composes any amount functions to a single one that calls all of them + * (independend of arg length) + * + * */ +// max(fill(double(add(...args)))) +function compose(...funcs) { + // It returns a new function that will apply each function in the array from right to left + return function applyFromRightToLeft(...args) { + // This reduce function iterates over the functions array + return funcs.reduce((result, fn) => { + // If the result is not an array, convert it to an array + const argsArray = Array.isArray(result) ? result : [result]; + // Apply the current function to the arguments (result or [result]) + return fn(...argsArray); + }, args); // Initial arguments are passed to the first function + }; +} + +/** + * limitb(binary, lmt) ⇒ function + * + * @param {binary} function - any function + * @param {lmt} number - any limit (integer) + * @returns {function} - returns a function that can be calles lmt times + * + * */ +function limitb(binary, lmt) { + let callCount = 0; + return function limitedBinary(arg1, arg2) { + callCount += 1; // to be lint compliant + return (callCount <= lmt) ? binary(arg1, arg2) : undefined; + // one could also do this without the extra line for incrementing the counter + // return (++callCount <= lmt) ? binary(arg1, arg2) : undefined; + }; +} + +/** + * limit(func, lmt) ⇒ function + * + * @param {func} function - any function + * @param {lmt} number - any limit (integer) + * @returns {function} - returns a function that can be calles lmt times + * + * */ +function limit(func, lmt) { + let callCount = 0; + return function limitedBinary(...args) { + callCount += 1; // to be lint compliant + return (callCount <= lmt) ? func(...args) : undefined; + // one could also do this without the extra line for incrementing the counter + // return (++callCount <= lmt) ? func(...args) : undefined; + }; +} + +/** + * genFrom(x) ⇒ function + * + * @param {x} number - any integer + * @returns {number} - the count of how often it has been called + x + * + * */ +function* genFrom(x) { + let currentValue = x; + while (true) { + yield currentValue; + currentValue += 1; + } +} + +/** + * genTo(gen, lmt) ⇒ function + * + * @param {gen} function - the generator + * @param {lmt} number - any integer + * + * */ +function* genTo(gen, lmt) { + let lim = lmt - 1; + let next = gen.next(); + + while (!next.done && lim > 0) { + yield next.value; + lim -= 1; + next = gen.next(); + } +} + +/** + * genFromTo(start, end) ⇒ function + * + * @param {start} number - any integer + * @param {end} number - any integer + * + * */ +function* genFromTo(start, end) { + if (start >= end) throw Error('Start must not be greater than end'); + let currentValue = start; + while (currentValue < end) { + yield currentValue; + currentValue += 1; + } + return undefined; +} + +/** + * elementGen(array, gen) ⇒ function + * + * @param {array} array - any array + * @param {gen} function* - the generator func + * @returns {function} - returns a generator that will produce elements from the array + * + * */ +function* elementGen(array, gen) { + while (true) { + const index = gen.next().value; + // If the generator function reaches the end, break the loop + if (index === undefined) { + break; + } + yield array[index]; + } +} + +/** + * element(array, gen) ⇒ function + * + * @param {array} array - any array + * @param {gen} function* - the generator func + * @returns {function} - returns a generator that will produce elements from the array + * + * */ +function* element(array, gen) { + let currentValue = 0; + while (true) { + yield array[currentValue]; + currentValue += 1; + } +} + + +/** + * collect(gen, array) ⇒ function + * + * @param {array} array - any array + * @param {gen} function* - the generator func + * @returns {function} - returns a generator that will produce elements from the array + * + * */ +function* collect(gen, array) { + const arrCopy = array; + while (true) { + const index = gen.next().value; + // If the generator function reaches the end, break the loop + if (index === undefined) { + break; + } + arrCopy[index] = index; + yield arrCopy[index]; + } +} + +/** + * filter(gen, predicate) ⇒ function + * + * @param {gen} function - any generator function + * @param {predicate} function* - the predicate + * @returns {function} - produces a generator that produces only the values + * approved by the predicate + * + * */ +function* filter(gen, predicate) { + let result; + while (true) { + result = gen.next(); + if (result.done) break; + + const index = result.value; + if (predicate(index)) { + yield index; + } + } +} + +/** + * filterTail(gen, predicate) ⇒ function + * + * @param {gen} function - any generator function + * @param {predicate} function - the predicate + * @returns {function} - same as filter() but with tail-recursion to perform the filtering + * + * */ +function* filterTail(gen, predicate) { + const { done, value } = gen.next(); + if (done) { + return; + } + if (predicate(value)) { + yield value; + } + yield* filterTail(gen, predicate); +} + +/** + * concatTwo(gen1, gen2) ⇒ function + * + * @param {gen1} function - any generator function + * @param {gen2} function* - any generator function + * @returns {function} - contatenated generators + * + * */ +function* concatTwo(gen1, gen2) { + let result; + while (true) { + result = gen1.next(); + if (result.done) break; + + yield result.value; + } + + while (true) { + result = gen2.next(); + if (result.done) break; + + yield result.value; + } +} + +/** + * concat(...gens) ⇒ function + * + * @param {...gens} function - any number of generator functions + * @returns {function} - contatenated generators + * + * */ +function* concat(...gens) { + const concatenated = gens.flatMap(gen => [...gen]); + yield* concatenated; +// for (const gen of gens) { +// let result; +// while (true) { +// result = gen.next(); +// if (result.done) break; +// yield result.value; +// } +// } +} + +/** + * concatTail(...gens) ⇒ function + * + * @param {...gens} function - any list of generator functions + * @returns {function} - same as concat() but with tail-recursion to perform the filtering + * + * */ +function* concatTail(...gens) { + if (gens.length === 0) { + return; + } + + const [firstGen, ...remainingGens] = gens; + yield* firstGen; + yield* concatTail(...remainingGens); +} + +/** + * gensymf(symbol) ⇒ function + * + * @param {symbol} let - any character + * @returns {function} - a function that returns unique symbols + * + * */ +function* gensymf(symbol) { + const usedValues = []; + while (true) { + let cntr = 1; + while (usedValues.includes(symbol + cntr)) { + cntr += 1; + } + usedValues[cntr - 1] = symbol + cntr; + yield symbol + cntr; + } +} + +/** + * gensymff(unary, seed) ⇒ function + * + * @param {unary} function - any generator function + * @param {seed} - the seed + * @returns {function} - a function that returns unique symbols + * + * */ +function gensymff(unary, seed) { + return function* gensymfInner(symbol) { + let cntr = seed; + while (true) { + const result = unary(cntr); + cntr += 1; + yield symbol + result; + } + }; +} + +/** + * fibonaccif(first, second) ⇒ function + * + * @param {first} int - start val + * @param {second} int - next fibonacci element + * @returns {function} - the next fibonacci element + * + * */ +function* fibonaccif(first, second) { + let n1 = first; + let n2 = second; + let nextTerm = 0; + while (true) { + yield n1; + nextTerm = n1 + n2; + n1 = n2; + n2 = nextTerm; + } +} + +/** + * counter(i) ⇒ object + * + * @param {i} int - the start pos + * @returns {up} - function counts +1 + * @returns {down} - functions counts -1 + */ +function counter(i) { + let cntr = i; + return { + up() { + cntr += 1; + return cntr; + }, + down() { + cntr -= 1; + return cntr; + }, + }; +} + +/** + * revocableb(binary) ⇒ object + * + * @param {binary} function - any binary function + * @returns {invoke} - function invokes binary + * @returns {revoke} - function disables binary + */ +function revocableb(binary) { + let isAllowed = true; + return { + invoke(arg1, arg2) { + return isAllowed ? binary(arg1, arg2) : undefined; + }, + revoke() { + isAllowed = false; + }, + }; +} + +/** + * revocable(func) ⇒ object + * + * @param {func} function - any function + * @returns {invoke} - function invokes generic func + * @returns {revoke} - function disables function + */ +function revocable(func) { + let isAllowed = true; + return { + invoke(...args) { + return isAllowed ? func(...args) : undefined; + }, + revoke() { + isAllowed = false; + }, + }; +} + +/** + * extract(array, prop) ⇒ array + * + * @param {array} array - any array + * @param {prop} let - any peroperty name + * @returns {array} - an array with the extracted prop values + * + */ +function extract(array, prop) { + const property = prop; + const extractedValues = []; + array.forEach((obj) => { + if (obj[property] !== undefined) extractedValues.push(obj[property]); + }); + return extractedValues; +} + +/** + * m(value, source) ⇒ object + * + * @param {value} function - any function + * @param {source} let - optional: the source parameter + * @returns {obj} - an object + */ +function m(value, source) { + return { + value, + source: (source === undefined) ? value.toString() : source, + }; +} + +/** + * addmTwo(m1, m2) ⇒ object + * + * @param {m1} function - any m function + * @param {m2} function - any other m function + * @returns {obj} - an object + * + * */ +function addmTwo(m1, m2) { + const m1Source = (m1.source === undefined) ? m1.value.toString() : m1.source; + const m2Source = (m2.source === undefined) ? m2.value.toString() : m2.source; + return { + value: m1.value + m2.value, + source: `(${m1Source}+${m2Source})`, + }; +} + +/** + * addm(...ms) ⇒ object + * + * @param {m1} obj - any m function + * @param {m2} obj - any other m function + * @returns {obj} - an object + * + * */ +function addm(...ms) { + const value = ms.reduce((sum, current) => sum + current.value, 0); + const source = ms.map(current => (current.source === undefined ? current.value : current.source)).join('+'); + return { + value, + source: `(${source})`, + }; +} + +/** + * liftmbM(binary, op) ⇒ object + * + * @param {binary} function - any function + * @param {m2} function - any other m function + * @returns {obj} - an object + * + * */ +function liftmbM(binary, op) { + return function innerCall(m1, m2) { + return { + value: binary(m1.value, m2.value), + source: `(${m1.source === undefined ? m1.value : m1.source}${op}${m2.source === undefined ? m2.value : m2.source})`, + }; + }; +} + +/** + * liftmb(binary, op) ⇒ object + * + * @param {binary} function - any binary function + * @param {op} let - the concatenator + * @returns {obj} - an object with the func applied + * + * */ +function liftmb(binary, op) { + return function innerCall(arg1, arg2) { + return { + value: binary(arg1, arg2), + source: `(${arg1.toString()}${op}${arg2.toString()})`, + }; + }; +} + +/** + * liftm(func, op) ⇒ object + * + * @param {func} function - any function + * @param {op} let - the concatenator + * @returns {obj} - an object with the func applied + * + * */ +function liftm(func, op) { + return function innerCall(...args) { + const initialValue = (func === addb) ? 0 : 1; + const processedArgs = args.map(arg => (typeof arg === 'object' ? arg.value : arg)); + const value = processedArgs.reduce((arg1, arg2) => func(arg1, arg2), initialValue); + const source = args.map(arg => (typeof arg === 'object' ? arg.source || arg.value : arg)).join(op); + + return { + value, + source: `(${source})`, + }; + }; +} + +/** + * exp(value) ⇒ any + * + * @param {value} array - an array of any + * @returns the result of the operation + */ +function exp(value) { + if (typeof value[0] === 'function') { + const result = value.slice(1).reduce((arg1, arg2) => arg1 * arg2, 1); + return result; + } + return value; +} + +/** + * expn(value) ⇒ any + * + * @param {value} array - a nested array of any + * @returns the result of the operation + */ +function expn(value) { + if (typeof value === 'number') { + // If the element is a number, return it as is + return value; + } else if (Array.isArray(value)) { + // If the element is an array, recursively evaluate its contents + const operator = value[0]; + const operands = value.slice(1).map(expn); + + if (typeof operator === 'function') { + // If the first element is a function, apply it to the evaluated operands + return operator(...operands); + } + // If the first element is not a function, handle it accordingly + // console.error('Invalid expression - the first element is not a function.'); + return undefined; + } + // If the element is neither a number nor an array, handle it accordingly + // console.error('Invalid expression - unexpected element type.'); + return undefined; +} + +/** + * addg(value) ⇒ number | undefined + * + * @param {value} let - an array of any + * @returns the result of the operation + */ +function addg(value) { + let tmpSum = value || 0; + if (value === undefined) return undefined; + + return function adder(arg) { + if (arg === undefined) return tmpSum; + tmpSum += arg; + return adder; + }; +} + +/** + * liftg(binary) ⇒ function + * + * @param {binary} function - a binary function + * @returns a function that takes one addtl value and executes the + * binary on the result and the value + */ +function liftg(binary) { + let tmpRes = 0; + let iterations = 0; + if (binary === undefined) return undefined; + + return function executor(arg) { + if (arg === undefined && iterations === 0) return undefined; + if (arg === undefined) return tmpRes; + tmpRes = (iterations === 0) ? arg : binary(tmpRes, arg); + iterations += 1; + return executor; + }; +} + +/** + * arrayg(value) ⇒ array + * + * @param {value} number - any number + * @returns an array holding all args from earlier invocations + */ +function arrayg(value) { + const tmpRes = value === undefined ? [] : [value]; + if (value === undefined) return tmpRes; + return function executor(arg) { + if (arg === undefined) return tmpRes; + tmpRes.push(arg); + return executor; + }; +} + +/** + * continuizeu(unary) ⇒ function + * + * @param {unary} function - any unary function + * @returns a function that takes a callback and an argument + */ +function continuizeu(unary) { + return function executor(callback, arg) { + callback(unary(arg)); + }; +} + +/** + * continuize(func) ⇒ function + * + * @param {func} function - any unary function + * @returns a function that takes a callback and arguments + */ +function continuize(func) { + return function executor(callback, ...args) { + callback(func(...args)); + }; +} + +/** + * vector() ⇒ function + * + * @returns a vector object + */ +function vector() { + return { + secretArray: [], + append: function append(arg) { + this.secretArray.push(arg); + }, + store: function store(pos, arg) { + this.secretArray[pos] = arg; + }, + get: function get(pos) { + return this.secretArray[pos]; + }, + }; +} + +/** + * exploitVector(v) ⇒ function + * + * @param {v} object - any vector object + * @returns the secret array of the vector object + */ +function exploitVector(v) { + return v.secretArray; +} + +/** + * vectorSafe() ⇒ function + * + * @returns a vector object that denies direct variable access + */ +function vectorSafe() { + class Vector { + #secretArray = []; + + append(arg) { + this.#secretArray.push(arg); + } + + store(pos, arg) { + this.#secretArray[pos] = arg; + } + + get(pos) { + return this.#secretArray[pos]; + } + } + return new Vector(); +} + +/** + * pubsub() + * + * @returns an object with publish/subscribe functionality + */ +function pubsub() { + class PubSub { + #callback; + + subscribe(callback) { + this.#callback = callback; + } + + publish(text) { + this.#callback(text); + } + } + return new PubSub(); +} + +/** + * mapRecurse(array, callback) ⇒ array + * + * @param {array} array - any array of numbers + * @param {callback} function - the callback function + * @returns an a transformation for each element of a given array, recursively + * + * */ +function mapRecurse(array, callback) { + if (array.length === 0) { + return []; + } + // Recursive case: process the first element and concatenate the result with the rest of the array + return [callback(array[0])].concat(mapRecurse(array.slice(1), callback)); +} + +/** + * filterRecurse(array, predicate) ⇒ array + * + * @param {array} array - any array of numbers + * @param {callback} function - the callback function + * @returns an a transformation for each element of a given array, recursively + * + * */ +function filterRecurse(array, predicate) { + if (array.length === 0) { + return []; + } + const firstElement = array[0]; + // Recursive case: process the first element and concatenate the result with the rest of the array + const filteredRest = filterRecurse(array.slice(1), predicate); + // Check if the first element satisfies the predicate + if (predicate(firstElement)) { + return [firstElement].concat(filteredRest); + } + return filteredRest; +} + +module.exports = { + identity, + addb, + subb, + mulb, + minb, + maxb, + add, + sub, + mul, + min, + max, + addRecurse, + mulRecurse, + minRecurse, + maxRecurse, + not, + acc, + accPartial, + accRecurse, + fill, + fillRecurse, + set, + identityf, + addf, + liftf, + pure, + curryb, + curry, + inc, + twiceUnary, + doubl, + square, + twice, + reverseb, + reverse, + composeuTwo, + composeu, + composeb, + composeTwo, + compose, + limitb, + limit, + genFrom, + genTo, + genFromTo, + elementGen, + element, + collect, + filter, + filterTail, + concatTwo, + concat, + concatTail, + gensymf, + gensymff, + fibonaccif, + counter, + revocableb, + revocable, + extract, + m, + addmTwo, + addm, + liftmbM, + liftmb, + liftm, + exp, + expn, + addg, + liftg, + arrayg, + continuizeu, + continuize, + vector, + exploitVector, + vectorSafe, + pubsub, + mapRecurse, + filterRecurse, +}; diff --git a/test/Bltzz_tests.js b/test/Bltzz_tests.js new file mode 100644 index 0000000..3f18a02 --- /dev/null +++ b/test/Bltzz_tests.js @@ -0,0 +1,1040 @@ +const chai = require('chai'); +const sinonChai = require('sinon-chai'); + +require('mocha-sinon'); + +const assert = chai.assert; +const expect = chai.expect; + +chai.use(sinonChai); + +const sol = require('../Solutions/Bltzz_solution'); + + +describe('JS_Fun_Practice', () => { + describe('identity()', () => { + it('takes an argument and returns that argument', () => { + assert.equal(sol.identity(3), 3); + }); + }); + + + describe('addb(a,b)', () => { + it('takes two numbers and returns their sum', () => { + assert.equal(sol.addb(3, 4), 7); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.addb(40, '2')).to.throw(TypeError); + expect(() => sol.addb(40, [])).to.throw(TypeError); + expect(() => sol.addb(40, {})).to.throw(TypeError); + expect(() => sol.addb('40', 2)).to.throw(TypeError); + expect(() => sol.addb([], 2)).to.throw(TypeError); + expect(() => sol.addb({}, 2)).to.throw(TypeError); + }); + }); + + + describe('subb(a,b)', () => { + it('takes two numbers and returns their difference', () => { + assert.equal(sol.subb(3, 4), -1); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.subb(40, '2')).to.throw(TypeError); + expect(() => sol.subb(40, [])).to.throw(TypeError); + expect(() => sol.subb(40, {})).to.throw(TypeError); + expect(() => sol.subb('40', 2)).to.throw(TypeError); + expect(() => sol.subb([], 2)).to.throw(TypeError); + expect(() => sol.subb({}, 2)).to.throw(TypeError); + }); + }); + + + describe('mulb(a,b)', () => { + it('takes two numbers and returns their product', () => { + assert.equal(sol.mulb(3, 4), 12); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.mulb(40, '2')).to.throw(TypeError); + expect(() => sol.mulb(40, [])).to.throw(TypeError); + expect(() => sol.mulb(40, {})).to.throw(TypeError); + expect(() => sol.mulb('40', 2)).to.throw(TypeError); + expect(() => sol.mulb([], 2)).to.throw(TypeError); + expect(() => sol.mulb({}, 2)).to.throw(TypeError); + expect(() => sol.mulb('foo', 'bar')).to.throw(TypeError); + }); + }); + + + describe('minb(a,b)', () => { + it('takes two numbers and returns the smaller one', () => { + assert.equal(sol.minb(3, 4), 3); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.minb(40, '2')).to.throw(TypeError); + expect(() => sol.minb(40, [])).to.throw(TypeError); + expect(() => sol.minb(40, {})).to.throw(TypeError); + expect(() => sol.minb('40', 2)).to.throw(TypeError); + expect(() => sol.minb([], 2)).to.throw(TypeError); + expect(() => sol.minb({}, 2)).to.throw(TypeError); + expect(() => sol.minb('foo', 'bar')).to.throw(TypeError); + }); + }); + + + describe('maxb(a,b)', () => { + it('takes two numbers and returns the larger one', () => { + assert.equal(sol.maxb(3, 4), 4); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.maxb(40, '2')).to.throw(TypeError); + expect(() => sol.maxb(40, [])).to.throw(TypeError); + expect(() => sol.maxb(40, {})).to.throw(TypeError); + expect(() => sol.maxb('40', 2)).to.throw(TypeError); + expect(() => sol.maxb([], 2)).to.throw(TypeError); + expect(() => sol.maxb({}, 2)).to.throw(TypeError); + expect(() => sol.maxb('foo', 'bar')).to.throw(TypeError); + }); + }); + + + describe('add(...nums)', () => { + it('is an add fuction that is generalized for any amount of arguments', () => { + assert.equal(sol.add(1, 2, 4), 7); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.add(40, 41, '2')).to.throw(TypeError); + expect(() => sol.add(40, [], 12)).to.throw(TypeError); + expect(() => sol.add(40, {}, 42)).to.throw(TypeError); + expect(() => sol.add('40', 2)).to.throw(TypeError); + expect(() => sol.add([], 42, 2)).to.throw(TypeError); + expect(() => sol.add({}, 2)).to.throw(TypeError); + expect(() => sol.add('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.add(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + expect(() => sol.add()).to.throw('No arguments provided.'); + }); + }); + + + describe('sub(...nums)', () => { + it('is a sub fuction that is generalized for any amount of arguments', () => { + assert.equal(sol.sub(1, 2, 4), -5); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.sub(40, 41, '2')).to.throw(TypeError); + expect(() => sol.sub(40, [], 12)).to.throw(TypeError); + expect(() => sol.sub(40, {}, 42)).to.throw(TypeError); + expect(() => sol.sub('40', 2)).to.throw(TypeError); + expect(() => sol.sub([], 42, 2)).to.throw(TypeError); + expect(() => sol.sub({}, 2)).to.throw(TypeError); + expect(() => sol.sub('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.sub(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + expect(() => sol.sub()).to.throw('No arguments provided.'); + }); + }); + + + describe('mul(...nums)', () => { + it('is a mul fuction that is generalized for any amount of arguments', () => { + assert.equal(sol.mul(1, 2, 4), 8); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.mul(40, 41, '2')).to.throw(TypeError); + expect(() => sol.mul(40, [], 12)).to.throw(TypeError); + expect(() => sol.mul(40, {}, 42)).to.throw(TypeError); + expect(() => sol.mul('40', 2)).to.throw(TypeError); + expect(() => sol.mul([], 42, 2)).to.throw(TypeError); + expect(() => sol.mul({}, 2)).to.throw(TypeError); + expect(() => sol.mul('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.mul(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + expect(() => sol.mul()).to.throw('No arguments provided.'); + }); + }); + + + describe('min(...nums)', () => { + it('is a min fuction that is generalized for any amount of arguments', () => { + assert.equal(sol.min(1, 2, 4), 1); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.min(40, 41, '2')).to.throw(TypeError); + expect(() => sol.min(40, [], 12)).to.throw(TypeError); + expect(() => sol.min(40, {}, 42)).to.throw(TypeError); + expect(() => sol.min('40', 2)).to.throw(TypeError); + expect(() => sol.min([], 42, 2)).to.throw(TypeError); + expect(() => sol.min({}, 2)).to.throw(TypeError); + expect(() => sol.min('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.min(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + expect(() => sol.min()).to.throw('No arguments provided.'); + }); + }); + + + describe('max(...nums)', () => { + it('is a max fuction that is generalized for any amount of arguments', () => { + assert.equal(sol.max(1, 6, 4), 6); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.max(40, 41, '2')).to.throw(TypeError); + expect(() => sol.max(40, [], 12)).to.throw(TypeError); + expect(() => sol.max(40, {}, 42)).to.throw(TypeError); + expect(() => sol.max('40', 2)).to.throw(TypeError); + expect(() => sol.max([], 42, 2)).to.throw(TypeError); + expect(() => sol.max({}, 2)).to.throw(TypeError); + expect(() => sol.max('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.max(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + expect(() => sol.max()).to.throw('No arguments provided.'); + }); + }); + + + describe('addRecurse(...nums)', () => { + it('is an add fuction that is generalized but uses recursion', () => { + assert.equal(sol.addRecurse(1, 2, 4), 7); + assert.equal(sol.addRecurse(), 0); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.addRecurse(40, 41, '2')).to.throw(TypeError); + expect(() => sol.addRecurse(40, [], 12)).to.throw(TypeError); + expect(() => sol.addRecurse(40, {}, 42)).to.throw(TypeError); + expect(() => sol.addRecurse('40', 2)).to.throw(TypeError); + expect(() => sol.addRecurse([], 42, 2)).to.throw(TypeError); + expect(() => sol.addRecurse({}, 2)).to.throw(TypeError); + expect(() => sol.addRecurse('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.addRecurse(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + }); + }); + + + describe('mulRecurse(...nums)', () => { + it('is a mul fuction that is generalized but uses recursion', () => { + assert.equal(sol.mulRecurse(1, 2, 4), 8); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.mulRecurse(40, 41, '2')).to.throw(TypeError); + expect(() => sol.mulRecurse(40, [], 12)).to.throw(TypeError); + expect(() => sol.mulRecurse(40, {}, 42)).to.throw(TypeError); + expect(() => sol.mulRecurse('40', 2)).to.throw(TypeError); + expect(() => sol.mulRecurse([], 42, 2)).to.throw(TypeError); + expect(() => sol.mulRecurse({}, 2)).to.throw(TypeError); + expect(() => sol.mulRecurse('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.mulRecurse(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + }); + }); + + describe('minRecurse(...nums)', () => { + // TODO: Nice addon: Count the function calls by wrapping recursive method in class function + // See: https://stackoverflow.com/questions/51699584/how-to-spy-on-a-recursive-function-in-javascript/51699585#51699585 + it('is a min fuction that is generalized but uses recursion', () => { + assert.equal(sol.minRecurse(1, 2, 4), 1); + assert.equal(sol.minRecurse(3, 2, 1), 1); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.minRecurse(40, 41, '2')).to.throw(TypeError); + expect(() => sol.minRecurse(40, [], 12)).to.throw(TypeError); + expect(() => sol.minRecurse(40, {}, 42)).to.throw(TypeError); + expect(() => sol.minRecurse('40', 2)).to.throw(TypeError); + expect(() => sol.minRecurse([], 42, 2)).to.throw(TypeError); + expect(() => sol.minRecurse({}, 2)).to.throw(TypeError); + expect(() => sol.minRecurse('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.minRecurse(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + }); + }); + + + describe('maxRecurse(...nums)', () => { + it('is a max fuction that is generalized but uses recursion', () => { + assert.equal(sol.maxRecurse(1, 2, 4), 4); + }); + + it('should throw a TypeError if arguments are not numbers', () => { + expect(() => sol.maxRecurse(40, 41, '2')).to.throw(TypeError); + expect(() => sol.maxRecurse(40, [], 12)).to.throw(TypeError); + expect(() => sol.maxRecurse(40, {}, 42)).to.throw(TypeError); + expect(() => sol.maxRecurse('40', 2)).to.throw(TypeError); + expect(() => sol.maxRecurse([], 42, 2)).to.throw(TypeError); + expect(() => sol.maxRecurse({}, 2)).to.throw(TypeError); + expect(() => sol.maxRecurse('foo', 'bar', 4)).to.throw(TypeError); + expect(() => sol.maxRecurse(4, 5, 6, 7, 8, 9, 10, 11, '12')).to.throw(TypeError); + }); + }); + + + describe('not(func)', () => { + it('takes a function and returns the negation of its result', () => { + const isOdd = x => x % 2 === 1; + const isEven = sol.not(isOdd); + assert.equal(isEven(1), false); + assert.equal(isEven(2), true); + }); + + it('should throw a TypeError if arguments are not a function', () => { + expect(() => sol.not(40, 41, '2')).to.throw(TypeError); + }); + }); + + + describe('acc(func,initial)', () => { + it(`takes a function and an initial value and returns a function + that runs the initial function on each argument, accumulating the result`, () => { + const add = sol.acc(sol.addb, 0); + assert.equal(add(1, 2, 4), 7); + + const mul = sol.acc(sol.mulb, 1); + assert.equal(mul(1, 2, 4), 8); + }); + + it('should throw a TypeError if first arguments are not a function', () => { + expect(() => sol.acc(40, 41, '2')).to.throw(TypeError); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.acc(sol.addb, 'ds')).to.throw(TypeError); + }); + }); + + + describe('accPartial(func,start,end)', () => { + it(`takes in a function, a start index, and an end index, and returns a function + that accumulates a subset of its arguments by applying the given function to + all elements between start and end`, () => { + const addSecondToThird = sol.accPartial(sol.add, 1, 3); + expect(addSecondToThird(1, 2, 4, 8)).to.deep.equal([1, 6, 8]); + + const subSecondToThird = sol.accPartial(sol.sub, 1, 3); + expect(subSecondToThird(1, 2, 4, 8)).to.deep.equal([1, -2, 8]); + + const subSecondToFourth = sol.accPartial(sol.sub, 1, 4); + expect(subSecondToFourth(1, 2, 4, 6, 8)).to.deep.equal([1, -8, 8]); + }); + + it('should throw a TypeError if first arguments are not a function', () => { + expect(() => sol.accPartial(40, 41, '2')).to.throw(TypeError); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.accPartial(sol.addb, 'ds')).to.throw(TypeError); + }); + }); + + + describe('accRecurse(func,initial)', () => { + it('does what acc does but uses recursion', () => { + const add = sol.accRecurse(sol.addb, 0); + assert.equal(add(1, 2, 4), 7); + + const mul = sol.accRecurse(sol.mulb, 1); + assert.equal(mul(1, 2, 4), 8); + }); + + it('should throw a TypeError if first arguments are not a function', () => { + expect(() => sol.accRecurse(40, 41, '2')).to.throw(TypeError); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.accRecurse(sol.addb, 'ds')).to.throw(TypeError); + }); + }); + + describe('fill(num)', () => { + it(`takes a number and returns an array with that many numbers equal to the given + number`, () => { + expect(sol.fill(3)).to.deep.equal([3, 3, 3]); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.fill()).to.throw(TypeError); + expect(() => sol.fill(sol.addb)).to.throw(TypeError); + expect(() => sol.fill('40')).to.throw(TypeError); + expect(() => sol.fill([])).to.throw(TypeError); + expect(() => sol.fill([], 12)).to.throw(TypeError); + expect(() => sol.fill({})).to.throw(TypeError); + expect(() => sol.fill('40', 2)).to.throw(TypeError); + expect(() => sol.fill({}, 2)).to.throw(TypeError); + expect(() => sol.fill('foo', 'bar', 4)).to.throw(TypeError); + }); + }); + + + describe('fillRecurse(num)', () => { + it('does what fill does but uses recursion', () => { + expect(sol.fillRecurse(3)).to.deep.equal([3, 3, 3]); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.fillRecurse()).to.throw(TypeError); + expect(() => sol.fillRecurse(sol.addb)).to.throw(TypeError); + expect(() => sol.fillRecurse('40')).to.throw(TypeError); + expect(() => sol.fillRecurse([])).to.throw(TypeError); + expect(() => sol.fillRecurse([])).to.throw(TypeError); + expect(() => sol.fillRecurse({})).to.throw(TypeError); + expect(() => sol.fillRecurse('foo')).to.throw(TypeError); + }); + }); + describe('set(...args)', () => { + it(`is given a list of arguments and returns an array with all duplicates + removed`, () => { + expect(sol.set(1, 1, 1, 2, 2, 2)).to.deep.equal([1, 2]); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.set(sol.addb)).to.throw(TypeError); + expect(() => sol.set('40')).to.throw(TypeError); + expect(() => sol.set([])).to.throw(TypeError); + expect(() => sol.set([], 12)).to.throw(TypeError); + expect(() => sol.set({})).to.throw(TypeError); + expect(() => sol.set('40', 2)).to.throw(TypeError); + expect(() => sol.set({}, 2)).to.throw(TypeError); + expect(() => sol.set('foo', 'bar', 4)).to.throw(TypeError); + }); + + it('should throw a No arguments provided error if second argument are not a number', () => { + expect(() => sol.set()).to.throw('No arguments provided.'); + }); + }); + + describe('identityf(x)', () => { + it('takes an argument and returns a function that returns that argument', () => { + assert.equal(sol.identityf(3)(), 3); + }); + }); + + + describe('addf(a)', () => { + it('adds from two invocations', () => { + assert.equal(sol.addf(3)(4), 7); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.addf()).to.throw(TypeError); + expect(() => sol.addf(sol.addb)).to.throw(TypeError); + expect(() => sol.addf('40')).to.throw(TypeError); + expect(() => sol.addf([])).to.throw(TypeError); + expect(() => sol.addf([], 12)).to.throw(TypeError); + expect(() => sol.addf({})).to.throw(TypeError); + expect(() => sol.addf('40', 2)).to.throw(TypeError); + expect(() => sol.addf({}, 2)).to.throw(TypeError); + expect(() => sol.addf('foo', 'bar', 4)).to.throw(TypeError); + }); + }); + + describe('liftf(binary)', () => { + it('takes a binary function, and makes it callable with two invocations', () => { + assert.equal(sol.liftf(sol.addb)(3)(4), 7); + assert.equal(sol.liftf(sol.mulb)(5)(6), 30); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.liftf()).to.throw(TypeError); + expect(() => sol.liftf(sol.liftf(sol.addb)('foo')(4))).to.throw(TypeError); + expect(() => sol.liftf(sol.liftf(sol.addb)([])(4))).to.throw(TypeError); + expect(() => sol.liftf(sol.liftf(sol.addb)({})(4))).to.throw(TypeError); + expect(() => sol.liftf(sol.liftf(sol.addb)()(4))).to.throw(TypeError); + expect(() => sol.liftf(sol.liftf(sol.addb)(42)([]))).to.throw(TypeError); + expect(() => sol.liftf(sol.liftf(sol.addb)(42)({}))).to.throw(TypeError); + expect(() => sol.liftf(sol.liftf(sol.addb)(42)())).to.throw(TypeError); + expect(() => sol.liftf(sol.liftf(sol.addb)(sol.mulb)(4))).to.throw(TypeError); + }); + }); + + describe('pure(x,y)', () => { + it('is a wrapper arround the impure function impure', () => { + expect(sol.pure(20, 5)).to.deep.equal([6, 120]); + expect(sol.pure(25, 6)).to.deep.equal([7, 175]); + }); + }); + + + describe('curryb(binary, a)', () => { + it(`takes a binary function and an argument, and returns a function that can take + a second argument`, () => { + assert.equal(sol.curryb(sol.addb, 3)(4), 7); + assert.equal(sol.curryb(sol.mulb, 5)(6), 30); + }); + + it('should throw a TypeError if second argument are not a number', () => { + expect(() => sol.curryb()).to.throw(TypeError); + expect(() => sol.curryb(sol.addb, 3)([])).to.throw(TypeError); + expect(() => sol.curryb(sol.addb, 3)({})).to.throw(TypeError); + expect(() => sol.curryb(sol.addb, 3)()).to.throw(TypeError); + expect(() => sol.curryb(sol.addb, 'foo')(4)).to.throw(TypeError); + expect(() => sol.curryb(sol.addb, [])(4)).to.throw(TypeError); + expect(() => sol.curryb(sol.addb, {})(4)).to.throw(TypeError); + expect(() => sol.curryb([], 3)(4)).to.throw(TypeError); + expect(() => sol.curryb({}, 3)(4)).to.throw(TypeError); + expect(() => sol.curryb('foo', 3)(4)).to.throw(TypeError); + expect(() => sol.curryb(3)(4)).to.throw(TypeError); + expect(() => sol.curryb([])(4)).to.throw(TypeError); + }); + }); + + + describe('curry(func, ...outer)', () => { + it('is a curry function generalized for any amount of arguments', () => { + assert.equal(sol.curry(sol.add, 1, 2, 4)(4, 2, 1), 14); + assert.equal(sol.curry(sol.sub, 1, 2, 4)(4, 2, 1), -12); + assert.equal(sol.curry(sol.mul, 1, 2, 4)(4, 2, 1), 64); + }); + }); + + + describe('inc(x)', () => { + it('shows multiple ways to create the inc function', () => { + assert.equal(sol.inc(5), 6); + assert.equal(sol.inc(sol.inc(5)), 7); + assert.equal(sol.inc(sol.inc(sol.inc(5))), 8); + }); + }); + + + describe('twiceUnary(binary)', () => { + it(`takes a binary function and returns a unary function that passes its argument + to the binary function twice`, () => { + assert.equal(sol.twiceUnary(sol.addb)(11), 22); + assert.equal(sol.twiceUnary(sol.mulb)(11), 121); + }); + }); + + + describe('doubl(x)', () => { + it('uses the function twiceUnary to create the doubl function', () => { + assert.equal(sol.doubl(11), 22); + }); + }); + + describe('square(x)', () => { + it('uses the function twiceUnary to create the square function', () => { + assert.equal(sol.square(11), 121); + }); + }); + + describe('twice(x)', () => { + it('is a twice function generalized for any amount of arguments', () => { + assert.equal(sol.twice(sol.add)(1, 2, 4), 14); + }); + }); + + describe('reverseb(binary)', () => { + it('reverses the arguments of a binary function', () => { + assert.equal(sol.reverseb(sol.subb)(3, 2), -1); + }); + }); + + describe('reverse(func)', () => { + it('is a reverse function generalized for any amount of arguments', () => { + assert.equal(sol.reverse(sol.sub)(1, 2, 4), 1); + }); + }); + + describe('composeuTwo(unary1,unary2)', () => { + it(`takes two unary functions and returns a unary function that calls them + both`, () => { + assert.equal(sol.composeuTwo(sol.doubl, sol.square)(5), 100); + }); + }); + + describe('composeu(...funcs)', () => { + it('is a compose function generalized for any amount of arguments', () => { + assert.equal( + sol.composeu( + sol.doubl, + sol.square, + sol.identity, + sol.curry(sol.add, 1, 2), + )(5), + 103, + ); + }); + }); + + describe('composeb(binary1,binary2)', () => { + it('takes two binary functions and returns a function that calls them both', () => { + assert.equal(sol.composeb(sol.addb, sol.mulb)(2, 3, 7), 35); + }); + }); + + describe('composeTwo(func1,func2)', () => { + it('takes two functions and returns a function that calls them both', () => { + assert.equal(sol.composeTwo(sol.add, sol.square)(2, 3, 7), 144); + }); + }); + + describe('compose(...funcs)', () => { + it(`takes any amount of functions and returns a function that takes any amount of + arguments and gives them to the first function, then that result to the + second function and so on`, () => { + assert.equal( + sol.compose(sol.add, sol.doubl, sol.fill, sol.max)(0, 1, 2), 6); + }); + }); + + describe('limitb(binary, lmt)', () => { + it('allows a binary function to be called a limited number of times', () => { + const addlmtb = sol.limitb(sol.addb, 1); + assert.equal(addlmtb(3, 4), 7); + assert.equal(addlmtb(3, 5), undefined); + }); + }); + + describe('limit(func, lmt)', () => { + it(`allows a function that is generalized for any amount of arguments + to be called a limited number of times`, () => { + const addlmt = sol.limit(sol.add, 1); + assert.equal(addlmt(1, 2, 4), 7); + assert.equal(addlmt(3, 5, 9, 2), undefined); + }); + }); + + describe('genFrom(x)', () => { + it('produces a generator that will produces a series of values', () => { + const index = sol.genFrom(0); + assert.equal(index.next().value, 0); + assert.equal(index.next().value, 1); + assert.equal(index.next().value, 2); + }); + }); + + describe('genTo(x)', () => { + it(`takes a generator and an end limit, and returns a generator that will + produce numbers up to that limit`, () => { + const index = sol.genTo(sol.genFrom(1), 3); + assert.equal(index.next().value, 1); + assert.equal(index.next().value, 2); + assert.equal(index.next().value, undefined); + }); + }); + + describe('genFromTo(x)', () => { + it('produces a generator that will produce values in a range', () => { + const index = sol.genFromTo(0, 3); + assert.equal(index.next().value, 0); + assert.equal(index.next().value, 1); + assert.equal(index.next().value, 2); + assert.equal(index.next().value, undefined); + }); + it('throws and error if start is greater than end', () => { + const index = sol.genFromTo(3, 1); + expect(() => index.next().value).to.throw('Start must not be greater than end'); + }); + }); + + describe('elementGen(array,gen)', () => { + it(`takes an array and a generator and returns a generator that will produce + elements from the array`, () => { + const ele = sol.elementGen(['a', 'b', 'c', 'd'], sol.genFromTo(1, 3)); + assert.equal(ele.next().value, 'b'); + assert.equal(ele.next().value, 'c'); + assert.equal(ele.next().value, undefined); + }); + }); + + describe('element(array,gen)', () => { + it(`is a modified elementGen function so that the generator argument is optional. + If a generator is not provided, then each of the elements of the array will + be produced.`, () => { + const ele = sol.element(['a', 'b', 'c', 'd']); + assert.equal(ele.next().value, 'a'); + assert.equal(ele.next().value, 'b'); + assert.equal(ele.next().value, 'c'); + assert.equal(ele.next().value, 'd'); + assert.equal(ele.next().value, undefined); + }); + }); + + describe('collect(gen,array)', () => { + it(`takes a generator and an array and produces a function that will collect the + results in the array`, () => { + const array = []; + const col = sol.collect(sol.genFromTo(0, 2), array); + assert.equal(col.next().value, 0); + assert.equal(col.next().value, 1); + assert.equal(col.next().value, undefined); + expect(array).to.deep.equal([0, 1]); + }); + }); + + describe('filter(gen,predicate)', () => { + it(`takes a generator and a predicate and produces a generator that produces only + the values approved by the predicate`, () => { + const fil = sol.filter(sol.genFromTo(0, 5), val => val % 3 === 0); + assert.equal(fil.next().value, 0); + assert.equal(fil.next().value, 3); + assert.equal(fil.next().value, undefined); + }); + }); + + describe('filterTail(gen,predicate)', () => { + it('uses tail-recursion to perform the filtering', () => { + const fil = sol.filterTail(sol.genFromTo(0, 5), val => val % 3 === 0); + assert.equal(fil.next().value, 0); + assert.equal(fil.next().value, 3); + assert.equal(fil.next().value, undefined); + }); + }); + + describe('concatTwo(gen1,gen2)', () => { + it('takes two generators and produces a generator that combines the sequences', () => { + const con = sol.concatTwo(sol.genFromTo(0, 3), sol.genFromTo(0, 2)); + assert.equal(con.next().value, 0); + assert.equal(con.next().value, 1); + assert.equal(con.next().value, 2); + assert.equal(con.next().value, 0); + assert.equal(con.next().value, 1); + assert.equal(con.next().value, undefined); + }); + }); + + describe('concat(...gens)', () => { + it('is generalized for any amount of arguments', () => { + const con = sol.concat( + sol.genFromTo(0, 3), + sol.genFromTo(0, 2), + sol.genFromTo(5, 7), + ); + assert.equal(con.next().value, 0); + assert.equal(con.next().value, 1); + assert.equal(con.next().value, 2); + assert.equal(con.next().value, 0); + assert.equal(con.next().value, 1); + assert.equal(con.next().value, 5); + assert.equal(con.next().value, 6); + assert.equal(con.next().value, undefined); + }); + }); + + describe('concatTail(...gens)', () => { + it('uses tail-recursion to perform the concating', () => { + const con = sol.concatTail( + sol.genFromTo(0, 3), + sol.genFromTo(0, 2), + sol.genFromTo(5, 7), + ); + assert.equal(con.next().value, 0); + assert.equal(con.next().value, 1); + assert.equal(con.next().value, 2); + assert.equal(con.next().value, 0); + assert.equal(con.next().value, 1); + assert.equal(con.next().value, 5); + assert.equal(con.next().value, 6); + assert.equal(con.next().value, undefined); + }); + }); + + describe('gensymf(symbol)', () => { + it('makes a function that generates unique symbols', () => { + const genG = sol.gensymf('G'); + const genH = sol.gensymf('H'); + assert.equal(genG.next().value, 'G1'); + assert.equal(genH.next().value, 'H1'); + assert.equal(genG.next().value, 'G2'); + assert.equal(genH.next().value, 'H2'); + assert.equal(genG.next().value, 'G3'); + assert.equal(genH.next().value, 'H3'); + assert.equal(genG.next().value, 'G4'); + assert.equal(genH.next().value, 'H4'); + assert.equal(genG.next().value, 'G5'); + assert.equal(genH.next().value, 'H5'); + assert.equal(genG.next().value, 'G6'); + assert.equal(genH.next().value, 'H6'); + }); + }); + + describe('gensymff(unary, seed)', () => { + it('takes a unary function and a seed and returns a gensymf', () => { + const gensymf = sol.gensymff(sol.inc, 0); + const genG = gensymf('G'); + const genH = gensymf('H'); + assert.equal(genG.next().value, 'G1'); + assert.equal(genH.next().value, 'H1'); + assert.equal(genG.next().value, 'G2'); + assert.equal(genH.next().value, 'H2'); + }); + }); + + describe('fibonaccif(first, second)', () => { + it('returns a generator that will return the next fibonacci number', () => { + const fib = sol.fibonaccif(0, 1); + assert.equal(fib.next().value, 0); + assert.equal(fib.next().value, 1); + assert.equal(fib.next().value, 1); + assert.equal(fib.next().value, 2); + assert.equal(fib.next().value, 3); + assert.equal(fib.next().value, 5); + assert.equal(fib.next().value, 8); + }); + it('returns a generator that will return the next fibonacci number with different start val', () => { + const fib = sol.fibonaccif(1, 2); + assert.equal(fib.next().value, 1); + assert.equal(fib.next().value, 2); + assert.equal(fib.next().value, 3); + assert.equal(fib.next().value, 5); + assert.equal(fib.next().value, 8); + }); + }); + + describe('counter(i)', () => { + it(`returns an object containing two functions that implement an up/down counter, + hiding the counter`, () => { + const obj = sol.counter(10); + const { up, down } = obj; + assert.equal(up(), 11); + assert.equal(down(), 10); + assert.equal(down(), 9); + assert.equal(up(), 10); + }); + }); + + describe('revocableb(binary)', () => { + it(`takes a binary function, and returns an object containing an invoke function + that can invoke a function and a revoke function that disables the invoke + function`, () => { + const rev = sol.revocableb(sol.addb); + assert.equal(rev.invoke(3, 4), 7); + rev.revoke(); + assert.equal(rev.invoke(5, 7), undefined); + }); + }); + + describe('revocable(binary)', () => { + it(`takes a function that is generalized for any amount of arguments, and returns + an object containing an invoke function that can invoke a function and a revoke + function that disables the invoke function`, () => { + const rev = sol.revocable(sol.add); + assert.equal(rev.invoke(3, 4), 7); + rev.revoke(); + assert.equal(rev.invoke(5, 7), undefined); + }); + }); + + describe('extract(array,prop)', () => { + it(`takes an array of objects and an object property name and converts each object + in the array by extracting that property`, () => { + const people = [{ name: 'john' }, { name: 'bob' }]; + expect(sol.extract(people, 'name')).to.deep.equal(['john', 'bob']); + }); + }); + describe('m(value,source)', () => { + it('takes a value and an optional source string and returns them in an object', () => { + expect(sol.m(1)).to.deep.equal({ value: 1, source: '1' }); + expect(sol.m(Math.PI, 'pi')).to.deep.equal({ + value: Math.PI, + source: 'pi', + }); + }); + }); + describe('addmTwo(m1,m2)', () => { + it('adds two m objects and returns an m object', () => { + expect(sol.addmTwo(sol.m(3), sol.m(4))).to.deep.equal({ + value: 7, + source: '(3+4)', + }); + expect(sol.addmTwo(sol.m(1), sol.m(Math.PI, 'pi'))).to.deep.equal({ + value: Math.PI + 1, + source: '(1+pi)', + }); + }); + }); + + describe('addm(...ms)', () => { + it(`is a function that is generalized for any amount of arguments that adds m + objects and returns an m object`, () => { + expect(sol.addm(sol.m(1), sol.m(2), sol.m(4))).to.deep.equal({ + value: 7, + source: '(1+2+4)', + }); + }); + }); + + describe('liftmbM(binary, op)', () => { + it(`takes a binary function and a string and returns a function that acts on m + objects`, () => { + expect(sol.liftmbM(sol.addb, '+')(sol.m(3), sol.m(4))).to.deep.equal({ + value: 7, + source: '(3+4)', + }); + expect(sol.liftmbM(sol.mulb, '*')(sol.m(3), sol.m(4))).to.deep.equal({ + value: 12, + source: '(3*4)', + }); + }); + }); + + describe('liftmb(binary, op)', () => { + it(`is a modified function liftmbM that can accept arguments that are either numbers + or m objects`, () => { + expect(sol.liftmb(sol.addb, '+')(3, 4)).to.deep.equal({ + value: 7, + source: '(3+4)', + }); + }); + }); + + describe('liftm(func, op)', () => { + it(`is a modified function liftmbM that is generalized for any amount of arguments + that can accept arguments that are either numbers or m objects`, () => { + expect(sol.liftm(sol.addb, '+')(sol.m(3), sol.m(4))).to.deep.equal({ + value: 7, + source: '(3+4)', + }); + expect(sol.liftm(sol.mulb, '*')(sol.m(3), sol.m(4))).to.deep.equal({ + value: 12, + source: '(3*4)', + }); + expect(sol.liftm(sol.mulb, '*')(3, 4)).to.deep.equal({ + value: 12, + source: '(3*4)', + }); + }); + }); + + describe('exp(value)', () => { + it('evaluates simple array expressions', () => { + assert.equal(sol.exp([sol.mul, 1, 2, 4]), 8); + assert.equal(sol.exp(42), 42); + }); + }); + + describe('expn(value)', () => { + it('is a modified exp that can evaluate nested array expressions', () => { + assert.equal( + sol.expn([Math.sqrt, [sol.add, [sol.square, 3], [sol.square, 4]]]), + 5, + ); + assert.equal(sol.expn(34), 34); + }); + }); + + describe('addg(value)', () => { + it('adds from many invocations, until it sees an empty invocation', () => { + assert.equal(sol.addg(), undefined); + assert.equal(sol.addg(2)(), 2); + assert.equal(sol.addg(2)(7)(), 9); + assert.equal(sol.addg(3)(0)(4)(), 7); + assert.equal(sol.addg(1)(2)(4)(8)(), 15); + }); + }); + + describe('liftg(value)', () => { + it('will take a binary function and apply it to many invocations', () => { + assert.equal(sol.liftg(sol.mulb)(), undefined); + assert.equal(sol.liftg(sol.mulb)(3)(), 3); + assert.equal(sol.liftg(sol.mulb)(3)(0)(4)(), 0); + assert.equal(sol.liftg(sol.mulb)(1)(2)(4)(8)(), 64); + }); + }); + + describe('arrayg(value)', () => { + it('will build an array from many invocations', () => { + expect(sol.arrayg()).to.deep.equal([]); + expect(sol.arrayg(3)()).to.deep.equal([3]); + expect(sol.arrayg(3)(4)(5)()).to.deep.equal([3, 4, 5]); + }); + }); + + describe('continuizeu(unary)', () => { + beforeEach(function invocation() { + this.sinon.stub(console, 'log'); + }); + it(`takes a unary function and returns a function + that takes a callback and an argument`, () => { + sol.continuizeu(Math.sqrt)(console.log, 81); // eslint-disable-line no-console + expect(console.log.calledOnce).to.be.true; // eslint-disable-line + // no-console, no-unused-expressions + expect(console.log.calledWith(9)).to.be.true; // eslint-disable-line + // no-console, no-unused-expressions + }); + }); + + describe('continuize(any)', () => { + beforeEach(function invocation() { + this.sinon.stub(console, 'log'); + }); + + it(`takes a function and returns a function + that takes a callback and an argument`, () => { + sol.continuize(sol.mul)(console.log, 81, 4, 2);// eslint-disable-line + // no-console, no-unused-expressions + expect(console.log.calledOnce).to.be.true;// eslint-disable-line + // no-console, no-unused-expressions + expect(console.log.calledWith(648)).to.be.true;// eslint-disable-line + // no-console, no-unused-expressions + }); + }); + + describe('vector()', () => { + it(`is an array wrapper object with methods get, store, and append, such that an + attacker cannot get access to the private array`, () => { + const v = sol.vector(); + v.append(7); + v.store(1, 8); + assert.equal(v.get(0), 7); + assert.equal(v.get(1), 8); + }); + }); + + describe('exploitVector()', () => { + it('accesses array outside of vector', () => { + const v = sol.vector(); + v.append(1); + v.append(2); + expect(sol.exploitVector(v)).to.deep.equal([1, 2]); + }); + }); + + describe('vectorSafe()', () => { + it('can\'t access array outside of vector', () => { + const v = sol.vectorSafe(); + v.append(1); + v.append(2); + expect(sol.exploitVector(v)).to.deep.equal(undefined); + }); + }); + + describe('pubsub()', () => { + beforeEach(function invocation() { + this.sinon.stub(console, 'log'); + }); + + it(`makes a publish/subscribe object. It will reliably deliver all publications + to all subscribers in the right order`, () => { + const ps = sol.pubsub(); + ps.subscribe(console.log);// eslint-disable-line + // no-console, no-unused-expressions + ps.publish('It works!'); + + expect(console.log.calledOnce).to.be.true;// eslint-disable-line + // no-console, no-unused-expressions + expect(console.log.calledWith("It works!")).to.be.true;// eslint-disable-line + // no-console, no-unused-expressions + }); + }); + + describe('mapRecurse(array, predicate)', () => { + it('performs a transformation for each element of a given array, recursively', () => { + expect(sol.mapRecurse([1, 2, 3, 4], x => x * 2)).to.deep.equal([ + 2, 4, 6, 8, + ]); + }); + }); + + describe('filterRecurse(array, predicate)', () => { + it(`takes in an array and a predicate function and returns a new array by + filtering out all items using the predicate, recursively.`, () => { + expect(sol.filterRecurse([1, 2, 3, 4], x => x % 2 === 0)).to.deep.equal( + [2, 4], + ); + }); + }); +}); \ No newline at end of file