// playing with algorithms series
//
// given a list of items (nested arrays may occur)
// find the largest item and return it. (Items may be strings)
//
// signature: fn(num1, num2, arr1, ...)
//
// example:
//
// fn(1, 2, [5, 9], [3, [12, 7]]) should return 12
// used for functional implementations
function flatten(...args) {
return args.reduce(
/* acc is short for "accumulator" here */
(acc, item) => [
...acc, // spread the accumulator out
/* we'll spread the following out into multiple arguments */
...(Array.isArray(item)
? /* If the item is an array...
* we need to use recursion, so spread the array out
* and call ourselves again. */
flatten(...item)
: /* Otherwise, return array containing a single item
* so that spread can still work */
[item]),
],
/* the starting result for Array#reduce is an array with no items.
* This will be returned if there is nothing to flatten, but also
* serves as the initial value to `acc` above (the accumulator) */
[]
);
}
const implementations = {
imperative: function findMax(...args) {
let largest;
const stack = args;
while (stack.length > 0) {
const candidate = stack.pop();
if (Array.isArray(candidate)) {
stack.push(...candidate);
} else if (largest === undefined || candidate > largest) {
largest = candidate;
}
}
return largest;
},
recursive: function findMax(...args) {
let largest;
for (let candidate of args) {
if (Array.isArray(candidate)) {
candidate = findMax(...candidate);
}
if (largest === undefined || candidate > largest) {
largest = candidate;
}
}
return largest;
},
functional: function findMax(...args) {
const largest = (a, b) => (([a = b, b = a] = [a, b]), a > b ? a : b);
return flatten(args).reduce(largest, undefined);
},
functionalWithSort: function findMax(...args) {
const ascendingOrder = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
return flatten(args)
.sort(ascendingOrder)
.pop();
},
// cheating, though, since it doesn't handle strings correctly
usingMax: function findMax(...args) {
return Math.max(...flatten(args));
},
};
function assert({ fn, expectation } = {}) {
try {
let result = fn();
if (!(result === expectation)) {
console.error(`[FAIL] Expected ${expectation}; got ${result}`);
} else {
console.log(`[PASS] Passed; got ${result}`);
}
} catch (err) {
console.error(`[FAIL] ${err.message}; expected ${expectation}`);
}
}
Object.entries(implementations).forEach(([name, fn]) => {
console.log(`Testing ${name}...`);
assert({ fn: () => fn(1, 2, 3, 4, 5), expectation: 5 });
assert({ fn: () => fn(1, 3, 5, 2, 1), expectation: 5 });
assert({ fn: () => fn(), expectation: undefined });
assert({ fn: () => fn(1), expectation: 1 });
assert({ fn: () => fn([1, 2, 9]), expectation: 9 });
assert({ fn: () => fn(-40, -90, -114), expectation: -40 });
assert({
fn: () => fn([1, 9, [459, 932, [19], [20, 21]], 1005, [32, 1543, 900]]),
expectation: 1543,
});
assert({ fn: () => fn(0), expectation: 0 });
assert({ fn: () => fn(-4, [-90, 0], -3), expectation: 0 });
assert({
fn: () => fn(1.25, 9.3932, Math.PI, Number.MAX_SAFE_INTEGER),
expectation: Number.MAX_SAFE_INTEGER,
});
assert({
fn: () => fn(1, 10, [5, 9], [12, [900], [34, 56, 7600]], 34),
expectation: 7600,
});
assert({
fn: () => fn('hello', 'world', 'strings', ['are', 'fun']),
expectation: 'world',
});
});