Post

JavaScript 30 problems with solutions

30 Days of JavaScript

So there is this study plan on leetcode titled “30 Days of JavaScript”, it mostly comprises of simple problems that focus on the functional nature of JavaScript. Following are the solutions to the listed problems along with a brief discussion.

Create Hello World Function

1
2
3
4
5
6
var createHelloWorld = function() {
    
    return function(...args) {
        
    }
};
  • In the above code a function is being declared as a ‘var’, this is because in functional programming paradigm, functions are first class objects.
  • That means they can be thrown around to other funtions, can be mapped on other funtions, can be passed as a parameter to other funtions.
  • In this the solution is rather simple.
1
2
3
4
5
var createHelloWorld = function() {
    
    return function(...args) { return "Hello World";        
    }
};

Counter

Now this problem is interesting, the solution looks like this

1
2
3
4
5
var createCounter = function(n) {
    return function() {
        return n++;
    };
};
  • The interesting part here is how n lives even though no memory got allocated explicitly to preserve it, this is called a closure, a function returning a function, the ability for the inner function to maintain it’s variable is a feature of JS closures.
  • In Cpp similar behaviour would require us to somehow assign where the variable lives, as follows.
#include<iostream>
#include<functional>

class Counter {
private:
  int _n;
public:
  Counter(int initValue) : _n(initValue) {} //Member init list
  
  std::functiona<int()> createCounter() {
  return [this]() -> {return n++};
  }
};
  • See how an object exists that is getting passed to the inner function via “this”, that’s what closure behave like in JavaScript.

To Be Or Not To Be

  • There is an attempt to be philosophical here, I am gonna choose to be and solve this question.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * @param {string} val
 * @return {Object}
 */
var expect = function(val) {
    // In JS we can treat functions as first class obejcts

    return {
        toBe: (cmp) => {
            if(val !== cmp) throw new Error("Not Equal");
            else return true;
        },

        notToBe: (val2) => {
            if(val === val2) throw new Error("Equal");
            else return true;
        }
    }
    
};

  • The function returns an object of functions!, think about that.
  • What is going on is that imagine the wrapping funtion to be object, one its on we have the “val”, other values will be passed later on, what it is returning is a object which is “aware” of the “val”, and needs the other values to funtion (which we are passing in the calls).

Counter II

  • It’s a combination of problems before, this time one particular value needs to stay constant and stored somewhere in order to be used later, so we just create that variable and do exactly that.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * @param {integer} init
 * @return { increment: Function, decrement: Function, reset: Function }
 */
var createCounter = function(init) {
    const initValue = init;

    return {
        increment: () => {return ++init;}, //The "++" is like C++
        decrement: () => {return --init;},
        reset: () => {
            init = initValue;
            return init}
    }
    
};

/**
 * const counter = createCounter(5)
 * counter.increment(); // 6
 * counter.reset(); // 5
 * counter.decrement(); // 4
 */

Appy Transform Over Each Element in Array

  • Following is just a simple implementation of arra.map
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * @param {number[]} arr
 * @param {Function} fn
 * @return {number[]}
 */
var map = function(arr, fn) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    result.push(fn(arr[i], i));
  }
  return result;
};

Filter Elements From Array

  • In this problem we just simply evaluate and check if the element should be pushed into the return array, if the answer is yes, we push if not we continue.
  • It’s a simple question to show you how array.filter works.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * @param {number[]} arr
 * @param {Function} fn
 * @return {number[]}
 */
var filter = function(arr, fn) {
    const newArr = [];
    for(let i = 0; i<arr.length; i++) {
        if(fn(arr[i],i)) {
            newArr.push(arr[i]);
        }
    }
    return newArr;
 
};
  • What perhaps is more interesting is how this is a functional approach (not the code), but the idea of using filters. In Haskell for example you can do
xs = [1,2,3,4,5,6,7,8,9,...] --This ... is actual syntax 
x_prime = [x <- xs | x > 5] -- This defination is simlar to how sets are defined in maths.
  • Here the condition is acting as “filter”, generally you can have more elegant looking implementations when such filtering is required when using funtional paradigm rather than loops and such in OOP.

Array Reduce Transformation

  • This problem just builds a little but on the concepts we have already seen, here is a simple solution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * @param {number[]} nums
 * @param {Function} fn
 * @param {number} init
 * @return {number}
 */
var reduce = function(nums, fn, init) {
    if(nums.length == 0)
        return init;
    let val = init;
    for(let i =0; i<nums.length; i++) {
        val = fn(val,nums[i]);
    }
    return val;
    
};

Function Composition

  • Function compositin is nothing but the simple math fact that if you apply f(x) on g(x) on h(x) then the result is equal to F(x) = f(g(h(x)))
  • This can be written in code as follows
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * @param {Function[]} functions
 * @return {Function}
 */
var compose = function(funcs) {
    
    return function(x) {
        return funcs.reduceRight((acc, func) => func(acc), x);
    };
};

/**
 * const fn = compose([x => x + 1, x => 2 * x])
 * fn(4) // 9
 */
  • Following is the implementation of similar concept in C++

{% raw %}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <vector>
#include <functional>

// Function to compose a list of functions
std::function<int(int)> composeFunctions(const std::vector<std::function<int(int)>>& funcs) {
    return [funcs](int x) {
        int result = x;
        // Iterate through the functions in reverse order
        for (auto it = funcs.rbegin(); it != funcs.rend(); ++it) {
            result = (*it)(result);
        }
        return result;
    };
}

int main() {
    // Example functions
    auto f1 = [](int x) { return x + 2; };
    auto f2 = [](int x) { return x * 3; };
    auto f3 = [](int x) { return x - 5; };

    // Compose the functions
    auto composedFn = composeFunctions({f1, f2, f3});

    // Use the composed function
    std::cout << "Result: " << composedFn(5) << std::endl; // This would compute f1(f2(f3(5)))

    return 0;
}

Return Length Of Argument

  • I mean, there isn’t much to this one tbh.
1
2
3
4
5
6
7
8
9
10
11
/**
 * @param {...(null|boolean|number|string|Array|Object)} args
 * @return {number}
 */
var argumentsLength = function(...args) {
    return args.length;  
};

/**
 * argumentsLength(1, 2, 3); // 3
 */

Allow One Function Call

  • Here is the solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * @param {Function} fn
 * @return {Function}
 */
var once = function(fn) {
    let called = false;
    let result;

    return function(...args) {
        if (!called) {
            result = fn.apply(this, args);
            called = true;
            return result;
        }
        return undefined;
    }
};

/**
 * let fn = (a,b,c) => (a + b + c)
 * let onceFn = once(fn)
 *
 * onceFn(1,2,3); // 6
 * onceFn(2,3,6); // returns undefined without calling fn
 */
  • You can omit the last line, as when nothing is specified JS by default returns undefined.

Memoize

  • It’s just the heading, just create an object to store the result of function calls, if the answer has been calculated already return that, or if not calculate and store it for later calls.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function memoize(fn) {
    const cache = {};

    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) {
            return cache[key];
        } else {
            const result = fn.apply(this, args);
            cache[key] = result;
            return result;
        }
    };
}

Add Two Promises

  • In this we just wait till both promises are complete and then just return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function(promise1, promise2) {
    const  [val1,val2] = await Promise.all([promise1,promise2]);
    return val1+val2;
};

/**
 * addTwoPromises(Promise.resolve(2), Promise.resolve(2))
 *   .then(console.log); // 4
 */

Sleep

  • I would like to tbh.
  • But let’s solve this problem first
  • We just have to create a promise and wait for it, we pass the waiting time to setTimeout for that to happen after creating a new promise.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * @param {number} millis
 * @return {Promise}
 */
async function sleep(millis) {
    await new Promise(resolve => setTimeout(resolve,millis));
}

/** 
 * let t = Date.now()
 * sleep(100).then(() => console.log(Date.now() - t)) // 100
 */```


## TimeOut Cancellation

- I need to think a little about this one. update coming soon.
- The solution is below

```js
const cancellable = function(fn, args, t) {
    const cancelFn = function (){
      clearTimeout(timer);
  };
  const timer = setTimeout(()=>{
      fn(...args)
  }, t);
  return cancelFn ;
};

Interval Cancellation

  • Same as above, but with different goal.
1
2
3
4
5
6
7
8
var cancellable = function(fn, args, t) {
    fn(...args);
    const timer = setInterval(() => fn(...args), t);

    const cancelFn = () => clearInterval(timer);
    return cancelFn;
};

Promise Time Limit

  • We just implement what we have seen already with a different syntax and goal.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var timeLimit = function(fn, t) {
  return async function(...args) {
    return new Promise((delayresolve, reject) => {
      const timeoutId = setTimeout(() => {
        clearTimeout(timeoutId);
        reject("Time Limit Exceeded");
      }, t);

      fn(...args)
        .then((result) => {
          clearTimeout(timeoutId);
          delayresolve(result);
        })
        .catch((error) => {
          clearTimeout(timeoutId);
          reject(error);
        });
    });
  };
};

Cache with Time Limit

  • We just use the concepts learned and create and object to do our bidding.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var TimeLimitedCache = function() {
    this.memory= new Map();
};

/** 
 * @param {number} key
 * @param {number} value
 * @param {number} duration time until expiration in ms
 * @return {boolean} if un-expired key already existed
 */
TimeLimitedCache.prototype.set = function(key, value, duration) {
    let isthere=false;
    if(this.memory.has(key)){
        isthere=true;
        clearTimeout(this.memory.get(key)[1]);
    }
    const timeoutID=setTimeout(()=>{
        this.memory.delete(key);
    },duration)
    this.memory.set(key,[value,timeoutID]);
    return isthere;
};

/** 
 * @param {number} key
 * @return {number} value associated with key
 */
TimeLimitedCache.prototype.get = function(key) {
    if(this.memory.has(key)) return this.memory.get(key)[0];
    return -1;
};

/** 
 * @return {number} count of non-expired keys
 */
TimeLimitedCache.prototype.count = function() {
    return this.memory.size;
};

/**
 * const timeLimitedCache = new TimeLimitedCache()
 * timeLimitedCache.set(1, 42, 1000); // false
 * timeLimitedCache.get(1) // 42
 * timeLimitedCache.count() // 1
 */

Debounce

  • Wait, didn’t we already solve this too?
1
2
3
4
5
6
7
var debounce = function(fn, t = 1000) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), t);
    }
};
  • This list could have been better you know.

Execute Asynchronous Functionsl Parallely

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
 * @param {Array<Function>} functions
 * @return {Promise<any>}
 */
var promiseAll = function(functions) {
      return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;
    const totalFunctions = functions.length;

    if (totalFunctions === 0) {
      resolve(results); // Immediately resolve if the array is empty
      return;
    }

    functions.forEach((func, index) => {
      // Execute each function which returns a promise
      func()
        .then(result => {
          results[index] = result; // Store the result at the corresponding index
          completed += 1; // Increment the count of completed promises
          if (completed === totalFunctions) {
            resolve(results); // Resolve the main promise when all functions have completed
          }
        })
        .catch(error => {
          reject(error); // Reject the main promise if any promise fails
        });
    });
  });
};

/**
 * const promise = promiseAll([() => new Promise(res => res(42))])
 * promise.then(console.log); // [42]
 */```



## Is Object Empty

```js
/**
 * @param {Object|Array} obj
 * @return {boolean}
 */
var isEmpty = function(obj) {
    return Object.keys(obj).length === 0;
};
  • Yup, that was it!

Chunk Array

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * @param {Array} arr
 * @param {number} size
 * @return {Array}
 */
var chunk = function(arr, size) {
    const chunkedArr = [];
  for (let i = 0; i < arr.length; i += size) {
    chunkedArr.push(arr.slice(i, i + size));
  }
  return chunkedArr;
};
  • Yeah, not much to this one either.

Array prototype Last

  • In this we just update the base class and add a new functionality to it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * @return {null|boolean|number|string|Array|Object}
 */
Array.prototype.last = function() {
    if (this.length === 0) {
      return -1;
    }
    return this[this.length - 1];
  };


/**
 * const arr = [1, 2, 3];
 * arr.last(); // 3
 */

Group By

  • Same concept as above but now for groupBy functionality.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * @param {Function} fn
 * @return {Object}
 */
Array.prototype.groupBy = function(fn) {
        return this.reduce((acc, item) => {
      const key = fn(item);
      if (!acc[key]) {
        acc[key] = [];
      }
      acc[key].push(item);
      return acc;
    }, {}); 
  };


/**
 * [1,2,3].groupBy(String) // {"1":[1],"2":[2],"3":[3]}
 */

Sort By

  • I remeber when I saw a backend engineer write code like this, I didn’t know you could do things like this.
1
2
3
4
5
6
7
8
9
10
/**
 * @param {Array} arr
 * @param {Function} fn
 * @return {Array}
 */
var sortBy = function(arr, fn) {
    const clonedArr = [...arr];
    clonedArr.sort((a,b) => fn(a) - fn(b));
    return clonedArr;
};

Join Two Arrays By ID

  • Just join them and sort them, and return!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * @param {Array} arr1
 * @param {Array} arr2
 * @return {Array}
 */
var join = function(arr1, arr2) {
  const merged = [...arr1, ...arr2];

  const mergedMap = new Map();

  merged.forEach(obj => {
    if (mergedMap.has(obj.id)) {
      mergedMap.set(obj.id, { ...mergedMap.get(obj.id), ...obj });
    } else {
      mergedMap.set(obj.id, obj);
    }
  });

  const joinedArray = Array.from(mergedMap.values());

  joinedArray.sort((a, b) => a.id - b.id);

  return joinedArray;
};

Flatten Depply Nested Array

  • Following is a recursive solution, it exceeds time limit for longer inputs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * @param {Array} arr
 * @param {number} depth
 * @return {Array}
 */
var flat = function (arr, n,cd=0) {
    let result = [];

  arr.forEach(item => {
    if (Array.isArray(item) && cd < n) {
      result = result.concat(flat(item, n, cd + 1));
    } else {
      result.push(item);
    }

  });    return result;

};
  • However with memoization it works fine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @param {any[]} arr
 * @param {number} depth
 * @return {any[]}
 */
var flat = function (arr, n) {
    if(n==0) return arr;
    let result = [];
    const traverse = (a, n) => {
        for(let i in a) {
            if(n>0 && Array.isArray(a[i]))
                traverse(a[i], n-1)
            else
                result.push(a[i])
        }
    }
    traverse(arr, n);
    return result;
};

Compact Object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var compactObject = function(obj) {
    if (typeof obj === 'object') {
        if (Array.isArray(obj)) {
            const tempArray = [];
            for (let index = 0; index < obj.length; index++) {
                if (Boolean(obj[index])) {
                    tempArray.push(compactObject(obj[index]));
                }
            }
            return tempArray;
        } else {
            const tempObject = {};
            for (const key in obj) {
                if (Boolean(obj[key])) {
                    tempObject[key] = compactObject(obj[key]);
                }
            }
            return tempObject;
        }
    }
    return obj;
};

Event Emitter

  • I sometimes forget that JS has classes too, cause I never think of it as an OOP langugage.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class EventEmitter {
    constructor () {
        this.subscriptions = new Map();
    }

    /**
     * @param {string} eventName
     * @param {Function} callback
     * @return {Object}
     */
    subscribe(eventName, callback) {
        const id = Symbol(callback);
        this.subscriptions.set(eventName, this.subscriptions.has(eventName)
            ? [...this.subscriptions.get(eventName), { id, callback }]
            : [{ id, callback }]
        );

        return {
            unsubscribe: () =>  this.subscriptions.set(eventName, this.subscriptions.get(eventName).filter(({ id: subId}) => subId !== id))
        };
    }
    
    /**
     * @param {string} eventName
     * @param {Array} args
     * @return {Array}
     */
    emit(eventName, args = []) {
        return (this.subscriptions.get(eventName) || []).map(({ callback }) => callback(...args));
    }
}

/**
 * const emitter = new EventEmitter();
 *
 * // Subscribe to the onClick event with onClickCallback
 * function onClickCallback() { return 99 }
 * const sub = emitter.subscribe('onClick', onClickCallback);
 *
 * emitter.emit('onClick'); // [99]
 * sub.unsubscribe(); // undefined
 * emitter.emit('onClick'); // []
 */

Array Wrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * @param {number[]} nums
 * @return {void}
 */
var ArrayWrapper = function(nums) {
    this.array = nums;
    
};

/**
 * @return {number}
 */
ArrayWrapper.prototype.valueOf = function() {
    return this.array.reduce((p,c) => p+c,0)
    
}

/**
 * @return {string}
 */
ArrayWrapper.prototype.toString = function() {
    return JSON.stringify(this.array);
}

/**
 * const obj1 = new ArrayWrapper([1,2]);
 * const obj2 = new ArrayWrapper([3,4]);
 * obj1 + obj2; // 10
 * String(obj1); // "[1,2]"
 * String(obj2); // "[3,4]"
 */

Calculator With Method Chaining

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Calculator {

    /** 
     * @param {number} value
     */
    constructor(value) {
        this.result = value
    }

    /** 
     * @param {number} value
     * @return {Calculator}
     */
    add(value){
        this.result += value;
        return this
    }

    /** 
     * @param {number} value
     * @return {Calculator}
     */
    subtract(value){
        this.result -= value
        return this
    }

    /** 
     * @param {number} value
     * @return {Calculator}
     */  
    multiply(value) {
        this.result *= value
        return this
    }

    /** 
     * @param {number} value
     * @return {Calculator}
     */
    divide(value) {
        if(value === 0 ) throw new Error("Division by zero is not allowed") 
        this.result  = this.result / value
        return this
  }

    /** 
     * @param {number} value
     * @return {Calculator}
     */
    power(value) {
        this.result = Math.pow(this.result,value)
        return this
    }

    /** 
     * @return {number}
     */
    getResult() {
        return this.result
    }
}
This post is licensed under CC BY 4.0 by the author.