DEV Community

Mitchell
Mitchell

Posted on • Edited on

Function - JavaScript Challenges

You can find all the code in this post in the repo Github.


Function related challenges


Arguments & parameters length

  • Arguments are the parameters you pass to an invoked function. You can access it by the "arguments" object.
  • Parameters are the parameters a function expects to receive. You can access it by the .length property of the function.
/**
 * @param {Function} fn
 * @return {number}
 */

// Time: O(1) | Space: O(1)
function functionLength(fn) {
  return fn.length;
}

// Usage example
function myFunction(a, b, c) {
  console.log(a, b, c);
}

console.log(functionLength(myFunction)); // => 3

/**
 * @param {...any} args
 * @return {number}
 */

// Time: O(1) | Space: O(1)
function numOfArguments(...args) {
  return arguments.length;
  // or
  // return args.length;
}

// Usage example
console.log(numOfArguments(1, 2, 3, 4, 5)); // => 5
console.log(numOfArguments()); // => 0
Enter fullscreen mode Exit fullscreen mode

Compose

The thing to notice is that the functions are invoked in the reverse order of passing.

/**
 * @param {...Functions} fns
 * @return Function
 */

// Time: O(n) | Space: O(1)
function compose(...fns) {
  return function (x) {
    let result = x;

    for (const fn of fns.reverse()) {
      result = fn(result);
    }

    return result;
  };
}

// Usage example
const add1 = (num) => num + 1;
const double = (num) => num * 2;
const subtract10 = (num) => num - 10;

const composedFn = compose(subtract10, double, add1);
console.log(composedFn(3)); // (3 + 1) * 2 - 10 => -2
Enter fullscreen mode Exit fullscreen mode

Currying

This challenge is about currying and partial applications.
The idea is to count the number of arguments, if it excels the number of parameters, then invoke the original function with arguments. Otherwise, return a curried function to continue to collect the remaining arguments.

/**
 * @param {Function} fn
 * @return {Function}
 */

// Time: O(1) | Space: O(1)
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }

    return curried.bind(this, ...args);
  };
}

// Usage example
// single parameter case
function add(a, b) {
  return a + b;
}

const curriedAdd = curry(add);
console.log(curriedAdd(3)(4)); // => 7
const alreadyAddedThree = curriedAdd(3);
console.log(alreadyAddedThree(4)); // => 7

// fixed parameters case
function addTwo(a, b) {
  return a + b;
}

const curriedAddTwo = curry(addTwo);
console.log(curriedAddTwo(3, 4)); // => 7
console.log(curriedAddTwo(3)(4)); // => 7
const alreadyAddedThreeB = curriedAdd(3);
console.log(alreadyAddedThreeB(4)); // => 7

//-------------------------------------------

/**
 * @param {Function} fn
 * @return {Function}
 */

// Time: O(1) | Space: O(1)
function curry(fn) {
  return function curried(...args) {
    const bindFn = curried.bind(this, ...args);
    bindFn[Symbol.toPrimitive] = () => fn.call(this, ...args);

    return bindFn;
  };
}

// Usage example
// non-fixed parameters case
function multiply(...numbers) {
  return numbers.reduce((a, b) => a * b, 1);
}

const curriedMultiply = curry(multiply);
const multiplyByThree = curriedMultiply(3);
console.log(multiplyByThree); // => 3
console.log(multiplyByThree(4)); // => 12

const multiplyByFifteen = multiplyByThree(5);
console.log(multiplyByFifteen); // => 15
console.log(multiplyByFifteen(2)); // => 30

console.log(curriedMultiply(1)(2)(3)(4)); // => 24
console.log(curriedMultiply(1, 2, 3, 4)); // => 24

//-------------------------------------------
// support placeholder "_"
// Time: O(1) | Space: O(1)
function curry(fn) {
  return function curried(...args) {
    const complete =
      args.length >= fn.length &&
      !args.slice(0, fn.length).includes(curry.placeholder);

    if (complete) {
      return fn.call(this, ...args);
    }

    return function (...newArgs) {
      const res = args.map((arg) =>
        arg === curry.placeholder && newArgs.length ? newArgs.shift() : arg
      );
      return curried(...res, ...newArgs);
    };
  };
}

curry.placeholder = Symbol();
Enter fullscreen mode Exit fullscreen mode

Memo

This challenge is about memorization which is a technique for storing expensive computing and saving resources.

You can use a cache Map to store the key-value pairs. Each time when invoking the function, check whether arguments are in the keys of cache, if there are, simply return the corresponding value. Otherwise, invoke the function with the new key, return the result, and store it in the cache.

/**
 * @param {Function} func
 * @return {Function}
 */

// Time: O(1) | Space: O(1)
function memoize(fn) {
  const cache = new Map();

  return function (arg) {
    if (cache.has(arg)) {
      return cache.get(arg);
    }

    const result = fn.call(this, arg);
    cache.set(arg, result);

    return result;
  };
}

// Usage example
function expensiveFunction(n) {
  console.log("Computing...");
  return n * 2;
}

// Create a memoized version of the function.
const memoizedExpensiveFunction = memoize(expensiveFunction);

// First call (computes and caches the result).
console.log(memoizedExpensiveFunction(5)); // => Computing... 10

// Second call with the same argument (returns the cached result).
console.log(memoizedExpensiveFunction(5)); // => 10

// Third call with a different argument (computes and caches the new result).
console.log(memoizedExpensiveFunction(10)); // => Computing... 20

// Fourth call with the same argument as the third call (returns the cached result).
console.log(memoizedExpensiveFunction(10)); // => 20

// ----------------------------------------
// When parameters could be array
/**
 * @param {Function} fn
 * @return {Function}
 */

// Time: O(1) | Space: O(1)
function memoize(fn) {
  const cache = new Map();

  return function (...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      return cache.get(key);
    }

    const result = fn.call(this, ...args);
    cache.set(key, result);

    return result;
  };
}

// Usage example
function expensiveMul(a, b) {
  console.log("Computing...");
  return a * b;
}

// Create a memoized version of the function.
const memoizedExpensiveMul = memoize(expensiveMul);

// First call (computes and caches the result).
console.log(memoizedExpensiveMul(3, 7)); // => Computing... 21

// Second call with the same argument (returns the cached result).
console.log(memoizedExpensiveMul(3, 7)); // => 21

// Third call with a different argument (computes and caches the new result).
console.log(memoizedExpensiveMul(5, 8)); // => Computing... 40

// Fourth call with the same argument as the third call (returns the cached result).
console.log(memoizedExpensiveMul(5, 8)); // => 40
Enter fullscreen mode Exit fullscreen mode

Partial

This challenge is about partial application.

The solution needs to support placeholder _.
The idea is to check the passed parameters, if it is a placeholder, then skip it.

/**
 * @param {Function} fn
 * @param {any[]} args
 * @returns {Function}
 */

// Time: O(1) | Space: O(1)
function partial(fn, ...args) {
  return function (...restArgs) {
    const copyArgs = args.map((arg) => {
      return arg === partial.placeholder ? restArgs.shift() : arg;
    });

    return fn.call(this, ...copyArgs, ...restArgs);
  };
}

partial.placeholder = Symbol();

// Usage example
const func = (...args) => args;
const func123 = partial(func, 1, 2, 3);
console.log(func123(4)); // => [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Reference

Top comments (0)