30 Days of JavaScript on LeetCode

2667. Create Hello World Function

/**
 * @return {Function}
 */
var createHelloWorld = function() {
    return function(...args) {
        return "Hello World"        
    }
};

/**
 * const f = createHelloWorld();
 * f(); // "Hello World"
 */

2620. Counter

/**
 * @param {number} n
 * @return {Function} counter
 */
var createCounter = function(n) {
    let counter = n;
    return function() {
        return counter++
    };
};

/** 
 * const counter = createCounter(10)
 * counter() // 10
 * counter() // 11
 * counter() // 12
 */

2704. To Be Or Not To Be

/**
 * @param {string} val
 * @return {Object}
 */
var expect = function(val) {
    return {
        toBe : (arg) => {
            if (val === arg){
                return true
            } 
            throw "Not Equal"            
        },
        notToBe : (arg) => {
            if (val !== arg) {
                return true
            }
            throw "Equal"
        }
    }    
};

/**
 * expect(5).toBe(5); // true
 * expect(5).notToBe(5); // throws "Equal"
 */

2665. Counter II

/**
 * @param {integer} init
 * @return { increment: Function, decrement: Function, reset: Function }
 */
var original;
var createCounter = function(init) {
    original = init
    return {
        increment: () => {
            return init += 1
        },
        decrement: () => {
            return init -= 1
        }, 
        reset: () => {
            init = original
            return init
        }
    }    
};

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

2635. Apply Transform Over Each Element in Array

/**
 * @param {number[]} arr
 * @param {Function} fn
 * @return {number[]}
 */
var map = function(arr, fn) {
    for(let [i, ele] of arr.entries()) { 
        ele = fn(ele, i)
    }
    return arr
};

2634. Filter Elements from Array

/**
 * @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;
};

2626. Array Reduce Transformation

/**
 * @param {number[]} nums
 * @param {Function} fn
 * @param {number} init
 * @return {number}
 */
// var reduce = function(nums, fn, init) {
//     let output = init;
//     for(let i = 0; i < nums.length; i++) {
//         output = fn(output, nums[i])
//     }
//     return output
// };

var reduce = function(arr, fn, initialVal) {
  let accumulator = initialVal;
  for (const element of arr) {
      accumulator = fn(accumulator, element);
  }
  return accumulator;
};

2629. Function Composition

/**
 * @param {Function[]} functions
 * @return {Function}
 */
var compose = function(functions) {
    return function(x) {
        let output = x
        for(func of functions.reverse()){
            output = func(output)
        }
        return output        
    }
};

/**
 * const fn = compose([x => x + 1, x => 2 * x])
 * fn(4) // 9
 */

2703. Return Length of Arguments Passed

/**
 * @return {number}
 */
var argumentsLength = function(...args) {
    return [...args].length
};

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

2666. Allow One Function Call

/**
 * @param {Function} fn
 * @return {Function}
 */
var once = function(fn) {
    let called = false
    return function(...args){
        if(!called){
            called = true
            return fn(...args)
        }
    }
};

/**
 * 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
 */

2623. Memoize

/**
 * @param {Function} fn
 */
function memoize(fn) {
    const cache = {}
    return function(...args) {
        const key  = JSON.stringify(args)
        if(key in cache){
            return cache[key]
        }
        cache[key] = fn(...args)
        return cache[key]        
    }
}


/** 
 * let callCount = 0;
 * const memoizedFn = memoize(function (a, b) {
 *     callCount += 1;
 *   return a + b;
 * })
 * memoizedFn(2, 3) // 5
 * memoizedFn(2, 3) // 5
 * console.log(callCount) // 1 
 */

2723. Add Two Promises

/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function(promise1, promise2) {
    return promise1.then(a => {
        return promise2.then(b => {
            return a + b
        })
    })
};

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

2621. Sleep

/**
 * @param {number} millis
 */
async function sleep(millis) {
    return new Promise(resolve => setTimeout(resolve, millis));   
}

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

2715. Timeout Cancellation

/**
 * @param {Function} fn
 * @param {Array} args
 * @param {number} t
 * @return {Function}
 */
var cancellable = function(fn, args, t) {
    const timer = setTimeout(fn, t, ...args)

    return () => {
        clearTimeout(timer)
    }
};

/**
 *  const result = []
 *
 *  const fn = (x) => x * 5
 *  const args = [2], t = 20, cancelT = 50
 *
 *  const start = performance.now() 
 *
 *  const log = (...argsArr) => {
 *      const diff = Math.floor(performance.now() - start);
 *      result.push({"time": diff, "returned": fn(...argsArr))
 *  }
 *       
 *  const cancel = cancellable(log, args, t);
 *
 *  const maxT = Math.max(t, cancelT)
 *           
 *  setTimeout(() => {
 *     cancel()
 *  }, cancelT)
 *
 *  setTimeout(() => {
 *     console.log(result) // [{"time":20,"returned":10}]
 *  }, maxT + 15)
 */

2725. Interval Cancellation

/**
 * @param {Function} fn
 * @param {Array} args
 * @param {number} t
 * @return {Function}
 */
var cancellable = function(fn, args, t) {
  fn(...args)
  const timer = setInterval(fn, t, ...args)  
  return () => {
    clearInterval(timer)      
  }
};

/**
 *  const result = []
 *
 *  const fn = (x) => x * 2
 *  const args = [4], t = 20, cancelT = 110
 *
 *  const start = performance.now()
 *
 *  const log = (...argsArr) => {
 *      const diff = Math.floor(performance.now() - start)
 *      result.push({"time": diff, "returned": fn(...argsArr)})
 *  }
 *       
 *  const cancel = cancellable(log, args, t);
 *
 *  setTimeout(() => {
 *     cancel()
 *  }, cancelT)
 *   
 *  setTimeout(() => {
 *    console.log(result)  // [
 *                         //      {"time":0,"returned":8},
 *                         //      {"time":20,"returned":8},
 *                         //      {"time":40,"returned":8},           
 *                         //      {"time":60,"returned":8},
 *                         //      {"time":80,"returned":8},
 *                         //      {"time":100,"returned":8}
 *                         //  ]
 *  }, cancelT + t + 15)    
 */

2637. Promise Time Limit

/**
 * @param {Function} fn
 * @param {number} t
 * @return {Function}
 */
var timeLimit = function(fn, t) {
    return async function(...args) {
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
             reject('Time Limit Exceeded')   
            }, t)
            fn(...args).then((value)=>{
                resolve(value)
            }).catch((err)=>{
                reject(err)
            })          
        })

    }
};

/**
 * const limited = timeLimit((t) => new Promise(res => setTimeout(res, t)), 100);
 * limited(150).catch(console.log) // "Time Limit Exceeded" at t=100ms
 */

2622. Cache With Time Limit

var TimeLimitedCache = function() {
    this.cache = {}
};

/** 
 * @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) {
    const currentTs = Date.now()
    if (key in this.cache) {
        const [prevVal, dur, ts] = this.cache[key]        
        if (ts + dur < currentTs){
        }
        this.cache[key] = [value, duration, currentTs]
        return true
    } 
    this.cache[key] = [value, duration, Date.now()]    
    return false
};

/** 
 * @param {number} key
 * @return {number} value associated with key
 */
TimeLimitedCache.prototype.get = function(key) {
    if(!(key in this.cache)) {
        return -1
    }
    const [value, dur, ts] = this.cache[key]
    const currentTs = Date.now()
    if (ts + dur > currentTs){
        return value
    }
    return -1    
};

/** 
 * @return {number} count of non-expired keys
 */
TimeLimitedCache.prototype.count = function() {
    let count = 0
    Object.keys(this.cache).forEach((key)=>{
        const [value, dur, ts] = this.cache[key]
        const currentTs = Date.now()
        if (ts + dur > currentTs){
            count += 1
        }
    })
    return count
};

/**
 * Your TimeLimitedCache object will be instantiated and called as such:
 * var obj = new TimeLimitedCache()
 * obj.set(1, 42, 1000); // false
 * obj.get(1) // 42
 * obj.count() // 1
 */

2627. Debounce

/**
 * @param {Function} fn
 * @param {number} t milliseconds
 * @return {Function}
 */
var debounce = function(fn, t) {
    var timer = null
    return function(...args) {
        clearTimeout(timer)       
        timer =  setTimeout(fn, t, ...args)
    }
};

/**
 * const log = debounce(console.log, 100);
 * log('Hello'); // cancelled
 * log('Hello'); // cancelled
 * log('Hello'); // Logged at t=100ms
 */

2721. Execute Asynchronous Functions in Parallel

/**
 * @param {Array<Function>} functions
 * @return {Promise<any>}
 */
var promiseAll = async function(functions) {
    return new Promise(async (resolve, reject) => {
        if (functions.length === []){
            resolve([])
            return
        }

        // const res = new Array(functions.length).fill(null)
        const res = []

        for(const prom of functions) {
            try {
                const subRes = await prom()
                res.push(subRes)
            } catch(err) {
                // Reject if any promise got rejected
                reject(err)
            }
        }
        // Resolve if all resolved
        resolve(res)               
    })
};

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

2727. Is Object Empty

/**
 * @param {Object | Array} obj
 * @return {boolean}
 */
var isEmpty = function(obj) {
     return Object.keys(obj).length === 0
};

2677. Chunk Array

/**
 * @param {Array} arr
 * @param {number} size
 * @return {Array[]}
 */
var chunk = function(arr, size) {
  l = arr.length
  // find number of iterations needed
  n = parseInt(l / size) 
  let ans = []
  for(let i = 0; i < n; i++ ){
      ans.push(arr.slice(i * size, (i * size) + size))
  } 
  const m = l % size
  if(m > 0) {
    ans.push(arr.slice(-m))
  } 

  return ans
};

2619. Array Prototype Last

Array.prototype.last = function() {
    if (this.length > 0) {
        return this[this.length - 1]
    }
    return -1    
};

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

2631. Group By

/**
 * @param {Function} fn
 * @return {Array}
 */
Array.prototype.groupBy = function(fn) {
    const output = {}
    this.forEach((x) => {
        const key = fn(x)
        if(key in output) {
            output[key].push(x)
        } else {
            output[key] = [x]
        }
    })

    return output
};

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

2724. Sort By

/**
 * @param {Array} arr
 * @param {Function} fn
 * @return {Array}
 */
var sortBy = function(arr, fn) {
    return arr.sort((a, b) => {
        return (fn(a) < fn(b)) ? -1 : 1;        
    });
};

2722. Join Two Arrays by ID

/**
 * @param {Array} arr1
 * @param {Array} arr2
 * @return {Array}
 */
var join = function(arr1, arr2) {
    const data = {}
    for(const ele of arr1){
        data[ele.id] = ele
    }

    for(const ele of arr2){
        if(ele.id in data){
            data[ele.id] = Object.assign(data[ele.id], ele)
        } else {
            data[ele.id] = ele
        }
    }
    return Object.values(data)

};

2625. Flatten Deeply Nested Array

/**
 * @param {any[]} arr
 * @param {number} depth
 * @return {any[]}
 */
var flat = function (arr, n) {
    const output = []
    const rec = (arg, d) => {   
       for(const val of arg){
            // Alternate 1 : typeof(val) === 'object'
            // Alternate 2 : Array.isArray(val)
           if(val instanceof Array && d < n){
               rec(val, d + 1)
           } else {
               output.push(val)
           }
       }
       return output
    }

    return rec(arr, 0)
};

2705. Compact Object

/**
 * @param {Object|Array} obj
 * @return {Object|Array}
 */
var compactObject = function(obj) {
    if(!obj) return false
    if (typeof obj != 'object') return obj

    let compactArr = []
    if (obj instanceof Array){
        for(const ele of obj){
            const sub = compactObject(ele)
            if (sub) {
                compactArr.push(sub)
            }
        }
        return compactArr
    }

    const compactObj = {}
    for(const key in obj) {
        const sub = compactObject(obj[key])
        if(sub){
            compactObj[key] = sub
        }
    }
    return compactObj
};

2694. Event Emitter

class EventEmitter {
    constructor() {
      this.subscriptions = {}
    }

    subscribe(event, cb) {
      console.log('check', typeof cb)
      if (event in this.subscriptions) {
        this.subscriptions[event].push(cb)
      } else {
        this.subscriptions[event] = [cb]
      }
      const index = this.subscriptions[event].length

    return {
        unsubscribe: () => {          
          this.subscriptions[event].splice(index - 1, 1)
          return undefined
        }
    };
  }

  emit(event, args = []) {
    const cb = this.subscriptions[event]
    let output = []
    if(cb){
      for(const c of cb) { 
          output.push(c(...args))
      }

    }          
    return output
  }
}

/**
 * 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'); // []
 */

2695. Array Wrapper

/**
 * @param {number[]} nums
 */
var ArrayWrapper = function(nums) {
    this.nums = nums
};

ArrayWrapper.prototype.valueOf = function() {
    let s = 0
    this.nums.forEach(e => {
        s += e
    })
    return s
}

ArrayWrapper.prototype.toString = function() {
    return "["+this.nums.toString()+"]"
}

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

2726. Calculator with Method Chaining

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 "Division by zero is not allowed" 
    }
    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
  }
}