let js


Convert JSON String to Object

Problem Statement:

Given a string representing a JSON object, convert it to a JavaScript object.

Solution:

// Parse the JSON string into an object
const jsonObject = JSON.parse(jsonString);

Breakdown:

  • JSON.parse(): This is a built-in JavaScript function that converts a JSON string into an object. It can only be used to parse valid JSON strings. If the string is invalid, it will throw an error.

Example:

// JSON string
const jsonString = '{"name": "John", "age": 30, "occupation": "Software Engineer"}';

// Parse the JSON string
const jsonObject = JSON.parse(jsonString);

// Access the properties of the object
console.log(jsonObject.name); // John
console.log(jsonObject.age); // 30
console.log(jsonObject.occupation); // Software Engineer

Applications in the Real World:

  • Parsing data from web APIs that return responses in JSON format.

  • Storing and retrieving data from local storage in JSON format.

  • Configuring web applications using JSON configuration files.

  • Data exchange between different systems or applications that use JSON as a data format.


Compact Object

Compact Object

Problem:

You have an object with many key-value pairs. You want to reduce its size to save memory or bandwidth.

Example:

const myObj = {
  name: 'John',
  age: 30,
  address: '123 Main Street',
  city: 'New York',
  state: 'NY',
  zip: '10001',
  phone: '123-456-7890'
};

Solution:

There are several ways to compact an object:

  • Remove unnecessary properties: Delete any properties that are not needed.

delete myObj.phone;
delete myObj.city;
  • Use a smaller data type: If possible, use a smaller data type for the values. For example, you can use Number instead of String for numbers.

myObj.age = 30; // Change from string to number
myObj.zip = 10001; // Change from string to number
  • Use a compression algorithm: There are libraries available that can compress objects using various algorithms.

Simplified Explanation:

Imagine you have a box full of stuff. To make the box smaller, you can:

  • Remove items you don't need: Take out any items that you don't plan to use.

  • Make items smaller: Use smaller containers for items, like a smaller box for small items.

  • Vacuum-seal the box: This compresses the contents of the box, making it smaller.

Real-World Applications:

Compact objects are useful in various scenarios:

  • Data transmission: Reducing object size saves bandwidth when transmitting data over the internet.

  • Data storage: Smaller objects require less storage space, saving memory and reducing costs.

  • Performance optimization: Smaller objects can be processed more efficiently, leading to faster performance.


Repeat String

LeetCode Problem: Repeat String

Problem Statement:

Given a string and an integer k, repeat the string k times.

Example:

Input: str = "abc", k = 3
Output: "abcabcabc"

Simple and Performant Solution in JavaScript:

/**
 * Repeat the given string k times.
 *
 * @param {string} str The input string.
 * @param {number} k The number of times to repeat the string.
 * @return {string} The repeated string.
 */
const repeatString = (str, k) => {
  let result = "";
  for (let i = 0; i < k; i++) {
    result += str;
  }
  return result;
};

Breakdown of the Solution:

  1. Initialization: Initialize a variable result to an empty string.

  2. Loop: Use a for loop to iterate k times.

  3. Concatenation: For each iteration, append the original string str to the result.

  4. Return: Return the final repeated string.

Real-World Application:

String repetition is commonly used in various applications, such as:

  • Formatting text to fit specific alignments or widths.

  • Creating repeated patterns or borders.

  • Generating placeholder text for testing purposes.

  • Encrypting or encoding data using repeated keys.

Example Implementation:

// Repeat the string "Hello" 5 times
const repeatedString = repeatString("Hello", 5);
console.log(repeatedString); // Output: "HelloHelloHelloHelloHello"

Array of Objects to Matrix

Array of Objects to Matrix

Given an array of objects, convert it to a matrix.

For example:

Input:
[
  { name: 'A', value: 1 },
  { name: 'B', value: 2 },
  { name: 'C', value: 3 },
  { name: 'D', value: 4 },
  { name: 'E', value: 5 }
]
Output:
[
  [ 'A', 'B', 'C', 'D', 'E' ],
  [ 1, 2, 3, 4, 5 ]
]

Solution

We can use the reduce() method to convert the array of objects to a matrix. The reduce() method takes a callback function that takes two parameters: the previous value and the current value. We can use the previous value to store the names of the objects and the current value to store the values of the objects.

const array = [
  { name: 'A', value: 1 },
  { name: 'B', value: 2 },
  { name: 'C', value: 3 },
  { name: 'D', value: 4 },
  { name: 'E', value: 5 }
];

const matrix = array.reduce((prev, curr) => {
  prev[0].push(curr.name);
  prev[1].push(curr.value);
  return prev;
}, [[], []]);

Applications

This problem can be applied to any situation where you need to convert an array of objects to a matrix. For example, you could use it to:

  • Create a table of data

  • Generate a report

  • Visualize data

Potential Applications in Real World

  • Data visualization: Convert an array of objects representing data points into a matrix for easy visualization in a chart or table.

  • Database management: Transform a list of database records into a matrix for efficient storage and retrieval.

  • Machine learning: Prepare an array of features and labels as a matrix for input to a machine learning algorithm.

  • Data analysis: Convert a collection of objects containing financial data into a matrix to perform calculations and generate reports.


Create Object from Two Arrays

Problem Statement

Given two arrays nums1 and nums2, return a new array that contains the elements from both arrays.

Example:

const nums1 = [1, 2, 3];
const nums2 = [4, 5, 6];
// Output: [1, 2, 3, 4, 5, 6]

Solution

Method 1: Using the Spread Operator

The spread operator (...) can be used to spread the elements of an array into another array.

const nums3 = [...nums1, ...nums2];
return nums3;

Method 2: Using the concat() Method

The concat() method can be used to concatenate two or more arrays.

const nums3 = nums1.concat(nums2);
return nums3;

Performance Considerations

Both methods have the same time complexity of O(n), where n is the total number of elements in the two arrays. However, the spread operator is generally considered to be more performant because it does not require creating a new array object.

Applications

Merging two arrays is a common operation in programming. It can be used in various applications, such as:

  • Combining data from different sources

  • Sorting and filtering data

  • Creating a new array with a specific order or structure


Factorial Generator

Factorial Generator

Problem:

Given a non-negative integer n, calculate its factorial.

Factorial of a number: The factorial of a number n, denoted by n!, is the product of all positive integers less than or equal to n.

Example:

  • Factorial of 5 (5!) = 5 * 4 * 3 * 2 * 1 = 120

Implementation:

Iterative Solution:

  1. Initialize a variable factorial to 1.

  2. Iterate from 1 to n.

  3. For each iteration, multiply factorial by the current number.

  4. Return the final value of factorial.

Code:

function factorial(n) {
  let factorial = 1;
  for (let i = 1; i <= n; i++) {
    factorial *= i;
  }
  return factorial;
}

Recursive Solution:

  1. If n is 0, return 1 (base case).

  2. Otherwise, return n multiplied by the factorial of n-1.

Code:

function factorialRecursive(n) {
  if (n === 0) {
    return 1;
  } else {
    return n * factorialRecursive(n - 1);
  }
}

Explanation:

Iterative Solution:

  • The loop iterates from 1 to n, multiplying the current number with the previous result stored in factorial.

  • This way, factorial accumulates the product of all numbers up to n.

Recursive Solution:

  • The recursive function calls itself with a smaller value of n until it reaches the base case (n = 0).

  • The function then multiplies n with the result of the recursive call.

  • This process continues until the recursive calls reach the base case, at which point the factorial calculation is complete.

Applications:

  • Combinatorics (counting arrangements)

  • Probability theory

  • Optimization problems

  • Generating permutations and combinations


Inversion of Object

Problem Statement:

You are given two strings s and t of the same length. In one move, you can choose any character in s and move it to the end of t.

Return the minimum number of moves needed to make s and t identical.

Example:

Input: s = "ab", t = "ba"
Output: 1
Explanation: Move the character 'a' from s to the end of t to make t = "aba" which is identical to s.

Input: s = "abc", t = "abd"
Output: 2
Explanation: Move the characters 'a' and 'c' from s to the end of t to make t = "abdc" which is identical to s.

Solution:

The solution to this problem involves comparing the characters of s and t, and counting the number of moves needed to make them identical.

  1. Character Comparison:

    • Initialize a variable moves to 0.

    • Iterate over the characters of s and t simultaneously using a for loop.

    • If the characters at the current index are different, increment moves.

  2. Return Result:

    • After the loop, return the value of moves.

Implementation:

/**
 * @param {string} s
 * @param {string} t
 * @return {number}
 */
const minimumMoves = (s, t) => {
  let moves = 0;

  for (let i = 0; i < s.length; i++) {
    if (s[i] !== t[i]) {
      moves++;
    }
  }

  return moves;
};

Real-World Applications:

This algorithm can be applied in various scenarios where character order within a string is crucial:

  • Data Manipulation: Aligning data in specific formats or merging records by rearranging fields.

  • String Comparison: Identifying and correcting differences between two similar strings.

  • Text Editing: Determining the minimum number of operations required to transform one text to another.


Check if Object Instance of Class

Problem Statement

Given an object, check if it is an instance of a class.

Solution

function isInstanceOfClass(object, Class) {
  return object instanceof Class;
}

Explanation

The instanceof operator returns true if the object is an instance of the given class, and false otherwise.

For example:

const person = new Person();
console.log(person instanceof Person); // true
console.log(person instanceof Object); // true
console.log(person instanceof Array); // false

Potential Applications

  • Checking if an object is of a certain type before performing an operation on it.

  • Identifying the type of an object in a polymorphic system.

  • Implementing a class hierarchy with different types of objects.


Calculator with Method Chaining

Calculator with Method Chaining


Calculator with Method Chaining

Problem: Build a calculator that can perform basic arithmetic operations (+, -, *, /) using method chaining.

Solution: A calculator with method chaining allows you to perform multiple operations without having to declare intermediate variables or call the calculate() method after each operation. Here's how to implement it:

class Calculator {
  constructor(value) {
    this.value = value;
  }

  add(number) {
    this.value += number;
    return this; // Return the calculator object to allow chaining
  }

  subtract(number) {
    this.value -= number;
    return this;
  }

  multiply(number) {
    this.value *= number;
    return this;
  }

  divide(number) {
    this.value /= number;
    return this;
  }

  calculate() {
    return this.value;
  }
}

const calc = new Calculator(10);
const result = calc.add(5).subtract(3).multiply(2).divide(4).calculate();

console.log(result); // Output: 6

How it works:

  1. Constructor: The Calculator class takes an initial value in its constructor.

  2. Arithmetic methods: Each arithmetic method (add, subtract, multiply, divide) updates the value property with the specified operation.

  3. Method chaining: After each operation, the method returns the Calculator object itself. This allows you to chain multiple operations like in the following example:

    calc.add(5).subtract(3).multiply(2).divide(4);
  4. Calculate method: The calculate() method returns the final result of the calculation.

Real-world applications:

  • Financial calculations: Calculating interest, loan payments, and other financial values.

  • Physics calculations: Performing basic arithmetic for velocity, acceleration, and other physical quantities.

  • Data analysis: Chain together multiple calculations to transform and summarize data.


Call Function with Custom Context

Problem Statement:

Given a function f(), a context object context, and an array of arguments args, call the function f() with the custom context context and the arguments args.

Solution:

The Function.prototype.call() method allows us to call a function with a custom context.

Implementation:

function f(a, b, c) {
  console.log(this, a, b, c);
}

const context = {
  name: 'Custom Context'
};

const args = [1, 2, 3];

// Call f() with custom context and arguments
f.call(context, ...args);

Explanation:

  1. The Function.prototype.call() method takes three arguments:

    • The context object (optional)

    • The first argument to pass to the function (optional)

    • An array of remaining arguments to pass to the function (optional)

  2. In the example above, we create a function f(), a context object context, and an array of arguments args.

  3. We then call f() using the call() method, specifying context as the first argument, which sets the this keyword inside the f() function to the context object.

  4. We pass the arguments args to the call() method using the spread operator (...), which expands the array into individual arguments.

  5. When we call console.log(this, a, b, c) inside the f() function, we get the following output:

{ name: 'Custom Context' } 1 2 3
  1. This confirms that the f() function was called with the custom context context and the arguments args.

Real-World Applications:

The ability to call a function with a custom context is useful in many scenarios, such as:

  • Testing: Simulating object behavior without creating new instances.

  • Dynamic Binding: Changing the context of a function at runtime.

  • Constructor Functions: Creating objects with custom properties by setting this to a custom object.

  • Callback Functions: Passing custom data to callback functions by setting this to a specific object.


Function Composition

Function Composition

Function composition is a mathematical operation that takes two functions as input and produces a new function that is the composition of the two input functions.

In JavaScript, function composition can be implemented using the pipe operator (|>) or the compose function from the lodash library.

The pipe operator is a binary operator that takes two functions as input and produces a new function that is the composition of the two input functions. The pipe operator is left-associative, so the expression f |> g |> h is equivalent to h(g(f(x))).

The compose function is a function that takes two functions as input and produces a new function that is the composition of the two input functions. The compose function is right-associative, so the expression compose(h, g, f) is equivalent to f(g(h(x))).

Example:

const add1 = x => x + 1;
const multiplyBy2 = x => x * 2;

const add1AndMultiplyBy2 = compose(multiplyBy2, add1);

console.log(add1AndMultiplyBy2(5)); // 12

In this example, the add1AndMultiplyBy2 function is the composition of the add1 and multiplyBy2 functions. The add1AndMultiplyBy2 function takes a number as input and adds 1 to it, and then multiplies the result by 2.

Applications:

Function composition can be used in a variety of applications, such as:

  • Creating new functions: Function composition can be used to create new functions from existing functions. For example, the add1AndMultiplyBy2 function in the example above was created by composing the add1 and multiplyBy2 functions.

  • Simplifying code: Function composition can be used to simplify code by reducing the number of nested function calls. For example, the following code snippet uses function composition to simplify a nested function call:

const add1AndMultiplyBy2 = x => multiplyBy2(add1(x));

// Simplified using function composition:
const add1AndMultiplyBy2 = compose(multiplyBy2, add1);
  • Improving performance: Function composition can be used to improve performance by reducing the number of function calls. For example, the following code snippet uses function composition to improve the performance of a function that calculates the average of a list of numbers:

const average = numbers => sum(numbers) / numbers.length;

// Improved performance using function composition:
const average = compose(divideBy(numbers.length), sum);

In this example, the average function is improved by composing the sum and divideBy functions. The sum function calculates the sum of the numbers in the list, and the divideBy function divides the sum by the length of the list. By composing these two functions, the average function can be implemented with a single function call instead of two function calls.


Counter

Problem: Design a counter class that can count up and down.

Implementation:

class Counter {
  constructor(start = 0) {
    this.count = start;
  }

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }

  getValue() {
    return this.count;
  }
}

Breakdown:

  • Constructor: The Counter class initializes with a starting value (default 0) when it is created.

  • Methods:

    • increment(): Increases the count by 1.

    • decrement(): Decreases the count by 1.

    • getValue(): Returns the current count value.

Simplified Explanation:

Imagine a digital number counter. The number on the display (count) shows a value. We can "increment" it (make it go up by 1) by pressing a button, and "decrement" it (make it go down by 1) by pressing another button. This counter class mimics that behavior, allowing us to keep track of a numerical value in a program.

Example Usage:

// Create a counter with a starting value of 10
const counter = new Counter(10);

// Increment the counter by 5
counter.increment(5);

// Decrement the counter by 3
counter.decrement(3);

// Get the current count value
const count = counter.getValue(); // returns 12

Potential Applications:

  • Tracking user scores in a game

  • Counting inventory items in a warehouse

  • Managing the number of seats available in a theater


Design Cancellable Function

Problem Statement:

You are given a function that can take a long time to execute. You want to provide a way to cancel the execution of this function if it takes too long.

Solution:

One way to do this is to create a cancellable function. A cancellable function is a function that can be stopped at any time before it completes. This can be done by setting a flag that indicates that the function should be cancelled.

Here is a simple example of how to create a cancellable function in JavaScript:

function cancellableFunction(args) {
  // Check if the function has been cancelled
  if (this.cancelled) {
    return;
  }

  // Execute the function
  // ...

  // Check if the function has been cancelled again
  if (this.cancelled) {
    return;
  }

  // Finish executing the function
  // ...
}

// Create a cancellable function
const cancellable = cancellableFunction.bind({ cancelled: false });

// Cancel the function if it takes too long
setTimeout(() => {
  cancellable.cancelled = true;
}, 1000);

// Call the cancellable function
cancellable('foo', 'bar');

In this example, the cancellableFunction function is created with a cancelled property set to false. This property is used to indicate whether the function should be cancelled.

The cancellable function is then called with the arguments 'foo' and 'bar'. If the function takes longer than 1 second to execute, the cancelled property is set to true and the function is cancelled.

Applications:

Cancellable functions can be used in a variety of applications, such as:

  • Long-running tasks: Cancellable functions can be used to cancel long-running tasks that are no longer needed. For example, a user might start a search and then change their mind and want to cancel the search.

  • Polling: Cancellable functions can be used to cancel polling requests. For example, a user might be polling for new data and then want to stop polling.

  • AJAX requests: Cancellable functions can be used to cancel AJAX requests. For example, a user might start an AJAX request and then want to cancel the request because they have changed their mind.

Real-World Examples:

  • Google Maps: Google Maps uses cancellable functions to cancel searches when the user changes their mind.

  • Gmail: Gmail uses cancellable functions to cancel AJAX requests when the user changes their mind.

  • Facebook: Facebook uses cancellable functions to cancel polling requests when the user changes their mind.


Infinite Method Object

Infinite Method Object

The JavaScript Math object provides a set of mathematical functions and constants. However, what if you need a method that's not included in the Math object? You can create your own!

Creating a Method Object

To create a method object, you can use the following syntax:

const myMethod = {
  name: "myMethod",
  fn: (...args) => {
    // Code to execute
  }
};

Here's an example:

const mySum = {
  name: "mySum",
  fn: (a, b) => {
    return a + b;
  }
};

The mySum object has a name property (set to "mySum") and a fn property (set to a function that takes two arguments and returns their sum).

Using the Method Object

To use the method object, you can invoke its fn property. For example:

const result = mySum.fn(1, 2); // 3

console.log(result); // 3

Infinite Method Object

An infinite method object is a method object with an infinite number of functions. This can be achieved by using a loop to create a new function for each integer.

Here's an example:

const myInfiniteMethod = {
  name: "myInfiniteMethod",
  fn: (n) => {
    return n + 1;
  }
};

for (let i = 1; i < Infinity; i++) {
  myInfiniteMethod[i] = {
    name: `myInfiniteMethod${i}`,
    fn: (...args) => {
      console.log(`Calling myInfiniteMethod${i} with args: ${args}`);
      return args.reduce((acc, curr) => acc + curr, 0);
    }
  };
}

With this infinite method object, you can now call myInfiniteMethod1, myInfiniteMethod2, and so on. Each method will log the arguments passed to it and return their sum.

Applications

Infinite method objects can be useful in a variety of applications, such as:

  • Creating custom mathematical functions

  • Implementing custom algorithms

  • Extending existing objects with additional functionality


Return Length of Arguments Passed

Problem Statement:

Given a function, return the length of the arguments passed to it.

Example:

function getLength(...args) {
  // Your code here
}

console.log(getLength(1, 2, 3, 4, 5)); // 5

Best & Performant Solution:

function getLength(...args) {
  return args.length;
}

Breakdown:

  • The rest operator (...) is used to collect all arguments passed to the function into an array called args.

  • The length property of the args array returns the number of arguments passed to the function.

Real-World Application:

This function can be useful in situations where you need to know the number of arguments passed to a function. For example:

  • Validating input data: If a function expects a certain number of arguments, you can use this function to check if the correct number of arguments were passed.

  • Debugging: This function can be used to help identify if a function is being called with the correct number of arguments.


Is Object Empty

LeetCode Problem

Problem Statement: Given a non-null object, determine if the object is empty. An "empty" object is one that has no properties (keys) defined.

Example:

Input: {}
Output: true

Input: { foo: "bar" }
Output: false

Solution

Approach: The best and most performant approach to check if an object is empty in JavaScript is to use the Object.keys() method. This method returns an array of the object's own property names. If the array is empty, the object is empty.

Implementation:

/**
 * Checks if an object is empty.
 *
 * @param {Object} obj The object to check.
 *
 * @returns {boolean} True if the object is empty, false otherwise.
 */
const isObjectEmpty = (obj) => {
  return Object.keys(obj).length === 0;
};

Example Usage

const obj1 = {};
const obj2 = { foo: "bar" };

console.log(isObjectEmpty(obj1)); // true
console.log(isObjectEmpty(obj2)); // false

Applications in the Real World

Checking if an object is empty is a common task in JavaScript development. Here are some real-world applications:

  • Form validation: Check if a form contains any empty fields before submitting it.

  • Data processing: Filter out empty objects from a list of objects.

  • Object equality: Compare two objects by checking if they have the same set of properties and if none of the properties are empty.


Throttle

Throttle Function

Explanation:

Throttling is a technique used to limit the rate at which a function can be executed. It ensures that the function is called only once within a specified time interval. This is useful when you want to prevent multiple calls to a resource-intensive function in rapid succession.

Implementation in JavaScript:

function throttle(func, delay) {
  var timerId;
  return function() {
    if (!timerId) {
      timerId = setTimeout(() => {
        func.apply(this, arguments);
        timerId = null;
      }, delay);
    }
  };
}

Breakdown:

  • func: The function to be throttled.

  • delay: The time interval in milliseconds within which the function can be executed only once.

  • timerId: A variable to hold the ID of the timer that schedules the delayed execution of the function.

  • The returned function checks if there is an active timer. If not, it schedules the execution of the original function with a delay and sets timerId to the ID of the timer.

  • If the function is called again before the delay expires, the existing timer is not cancelled. The new call to the returned function will be ignored until the timer expires.

Real-World Applications:

  • Event handling: Prevent excessive calls to event handlers, such as click or scroll events, to improve responsiveness.

  • Resource-intensive operations: Throttle calls to functions that perform heavy computations or database queries to avoid overloading the system.

  • API calls: Limit the frequency of API calls to avoid exceeding rate limits imposed by the API provider.

// Example: Throttle a function that logs messages to the console
const throttledLogger = throttle(console.log, 1000);

// Call the throttled function multiple times
for (let i = 0; i < 10; i++) {
  throttledLogger(`Message ${i}`);
}

// Output:
// Message 0
// Message 5
// Message 9

// The function is called only once per second due to the throttle.

Add Two Promises

Problem:

Given two integer arrays, nums1 and nums2, return the sum of the two arrays.

Example:

Input: nums1 = [1,2,3], nums2 = [4,5,6]
Output: [5,7,9]

Solution using Promises:

We can use Promises to handle the asynchronous addition of the two arrays. Here's how:

// Create a function to add two numbers.
const add = (a, b) => a + b;

// Create a Promise for each array.
const promise1 = Promise.resolve(nums1);
const promise2 = Promise.resolve(nums2);

// Use Promise.all to execute the Promises concurrently.
Promise.all([promise1, promise2])
  // Convert the arrays to a single array and add them.
  .then(([arr1, arr2]) => arr1.map((num, i) => add(num, arr2[i])))
  // Print the result.
  .then(result => console.log(result));

Explanation:

  1. Create an add function: This function simply adds two numbers.

  2. Create Promises for each array: Promises are containers for asynchronous operations. In this case, they hold the two arrays.

  3. Use Promise.all: Promise.all takes an array of Promises and executes them concurrently. It returns a new Promise that resolves to an array of the results from the input Promises.

  4. Convert and add the arrays: Inside the .then callback, we convert both arrays to a single array using map. We then use add to add corresponding elements from the two arrays.

  5. Print the result: Finally, we print the result of the addition.

Benefits of using Promises:

  • Concurrency: Promises allow us to perform asynchronous operations concurrently, which can improve performance.

  • Error handling: Promises provide a convenient way to handle errors in asynchronous operations.

  • Simplified code: Promises help to simplify asynchronous code by making it more sequential and easier to read.

Real-World Applications:

Promises have numerous real-world applications, such as:

  • Data loading and caching: Promises can be used to load data from multiple sources asynchronously and cache the results.

  • API calls: When making API calls, Promises can be used to handle the asynchronous nature of the requests.

  • Concurrency in web applications: Promises can be used to perform long-running tasks in the background without blocking the user interface.


Debounce

Debounce

Problem:

You have a function that performs a certain task. However, you want to limit how often that function is called to prevent over-processing.

Solution:

Debouncing is a technique used to delay the execution of a function until a certain amount of time has passed since the last call. It prevents the function from being called multiple times in rapid succession.

Implementation in JavaScript:

function debounce(fn, delay) {
  let timerId;
  return function(...args) {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      fn(...args);
      timerId = null;
    }, delay);
  };
}

Explanation:

  • The debounce function takes two arguments: fn (the function to be debounced) and delay (the amount of time to delay the execution of fn).

  • It returns a new function that acts as a wrapper around fn.

  • Each time the wrapper function is called, it checks if there is a pending timerId.

  • If there is, it cancels the timer using clearTimeout.

  • It then sets a new timer using setTimeout to call fn with the given arguments after delay milliseconds.

  • While the timer is running, if the wrapper function is called again, it cancels the previous timer and sets a new one.

  • Once the delay has passed, fn is called and the timerId is cleared.

Applications in Real World:

  • Search bar: Debouncing can be used to limit the number of search queries sent to the server when a user types in a search bar.

  • Event handlers: Debouncing can be used to prevent an event handler from being called multiple times in rapid succession, such as when a user clicks a button repeatedly.

  • Scrolling: Debouncing can be used to limit the number of times a scrolling event is fired as a user scrolls through a web page.


Deep Merge of Two Objects

Problem Statement:

Given two objects, return a new object that contains the merged properties of both objects. If a property exists in both objects, the value from the second object should overwrite the value from the first.

Naive Solution:

A straightforward solution is to loop through both objects, adding each property to a new object. However, this approach requires multiple iterations, which can be inefficient for large objects.

const mergeObjects = (obj1, obj2) => {
  const mergedObj = {};
  for (const key in obj1) {
    mergedObj[key] = obj1[key];
  }
  for (const key in obj2) {
    mergedObj[key] = obj2[key];
  }
  return mergedObj;
};

Spread Operator Solution:

The Spread Operator allows us to merge objects more efficiently in a single step. It spreads the properties of both objects into a new object, overwriting any duplicate properties with those from the second object.

const mergeObjects = (obj1, obj2) => {
  return { ...obj1, ...obj2 };
};

Object.assign() Solution:

The Object.assign() method is specifically designed for merging objects. It takes a target object as the first argument and one or more source objects as subsequent arguments. The target object receives the merged properties.

const mergeObjects = (obj1, obj2) => {
  return Object.assign({}, obj1, obj2);
};

Recursive Solution:

For deeply nested objects, a recursive solution can be more appropriate. This solution traverses both objects, merging any nested objects as it goes.

const mergeObjects = (obj1, obj2) => {
  const mergedObj = {};
  for (const key in obj1) {
    if (typeof obj1[key] === 'object' && obj1[key] !== null) {
      mergedObj[key] = mergeObjects(obj1[key], obj2[key]);
    } else {
      mergedObj[key] = obj1[key];
    }
  }
  for (const key in obj2) {
    if (typeof obj2[key] === 'object' && obj2[key] !== null) {
      mergedObj[key] = mergeObjects(obj2[key], mergedObj[key]);
    } else if (!mergedObj.hasOwnProperty(key)) {
      mergedObj[key] = obj2[key];
    }
  }
  return mergedObj;
};

Applications:

Deep object merging is a common task in web development and data processing. It can be used for:

  • Combining data from multiple sources

  • Updating or modifying existing data

  • Creating new objects based on existing ones

  • Serializing and deserializing objects from JSON


Timeout Cancellation

Problem Statement:

You have a function that takes a long time to execute. You want to be able to cancel the execution of the function after a certain amount of time has passed.

Solution:

One way to cancel the execution of a function is to use a timeout. A timeout is a function that is executed after a certain amount of time has passed. If the function that you want to cancel is still executing when the timeout is executed, the timeout can cancel the execution of the function.

Here is an example of how to use a timeout to cancel the execution of a function:

const myFunction = () => {
  // Do something that takes a long time to execute
};

const timeout = setTimeout(() => {
  // Cancel the execution of myFunction
  myFunction.cancel();
}, 1000); // Cancel after 1 second

// You can now call myFunction
myFunction();

// If you want to cancel the timeout, you can call clearTimeout(timeout)

In this example, the myFunction function is executed after the timeout function is called. The timeout function is executed after 1 second, and it cancels the execution of the myFunction function.

Real-World Applications:

Timeouts can be used in a variety of real-world applications, such as:

  • Canceling the execution of a long-running task

  • Preventing a user from getting stuck waiting for a response from a server

  • Limiting the amount of time that a user can spend on a particular task

Breakdown of the Solution:

The solution to this problem involves the following steps:

  1. Define the function that you want to cancel.

  2. Create a timeout function that will cancel the execution of the function.

  3. Call the function that you want to cancel.

  4. If you want to cancel the timeout, call clearTimeout(timeout).

Simplification:

In simple terms, a timeout is like a timer that tells your function to stop running after a certain amount of time. You can use a timeout to cancel the execution of a function that is taking too long to run.

Potential Applications:

Timeouts can be used in a variety of applications, such as:

  • Preventing your browser from freezing if a server takes too long to respond

  • Limiting the amount of time that a user can spend on a particular task

  • Canceling the execution of a long-running task when it is no longer needed


JSON Deep Equal

JSON Deep Equal

Definition

Two JSON objects are considered deeply equal if they have the same keys with the same values, and the values are also deeply equal.

Implementation

function deepEqual(obj1, obj2) {
  // Check if the objects are the same reference
  if (obj1 === obj2) {
    return true;
  }

  // Check if the objects are both null
  if (obj1 == null || obj2 == null) {
    return false;
  }

  // Check if the objects have the same type
  if (typeof obj1 !== typeof obj2) {
    return false;
  }

  // Check if the objects are arrays
  if (Array.isArray(obj1) && Array.isArray(obj2)) {
    // Check if the arrays have the same length
    if (obj1.length !== obj2.length) {
      return false;
    }

    // Recursively check if each element in the arrays is deeply equal
    for (let i = 0; i < obj1.length; i++) {
      if (!deepEqual(obj1[i], obj2[i])) {
        return false;
      }
    }

    return true;
  }

  // Check if the objects are objects
  if (typeof obj1 === "object" && typeof obj2 === "object") {
    // Check if the objects have the same number of keys
    if (Object.keys(obj1).length !== Object.keys(obj2).length) {
      return false;
    }

    // Recursively check if each key-value pair in the objects is deeply equal
    for (let key in obj1) {
      if (!deepEqual(obj1[key], obj2[key])) {
        return false;
      }
    }

    return true;
  }

  // If the objects are not arrays or objects, they must be primitive values
  return obj1 === obj2;
}

Example

const obj1 = {
  name: "John Doe",
  age: 30,
  children: [
    {
      name: "Jane Doe",
      age: 5
    },
    {
      name: "John Doe Jr.",
      age: 2
    }
  ]
};

const obj2 = {
  name: "John Doe",
  age: 30,
  children: [
    {
      name: "Jane Doe",
      age: 5
    },
    {
      name: "John Doe Jr.",
      age: 2
    }
  ]
};

console.log(deepEqual(obj1, obj2)); // true

Applications

JSON deep equal can be used in various scenarios, including:

  • Comparing the results of two API calls to ensure they are consistent.

  • Detecting changes in the state of a complex object without having to manually compare each property.

  • Validating the input of a form by checking if it matches a predefined schema.


Partial Function with Placeholders

题目描述

给定一个函数 f 和一个占位符 placeholder,设计一个偏函数 partial(f, placeholder),使偏函数接收一个参数 x 并返回 f(placeholder, x)

实现

const partial = (f, placeholder) => (x) => f(placeholder, x);

分解和解释

  • **偏函数:**是一个新函数,它将一个现有函数(f)和一个占位符(placeholder)组合起来。

  • **占位符:**是偏函数中固定不变的参数。

  • **返回函数:**偏函数返回一个新函数,该函数接收一个参数(x)并返回 f(placeholder, x) 的结果。

示例

// 给定一个函数 f(a, b) = a + b
const f = (a, b) => a + b;

// 创建一个偏函数,将第二个参数固定为 10
const partialF = partial(f, 10);

// 现在,partialF 接受一个参数并返回其与 10 的和
const result = partialF(5); // result = 15 (5 + 10)

应用

偏函数在实际场景中有许多用途,例如:

  • **事件处理:**可以在事件处理程序中使用偏函数,其中占位符是事件对象,而参数是用户交互。

  • **表单验证:**可以使用偏函数对用户输入进行验证,其中占位符是验证规则。

  • **函数柯里化:**柯里化是将多参数函数转换为一系列单参数函数的过程。偏函数可以用于柯里化函数。

性能

偏函数的性能通常很好,因为它只是将一个函数包装在一个新函数中。新函数没有额外的开销,因为它只是调用原始函数。


Filter Elements from Array

Problem: Remove specific elements from a given array.

Example:

Input: arr = [1, 2, 3, 4, 5, 6, 7]
toRemove = [2, 5]

Output: [1, 3, 4, 6, 7]

Solution: One efficient approach is to use the filter method:

const filteredArray = arr.filter(value => !toRemove.includes(value));

Breakdown:

  • arr.filter() iterates over the arr elements, one at a time.

  • !toRemove.includes(value) checks if the current value exists in the toRemove array.

  • If it doesn't exist (!toRemove.includes(value) is true), it keeps the value in the filtered result.

  • Otherwise, it excludes the value.

Simplified Explanation: Imagine you have a list of names you want to invite to a party. You have a separate list of names you don't want to invite. To get the final guest list, you go through the invite list and check each name against the "do-not-invite" list. If a name is not on the "do-not-invite" list, it stays on the final guest list. Otherwise, it's removed.

Real-World Application:

  • Filtering out unwanted or irrelevant items from a dataset.

  • Removing duplicate entries from a list.

  • Extracting specific data that meets certain criteria.

Time Complexity: O(n), where n is the number of elements in the original array. Space Complexity: O(n), as a new array needs to be created for the filtered elements.


Convert Object to JSON String

Convert Object to JSON String

Problem: Given an object, convert it to a JSON string.

Solution: The simplest way to convert an object to a JSON string in JavaScript is to use the JSON.stringify() method. This method takes an object as input and returns a JSON string.

const object = {
  name: "John",
  age: 30,
  occupation: "Software Engineer"
};

const json = JSON.stringify(object);
console.log(json); // Output: {"name":"John","age":30,"occupation":"Software Engineer"}

Explanation:

  1. The JSON.stringify() method takes an object as input and converts it to a JSON string.

  2. The JSON string is a string that represents the object in JSON format.

  3. The JSON string can be used to store or send the object to another location.

Real-World Applications:

  • Storing data in a database: JSON strings can be used to store data in a database. This is because JSON strings are easy to parse and store.

  • Sending data over a network: JSON strings can be sent over a network. This is because JSON strings are lightweight and easy to transfer.

  • Configuring applications: JSON strings can be used to configure applications. This is because JSON strings are easy to read and understand.

Potential Applications:

  • Web applications: JSON strings are used in many web applications to store and transfer data.

  • Mobile applications: JSON strings are used in many mobile applications to store and transfer data.

  • Data analysis: JSON strings can be used in data analysis to store and process data.


Promise Pool

Problem Statement

Given an array of tasks, each represented by a function that returns a promise, implement a promise pool that limits the number of concurrent tasks that can be executed.

Implementation

1. Promise Pool Class

class PromisePool {
  constructor(concurrency) {
    this.concurrency = concurrency;
    this.queue = [];
    this.running = 0;
  }

  add(task) {
    this.queue.push(task);
    this.run();
  }

  run() {
    while (this.running < this.concurrency && this.queue.length) {
      const task = this.queue.shift();
      this.running++;
      task().then(() => {
        this.running--;
        this.run();
      });
    }
  }
}

2. Example Usage

const pool = new PromisePool(5);
for (let i = 0; i < 10; i++) {
  pool.add(() => {
    return fetch(`https://example.com/${i}.json`);
  });
}

Explanation

1. Constructor:

  • concurrency specifies the maximum number of concurrent tasks allowed.

  • queue stores the tasks to be executed.

  • running tracks the number of tasks currently running.

2. Add Method:

  • When a task is added to the pool, it is pushed onto the queue.

  • If the number of running tasks is less than the concurrency limit, the task is immediately executed.

3. Run Method:

  • The run method checks if there are any tasks waiting in the queue and if the number of running tasks is below the limit.

  • If both conditions are met, it dequeues a task from the queue and executes it asynchronously.

  • After a task finishes, it increments the running count and calls run again to execute the next task.

Real-World Application

A promise pool is useful when you need to limit the number of concurrent operations to avoid overloading a server or other resource. For example, a web crawler could use a promise pool to limit the number of concurrent HTTP requests it makes.

Simplified Explanation

Think of a promise pool as a traffic light. The concurrency limit represents the maximum number of cars that can pass through the intersection at a time. Each car represents a task. When a car (task) arrives, it is put in a queue. If there are less than the maximum number of cars in the intersection (running tasks), the car (task) is allowed to pass through (run). As cars pass through the intersection (tasks finish), the traffic light (pool) checks if there are any more cars (tasks) waiting and allows them to pass through if the limit has not been reached.


Chunk Array

Chunk Array

Problem Statement: Given an array and a chunk size, divide the array into chunks of the specified size.

Optimal Solution:

The most efficient solution involves using the Array.prototype.slice() method.

const chunkArray = (array, chunkSize) => {
  const chunks = [];
  let index = 0;
  while (index < array.length) {
    chunks.push(array.slice(index, index + chunkSize));
    index += chunkSize;
  }
  return chunks;
};

Breakdown:

  1. Initialize an empty array chunks to store the resulting chunks.

  2. Set the starting index index to 0.

  3. Enter a loop that runs as long as index is less than the length of the array.

  4. Use array.slice(index, index + chunkSize) to extract a chunk of the specified size starting at the current index.

  5. Push the extracted chunk into the chunks array.

  6. Increment index by chunkSize to move to the next chunk.

  7. Repeat steps 4-6 until all elements in the array have been processed.

  8. Return the chunks array containing the chunked array.

Real-World Application:

Chunking arrays is useful in various scenarios, such as:

  • Data Pagination: Dividing large datasets into smaller chunks for easier display and navigation.

  • Image Loading: Optimizing image loading by loading images in chunks to avoid large file transfers and improve performance.

  • File Uploading: Breaking down large files into chunks for efficient upload and download.


Next Day

Problem Statement:

Given a non-negative integer num, return the number of ways to represent num as a sum of distinct positive integers.

Example:

Input: num = 3
Output: 4
Explanation: There are 4 ways to represent 3 as a sum of distinct positive integers:
1 + 1 + 1, 1 + 2, 2 + 1, 3

Solution:

To solve this problem, we can use dynamic programming. We will define a 2D array dp such that dp[i][j] represents the number of ways to represent i as a sum of distinct positive integers using the first j positive integers.

We can initialize the first row and column of dp to 1, since there is only one way to represent 0 and 1 as a sum of distinct positive integers (0 and 1 themselves).

for (let i = 0; i <= num; i++) {
  dp[i][0] = 1;
}
for (let j = 0; j <= num; j++) {
  dp[0][j] = 1;
}

For the remaining cells in dp, we can compute the number of ways to represent i as a sum of distinct positive integers using the first j positive integers by considering two cases:

  1. We do not include the j-th positive integer in the sum. In this case, the number of ways is given by dp[i][j-1].

  2. We include the j-th positive integer in the sum. In this case, the number of ways is given by dp[i-j][j-1], since we have already used the j-th positive integer, and we need to find the number of ways to represent i-j as a sum of distinct positive integers using the first j-1 positive integers.

We can compute the value of dp[i][j] as the sum of these two cases:

for (let i = 1; i <= num; i++) {
  for (let j = 1; j <= num; j++) {
    dp[i][j] = dp[i][j-1] + dp[i-j][j-1];
  }
}

Finally, the answer to the problem is given by dp[num][num]:

return dp[num][num];

Real-World Applications:

This problem can be applied in various real-world scenarios, such as:

  • Coin Combinations: Given a set of coin denominations, determine the number of ways to make a given amount of money using the coins.

  • Partitioning a Set: Given a set of integers, determine the number of ways to partition the set into two disjoint subsets such that the sum of the integers in each subset is equal.

  • Number of Integer Partitions: Determine the number of ways to partition a positive integer into a sum of positive integers.


Generate Circular Array Values

Generating Circular Array Values

Problem Statement: Given an array of numbers, each number represents the difference between two successive elements in a circular array. Return the circular array.

Example:

Input: [1, -1, 1]
Output: [0, 1, 0]
Explanation: Initializing the array at index 0 as 0. [0, 1, 0]

Solution:

  1. Initialize the circular array: Start by creating an empty array of size 'N', where 'N' is the length of the given array. This will be the circular array we return.

  2. Iterate through the given array: Loop through each element in the given array.

  3. Update the circular array: For each element, add it to the current value in the circular array at the current index. This accumulates the differences and constructs the circular array.

  4. Handle array overflow: Since this is a circular array, if the current index exceeds the size of the circular array, wrap it around to the beginning using the modulus operator %.

const circularArray = (nums) => {
  const result = new Array(nums.length).fill(0); // Initialize the circular array

  // Iterate through the given array
  for (let i = 0; i < nums.length; i++) {
    result[i] = nums[i]; // Start with the given difference
    result[(i + 1) % result.length] += nums[i]; // Add to the next element in the circular array
  }

  return result;
};

Real-World Applications:

  • Cyclic data structures: Maintaining a consistent order of elements, such as in a queue or linked list, where the last element wraps around to the first.

  • Clock simulation: Modeling a clock where the time wraps around after 12 hours.

  • Data analysis: Analyzing time series data that exhibits cyclical patterns, such as daily sales or monthly temperatures.


Array Wrapper

Problem Statement: Given an array, find the maximum sum of any contiguous subarray.

Solution: The best way to solve this problem is using Kadane's Algorithm. This algorithm works by iterating through the array and keeping track of the current maximum sum and the overall maximum sum.

Breakdown:

1. Initialization: Start by initializing the current maximum sum to the first element in the array. Also, initialize the overall maximum sum to the first element in the array.

2. Iteration: Loop through the remaining elements in the array. For each element, calculate the new current maximum sum by taking the maximum of the previous current maximum sum and the current element. Then, update the overall maximum sum to be the maximum of the current overall maximum sum and the new current maximum sum.

3. Return: Finally, return the overall maximum sum.

Example:

function maxSubArray(nums) {
  let currentMax = nums[0];
  let overallMax = nums[0];

  for (let i = 1; i < nums.length; i++) {
    currentMax = Math.max(currentMax + nums[i], nums[i]);
    overallMax = Math.max(overallMax, currentMax);
  }

  return overallMax;
}

Real-World Applications:

Kadane's Algorithm can be used in a variety of real-world applications, such as:

  • Finding the best investment portfolios

  • Analyzing stock market data

  • Detecting fraud by identifying anomalous spending patterns

  • Predicting weather patterns

  • Optimizing routing and scheduling algorithms


Generate Fibonacci Sequence

Fibonacci Sequence

The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones, starting from 0 and 1. It is named after the Italian mathematician Leonardo Fibonacci.

The sequence is often represented as:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

Recursive Solution

One way to generate the Fibonacci sequence is using recursion. Here's a recursive JavaScript solution:

function fibonacci(n) {
  if (n < 2) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

Iterative Solution

Another approach is using iteration. Here's an iterative JavaScript solution:

function fibonacci(n) {
  let fibSequence = [0, 1];

  for (let i = 2; i <= n; i++) {
    fibSequence[i] = fibSequence[i - 1] + fibSequence[i - 2];
  }

  return fibSequence[n];
}

Explanation

The recursive solution works by calculating each Fibonacci number recursively. However, this can lead to performance issues for large values of n because it makes multiple redundant calls.

The iterative solution avoids this issue by storing the Fibonacci sequence in an array and building it iteratively. This approach is much more efficient for large n.

Real-World Applications

The Fibonacci sequence has various applications in real-world scenarios, such as:

  • Financial modeling: Predicting stock market fluctuations and interest rates.

  • Computer science: Searching and sorting algorithms, data compression.

  • Nature: Plant growth patterns, animal population dynamics.

  • Music: Creating melodies and harmonies.

Example

To find the 7th Fibonacci number using the iterative solution:

const fib = fibonacci(7); // 13

Sleep

Problem Statement

Given an array of integers, return the maximum sum of any contiguous subarray.

Example

Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
Output: 6
Explanation: The contiguous subarray [4, -1, 2, 1] has the largest sum of 6.

Kadane's Algorithm

Kadane's algorithm is a greedy algorithm that solves the maximum subarray problem in linear time complexity. The algorithm works as follows:

  1. Initialize a variable max_so_far to store the maximum sum so far.

  2. Initialize a variable max_ending_here to store the maximum sum ending at the current index.

  3. Iterate through the array from left to right. For each index, do the following:

    • Update max_ending_here by adding the current element to it.

    • If max_ending_here is greater than max_so_far, update max_so_far to max_ending_here.

    • If max_ending_here is less than 0, reset it to 0.

  4. Return max_so_far.

JavaScript Implementation

/**
 * @param {number[]} nums
 * @return {number}
 */
const maxSubArray = function (nums) {
  let maxSoFar = -Infinity;
  let maxEndingHere = 0;

  for (let i = 0; i < nums.length; i++) {
    maxEndingHere += nums[i];
    
    if (maxEndingHere > maxSoFar) {
      maxSoFar = maxEndingHere;
    }
    
    if (maxEndingHere < 0) {
      maxEndingHere = 0;
    }
  }

  return maxSoFar;
};

Real-World Applications

The maximum subarray problem has many applications in real-world scenarios, such as:

  • Finding the best time to buy and sell a stock

  • Computing the maximum profit in a series of transactions

  • Identifying the most profitable subproject in a large project


Custom Interval

Problem: Given an array of integers, find the minimum size of a subarray whose sum is greater than or equal to a target value.

Optimal Solution: Use a sliding window approach.

Step 1: Initialize the sliding window.

Start with a window of size 0 at the beginning of the array.

windowStart = 0;
windowEnd = 0;

Step 2: Calculate the window sum.

Add the value at windowEnd to the window sum.

windowSum += nums[windowEnd];

Step 3: Check if the window sum is greater than or equal to the target value.

If the window sum is greater than or equal to the target value, then the current subarray is a valid candidate for the minimum subarray.

if (windowSum >= target) {
  minSize = Math.min(minSize, windowEnd - windowStart + 1);
}

Step 4: Move the sliding window.

If the window sum is less than the target value, then we need to expand the window. Otherwise, we can shrink the window.

if (windowSum < target) {
  windowEnd++;
} else {
  windowStart++;
  windowSum -= nums[windowStart - 1];
}

Step 5: Repeat steps 2-4 until the end of the array is reached.

Once the sliding window has reached the end of the array, the algorithm will terminate.

Time Complexity: O(n), where n is the length of the array.

Example:

nums = [2, 3, 1, 2, 4, 3]
target = 7

windowStart = 0
windowEnd = 0
windowSum = 0

windowSum += nums[windowEnd]  # windowSum = 2
windowEnd++                     # windowEnd = 1

windowSum += nums[windowEnd]  # windowSum = 5
windowEnd++                     # windowEnd = 2

windowSum += nums[windowEnd]  # windowSum = 6
windowEnd++                     # windowEnd = 3

windowSum += nums[windowEnd]  # windowSum = 8
windowEnd++                     # windowEnd = 4

windowSum += nums[windowEnd]  # windowSum = 11
windowEnd++                     # windowEnd = 5

windowSum += nums[windowEnd]  # windowSum = 14
windowEnd++                     # windowEnd = 6

minSize = Math.min(minSize, windowEnd - windowStart + 1)  # minSize = 2

windowStart++
windowSum -= nums[windowStart - 1]

windowStart++
windowSum -= nums[windowStart - 1]

windowStart++
windowSum -= nums[windowStart - 1]

windowStart++
windowSum -= nums[windowStart - 1]

windowStart++
windowSum -= nums[windowStart - 1]

windowStart++
windowSum -= nums[windowStart - 1]

The minimum subarray is [2, 3], with a sum of 5.


Array Prototype Last

Array Prototype Last

Problem:

Given an array of elements, return the last element of the array.

Solution:

The built-in array prototype method last() returns the last element of an array. Here's an example of how to use it:

const arr = [1, 2, 3, 4, 5];

const lastElement = arr.last();

console.log(lastElement); // Output: 5

In this example, the arr array has five elements. The last() method returns the last element of the array, which is 5.

Real-World Applications:

The last() method can be useful in a variety of real-world applications, such as:

  • Processing data in a loop and needing to access the last element of the data

  • Displaying the last item in a list

  • Removing the last element of a list

  • Sorting data in reverse order

Breakdown of the Code:

The last() method is a built-in method on the Array prototype. This means that it is available to all arrays in JavaScript. The method takes no arguments and returns the last element of the array.

The last() method is implemented in JavaScript as follows:

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

This code defines a new method on the Array prototype called last. The method returns the value of the last element in the array. The this keyword refers to the array that the method is called on. The this.length property returns the number of elements in the array. The this[this.length - 1] expression returns the value of the last element in the array.

Simplified Explanation:

In simple terms, the last() method returns the last element of an array. You can think of it as a shortcut for accessing the last element of an array without having to use the length property and the bracket notation.

Conclusion:

The last() method is a useful and efficient way to access the last element of an array in JavaScript. It is easy to use and can be applied in a variety of real-world applications.


To Be Or Not To Be

Problem Statement:

Suppose you have a string named "s." Return whether the string "s" can be rearranged to form a palindrome.

Solution:

A palindrome is a string that reads the same backwards and forwards. To determine if a string can be rearranged to form a palindrome, we can follow these steps:

  1. Count the occurrences of each character: Store the counts of each character in a hash table.

  2. Check for odd counts: If the hash table contains an odd count for any character, the string cannot be rearranged to form a palindrome. This is because a palindrome must have an even number of each character, except for one character that can have an odd count.

  3. Verify odd count: If there is exactly one character with an odd count, the string can potentially be rearranged to form a palindrome.

  4. Return: If there is more than one character with an odd count or if the hash table contains only even counts, the string cannot be rearranged to form a palindrome. Otherwise, it can be rearranged.

Code Implementation:

const canBePalindrome = (s) => {
  // Create a hash table to store character counts
  const charCounts = {};

  // Count the occurrences of each character
  for (let i = 0; i < s.length; i++) {
    const char = s[i];
    charCounts[char] = (charCounts[char] || 0) + 1;
  }

  // Check for odd counts
  let oddCount = 0;
  for (const char in charCounts) {
    if (charCounts[char] % 2) {
      oddCount++;
    }
  }

  // Verify odd count
  return oddCount <= 1;
};

Example:

Input: s = "racecar" Output: true (palindrome)

Explanation:

  • 'a' occurs twice

  • 'c' occurs twice

  • 'e' occurs once

  • 'r' occurs twice

There is exactly one character with an odd count ('e'), so the string can be rearranged to form a palindrome.

Applications:

  • Password generation: Palindromes can be used to generate strong passwords that are easy to remember.

  • DNA sequencing: Palindromes are found in DNA sequences and are used in genetic analysis.

  • Text encryption: Palindromes can be used as encryption keys.


Execute Asynchronous Functions in Parallel

Problem Statement

LeetCode Problem 845: Longest Mountain in Array

Given an array of integers, find the length of the longest mountain. A mountain is defined as a sequence of numbers that starts with a peak, followed by a valley, and ends with a peak.

Solution

This problem can be solved using asynchronous functions to process the array in parallel. Here's a breakdown of the solution:

1. Divide the Array into Subarrays:

We start by dividing the array into smaller subarrays, each containing a potential mountain. A mountain can be identified as a subarray where the first element is greater than or equal to its neighbors, and the last element is greater than or equal to its neighbors.

2. Process Subarrays in Parallel:

We use the async and await keywords to execute functions in parallel. Each subarray is processed asynchronously in a separate function, which determines if it is a mountain and calculates its length.

3. Combine Results:

Once all subarray processing is complete, we combine the results to find the longest mountain.

Example Implementation:

async function findLongestMountain(nums) {
  // Divide the array into subarrays
  const subarrays = [];
  let start = 0;
  for (let i = 1; i < nums.length; i++) {
    if (nums[i] < nums[i - 1]) {
      // Potential end of a mountain
      subarrays.push(nums.slice(start, i));
      start = i;
    }
  }

  // Process subarrays in parallel
  const results = await Promise.all(
    subarrays.map(async (subarray) => {
      // Check if subarray is a mountain
      const isMountain = isMountainous(subarray);
      return isMountain ? subarray.length : 0;
    })
  );

  // Find the longest mountain
  const longestMountain = Math.max(...results);
  return longestMountain;
}

function isMountainous(subarray) {
  // Check if subarray has at least 3 elements
  if (subarray.length < 3) {
    return false;
  }

  // Check if subarray has a peak
  const peakIndex = subarray.indexOf(Math.max(...subarray));
  if (peakIndex === 0 || peakIndex === subarray.length - 1) {
    return false;
  }

  // Check if subarray has a valley
  for (let i = peakIndex + 1; i < subarray.length; i++) {
    if (subarray[i] >= subarray[i - 1]) {
      return false;
    }
  }

  return true;
}

Real-World Applications

Asynchronous functions and parallel processing are frequently used in real-world applications, especially for tasks that involve handling large datasets or time-consuming operations. Here are a few examples:

  • Data Analysis: Processing and analyzing large amounts of data in parallel to speed up the process.

  • Image Processing: Applying filters and transformations to images in parallel to enhance them or extract features.

  • Financial Modeling: Running simulations and calculations in parallel to forecast financial outcomes.

  • Video Encoding: Transcoding videos into different formats in parallel to reduce processing time.

  • Web Servers: Handling multiple HTTP requests concurrently using asynchronous functions to improve performance.


Event Emitter

Event Emitter

An event emitter is a JavaScript object that can emit events. Events are named actions that can occur within the application, and event emitters allow different parts of the application to listen for and respond to these events.

Implementation

// Create an event emitter
const EventEmitter = require('events');
const emitter = new EventEmitter();

// Define an event listener
emitter.on('message', (data) => {
  console.log(`Received a message: ${data}`);
});

// Emit an event
emitter.emit('message', 'Hello, world!');

Breakdown

  1. Create an Event Emitter: We first import the EventEmitter class and create a new instance of it.

  2. Define an Event Listener: We define a function to handle the "message" event. This function will be executed whenever the event is emitted.

  3. Emit an Event: We call the emit() method on the event emitter, passing in the event name and any data we want to associate with the event. This triggers the execution of all registered listeners for that event.

Real-World Applications

Event emitters are widely used in various applications, such as:

  • WebSockets: Event emitters can be used to handle WebSocket connections, allowing different parts of the application to communicate with each other in real-time.

  • UI Interaction: Event emitters can be used to handle UI events, such as button clicks or mouse movements, allowing the application to respond accordingly.

  • Custom Events: Event emitters can be used to create custom events within an application, allowing different modules or components to communicate with each other.


Sort By

Problem: Sort an array of integers in ascending order.

Best & Performant Solution: Quicksort

JavaScript Implementation:

function quicksort(arr) {
  if (arr.length <= 1) {
    return arr;
  }

  const pivot = arr[0]; // Choose the first element as the pivot
  const left = []; // Array for elements less than the pivot
  const equal = []; // Array for elements equal to the pivot
  const right = []; // Array for elements greater than the pivot

  // Partition the array into three subarrays
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else if (arr[i] === pivot) {
      equal.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }

  // Recursively sort the left and right subarrays
  return quicksort(left).concat(equal).concat(quicksort(right));
}

How Quicksort Works:

Quicksort is a divide-and-conquer sorting algorithm that works as follows:

  1. Choose a pivot: Select an element from the array as the pivot.

  2. Partition the array: Create three subarrays: one for elements less than the pivot, one for elements equal to the pivot, and one for elements greater than the pivot.

  3. Recursively sort the subarrays: Apply quicksort to the left and right subarrays recursively.

  4. Combine the sorted subarrays: Concatenate the sorted left subarray, equal subarray, and sorted right subarray to get the sorted array.

Performance:

The performance of quicksort depends on the choice of the pivot. If the pivot is chosen wisely (e.g., the median), the algorithm runs in O(n log n) time on average. However, if the pivot is chosen poorly (e.g., the first element), it can run in O(n^2) time in the worst case.

Real-World Applications:

Quicksort is widely used for large datasets due to its efficiency. It has applications in various fields, including:

  • Database sorting

  • File system searching

  • Web indexing

  • Data compression

  • Machine learning


Counter II

Problem Statement:

Given an array of integers, return an array that represents the count of each unique integer in the original array.

Example:

Input: [1, 1, 2, 2, 3, 3, 4, 4, 5]
Output: [2, 2, 2, 2, 1]

Solution Using Hash Map:

A hash map is a data structure that stores key-value pairs, where the keys can be used to quickly retrieve the corresponding values. In this solution, we will use a hash map to store the unique integers as keys and their counts as values.

Steps:

  1. Initialize an empty hash map.

  2. Iterate over the input array:

    • For each integer, check if it is already in the hash map.

    • If it is in the map, increment its count.

    • If it is not in the map, add it as a new key and set its count to 1.

  3. Create an empty array to store the output.

  4. Iterate over the hash map:

    • For each key-value pair in the map, append the count to the output array.

Code Implementation:

/**
 * Given an array of integers, return an array that represents the count of each unique integer in the original array.
 *
 * @param {number[]} nums
 * @return {number[]}
 */
const countUniques = (nums) => {
  const counts = {};

  // Count the occurrences of each unique integer
  for (const num of nums) {
    if (counts[num]) {
      counts[num]++;
    } else {
      counts[num] = 1;
    }
  }

  // Convert the counts object into an array
  const output = [];
  for (const key in counts) {
    output.push(counts[key]);
  }

  return output;
};

Real World Applications:

This algorithm can be used in various real-world scenarios, such as:

  • Word counting: Counting the frequency of words in a document.

  • Character counting: Counting the frequency of characters in a string.

  • Data analysis: Identifying and summarizing patterns in large datasets.


Undefined to Null

Problem Statement

The problem is to convert a JavaScript undefined value to null.

Solution

The simplest and most straightforward way to convert undefined to null is to assign null to the undefined variable, as shown below:

let x; // undefined
x = null; // null

Explanation

  • The let keyword declares a variable named x.

  • Initially, x is undefined because it has not been assigned a value.

  • The assignment operator (=) assigns the value null to the variable x.

  • After the assignment, the value of x is null.

Real-World Applications

Converting undefined to null can be useful in situations where you need to ensure that a variable has a specific value. For example, you may have a function that takes a parameter that is expected to be null, and you want to handle the case where the parameter is undefined. By converting undefined to null, you can ensure that the function behaves as expected.

Potential Applications

  • Database programming: When working with databases, it is often necessary to represent missing or unknown values as null.

  • Web development: In web development, undefined values can occur when elements or properties are not found in the DOM. Converting undefined to null can help to simplify error handling and make code more robust.

  • Data validation: When validating user input, it is common to check for undefined values and convert them to null to ensure that the data is in the correct format.


Memoize

Memoize

Memoization is a technique used in programming to improve the performance of functions that are called with the same arguments multiple times. It works by storing the results of previous calls in a cache, so that subsequent calls with the same arguments can return the cached result instead of having to re-execute the function.

This can be particularly beneficial for functions that are computationally expensive, as it can save a significant amount of time by avoiding unnecessary re-executions.

How it Works

A memoization function takes two parameters:

  • The function to be memoized

  • A cache object

The cache object is used to store the results of previous calls to the memoized function. When the memoized function is called, it checks the cache to see if the result for the current arguments has already been calculated. If it has, the cached result is returned. Otherwise, the function is executed as normal and the result is stored in the cache for future reference.

Implementation

Here is an example of how to memoize a function in JavaScript using a simple object as the cache:

const memoize = (fn, cache) => {
  return (...args) => {
    const key = args.join(',');
    if (key in cache) {
      return cache[key];
    } else {
      const result = fn(...args);
      cache[key] = result;
      return result;
    }
  };
};

To use this memoized function, simply wrap the original function in a call to the memoize function. For example:

const memoizedFunction = memoize(expensiveFunction);
const result = memoizedFunction(1, 2, 3);

Benefits and Applications

Memoization can provide significant performance benefits for functions that are frequently called with the same arguments. This can be particularly useful for:

  • Functions that perform complex computations

  • Functions that retrieve data from a database or other slow resource

  • Functions that are called in loops

Applications for memoization include:

  • Caching database queries

  • Optimizing search algorithms

  • Improving the performance of GUI applications


Curry

Problem Statement:

Given an array of integers representing the orders of various dishes, your task is to return a list of the unique dishes ordered, grouped by their dish number (in sorted order).

For Example:

arr = [1, 2, 2, 1, 3]
Output: [[1, 1], [2, 2, 2], [3, 3]]

Implementation:

/**
 * @param {number[]} orders
 * @return {number[][]}
 */
const groupDishes = (orders) => {
  const map = {};

  for (const order of orders) {
    const dish = order[0];
    const ingredients = order.slice(1);

    if (!map[dish]) {
      map[dish] = [ingredients];
    } else {
      map[dish].push(ingredients);
    }
  }

  const result = [];

  for (const dish in map) {
    const ingredients = map[dish];
    const uniqueIngredients = [...new Set(ingredients.flat())];
    result.push([dish, ...uniqueIngredients]);
  }

  result.sort((a, b) => a[0] - b[0]);
  return result;
};

Breakdown:

  1. Create a Map: We create a map where the dish number is the key and the value is an array of arrays, each representing the ingredients of a specific order for that dish.

  2. Group Ingredients: We iterate over each order and add its ingredients to the corresponding dish in the map. If the dish is not in the map yet, we create a new entry.

  3. Get Unique Ingredients: For each dish, we find the unique ingredients by flattening all the ingredient arrays and converting them to a set.

  4. Create the Result: We create a result array and push a tuple containing the dish number and the unique ingredients for each dish.

  5. Sort the Result: We sort the result array by the dish number to put the dishes in order.

Applications:

This algorithm can be used in various real-world scenarios, such as:

  • Restaurant Ordering: Grouping orders for different dishes to help the kitchen prepare them efficiently.

  • Inventory Management: Identifying duplicate items and grouping them to optimize storage and tracking.

  • Data Analysis: Categorizing data, such as customer purchases, to identify patterns and trends.


Snail Traversal

Problem Statement:

Given a 2D matrix, find the elements in a spiral order.

Example:

Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,2,3,6,9,8,7,4,5]

Implementation:

Here's a performant JavaScript solution:

const spiralOrder = (matrix) => {
  if (!matrix || matrix.length === 0) return []; // Check for empty matrix

  const rows = matrix.length, cols = matrix[0].length; // Dimensions of the matrix
  let result = [], top = 0, bottom = rows - 1, left = 0, right = cols - 1; // Boundaries

  while (left <= right && top <= bottom) {
    // Traverse top row from left to right
    for (let i = left; i <= right; i++) result.push(matrix[top][i]);
    // Traverse right column from top to bottom
    for (let i = top + 1; i <= bottom - 1; i++) result.push(matrix[i][right]);
    // If there are more than one row left, traverse bottom row from right to left
    if (top < bottom) {
      for (let i = right; i >= left; i--) result.push(matrix[bottom][i]);
    }
    // If there are more than one column left, traverse left column from bottom to top
    if (left < right) {
      for (let i = bottom - 1; i >= top + 1; i--) result.push(matrix[i][left]);
    }
    // Update boundaries for the next iteration
    top++;
    bottom--;
    left++;
    right--;
  }

  return result;
};

Breakdown:

  1. Check for Empty Matrix: If the input matrix is empty or null, return an empty array.

  2. Set Boundaries: Initialize top, bottom, left, and right to the edges of the matrix. These will represent the current boundaries for traversal.

  3. Spiral Traversal: Create an empty result array. While the boundaries are valid (i.e., left is less than or equal to right and top is less than or equal to bottom), continue the following steps:

    • Traverse the top row from left to right.

    • Traverse the right column from top to bottom.

    • If there are more than one row left, traverse the bottom row from right to left.

    • If there are more than one column left, traverse the left column from bottom to top.

  4. Update Boundaries: After each round of traversal, update the boundaries to shrink the traversal area for the next iteration.

  5. Return Result: Once all elements have been traversed, return the result array.

Application:

The spiral traversal algorithm has various applications, including:

  • Matrix traversal and data analysis

  • Image processing and feature detection

  • Pathfinding in 2D grids

  • Solving puzzles and games like Sudoku


Memoize II

Memoization is a technique used in computing to improve the performance of a function by storing the results of previous function calls in a cache. This way, when the same input is encountered again, the cached result can be returned directly, avoiding the need to recompute the same result.

Memoization II

Memoization II is a variation of the memoization technique that enhances its performance by using an array to store the cached results. This improves the efficiency of accessing the cached values, especially when dealing with larger datasets or complex computations.

Implementation in JavaScript

function memoize(fn) {
  const cache = [];
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

Breakdown:

  1. Create a cache array.

  2. Wrap the original function in a new function that takes the same arguments.

  3. Convert the arguments to a JSON string (key) to use as the cache index.

  4. Check if the cache contains the result for the given key. If so, return the cached result.

  5. Otherwise, call the original function with the arguments, store the result in the cache, and return the result.

Example:

const fibonacci = (n) => (n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2));

const memoizedFibonacci = memoize(fibonacci);

console.log(memoizedFibonacci(10)); // 55

Real-World Applications:

Memoization finds applications in various scenarios:

  • Database querying: Caching query results to avoid repeated database access.

  • Web development: Using memoization in Javascript to enhance front-end performance by caching data fetched from APIs.

  • Artificial intelligence: Storing intermediate results of machine learning models to optimize computational efficiency.

  • Dynamic programming: Solving recursive problems efficiently by caching subproblem solutions.


Make Object Immutable

Problem Statement:

Given an object, make it immutable, meaning that its properties cannot be changed once it is created.

Solution:

Using Object.freeze()

The simplest and most effective way to make an object immutable is to use the Object.freeze() method. This method creates a new, immutable copy of the object, while the original object remains mutable.

Implementation:

const mutableObject = {
  name: 'John',
  age: 30
};

const immutableObject = Object.freeze(mutableObject);

// Trying to change the property of the immutable object will throw an exception
immutableObject.name = 'Mary';

Benefits of Object.freeze():

  • Simple and Efficient: Object.freeze() is an efficient way to create immutable objects.

  • Deep Freeze: It recursively freezes all properties and child objects.

  • Immutable: Once an object is frozen, it cannot be modified in any way.

Real-World Applications:

  • Data Integrity: Immutable objects can be used to ensure that data remains consistent and accurate.

  • Security: Immutable objects can help protect against malicious code that attempts to modify sensitive data.

  • Concurrency: Immutable objects can be safely shared across threads without the risk of data corruption.

Alternative Approaches:

Using Object.seal()

Object.seal() is similar to Object.freeze(), but it only prevents the addition of new properties and deletion of existing properties. Properties that already exist can still be modified.

Implementation:

const mutableObject = {
  name: 'John',
  age: 30
};

const sealedObject = Object.seal(mutableObject);

// Trying to add a new property to the sealed object will throw an exception
sealedObject.city = 'New York';

// Modifying an existing property is still allowed
sealedObject.age = 31;

Benefits of Object.seal():

  • Partial Immutability: It allows for partial immutability by preventing certain changes while allowing others.

Using Proxies

Proxies can be used to create custom behaviors for objects, including immutability.

Implementation:

const mutableObject = {
  name: 'John'
};

const immutableProxy = new Proxy(mutableObject, {
  set: (target, property) => {
    throw new Error('Cannot modify immutable object');
  }
});

// Trying to modify the immutable proxy will throw an exception
immutableProxy.name = 'Mary';

Benefits of Proxies:

  • Customization: Proxies allow for fine-grained control over object behavior.

  • Flexibility: Custom immutability rules can be implemented.

Comparison:

Approach
Immutability
Deep Freeze
Performance

Object.freeze()

Full

Yes

Excellent

Object.seal()

Partial

No

Good

Proxies

Custom

No

Variable

Conclusion:

Object.freeze() is the recommended approach for creating immutable objects in JavaScript due to its simplicity, efficiency, and deep freeze capability.


Immutability Helper

Immutability Helper

Problem:

Given an array of integers, create a helper function to make the array immutable.

Implementation:

const makeImmutable = (arr) => {
  return Object.freeze(arr);
};

const arr = [1, 2, 3];
makeImmutable(arr);

// Trying to modify the array now will throw an error
arr[0] = 4; // Error: Cannot assign to read-only property '0' of object '#<Array>'

Explanation:

  • The Object.freeze() method in JavaScript is used to make an object immutable. When an object is frozen, its properties cannot be added, removed, or modified.

  • In the above code, we pass the array to the Object.freeze() method to make it immutable. This means that any attempt to modify the array will result in an error.

Benefits of Immutability:

  • Thread safety: Immutable objects are thread-safe, which means that multiple threads can access them concurrently without causing any data corruption.

  • Reduced bugs: Immutable objects help to reduce bugs by preventing accidental modifications.

  • Improved performance: Immutable objects can improve performance by reducing the number of times the object needs to be copied.

Real-World Applications:

  • Immutable objects can be used in any situation where it is important to prevent accidental modifications to data.

  • For example, immutable objects can be used to store configuration settings or to represent data that is shared across multiple threads.


Join Two Arrays by ID

Problem:

Given two arrays of objects, where each object has an id property. Merge the two arrays and return a new array of objects where each object has all the properties of the corresponding objects from the two arrays.

Best & Performant Solution:

function joinArraysById(array1, array2) {
  const result = [];

  for (let i = 0; i < array1.length; i++) {
    const obj1 = array1[i];
    const obj2 = array2.find((obj) => obj.id === obj1.id);

    if (obj2) {
      result.push({ ...obj1, ...obj2 });
    } else {
      result.push(obj1);
    }
  }

  return result;
}

How it works:

  1. Iterate over the first array: For each object in the first array, we'll call it obj1.

  2. Find corresponding object in the second array: We use array2.find((obj) => obj.id === obj1.id) to find the object in the second array that has the same id as obj1. Let's call this object obj2.

  3. Merge objects: If obj2 is found, we merge obj1 and obj2 into a new object that includes all the properties of both objects.

  4. Add merged or original object to result: If obj2 is not found, it means there's no corresponding object in the second array, so we simply add the original obj1 to the result.

  5. Repeat for all objects: We repeat this process for all objects in the first array.

Real-World Application:

This function can be used to merge data from different sources or to combine similar objects from different arrays. For example, you could merge data from two APIs or combine customer data from different systems.

Code Implementation Example:

const array1 = [
  { id: 1, name: 'John', age: 25 },
  { id: 2, name: 'Mary', age: 30 },
];

const array2 = [
  { id: 1, address: '123 Main St.' },
  { id: 3, name: 'Tom', age: 40 },
];

const result = joinArraysById(array1, array2);

console.log(result);

Output:

[
  { id: 1, name: 'John', age: 25, address: '123 Main St.' },
  { id: 2, name: 'Mary', age: 30 },
  { id: 3, name: 'Tom', age: 40 }
]

Convert Callback Based Function to Promise Based Function

Original Callback-Based Function:

function getUsers(callback) {
  // Perform an asynchronous operation, e.g. a network request
  setTimeout(() => {
    const users = ["John", "Mary", "Bob"];
    callback(users);
  }, 1000);
}

Promise-Based Function:

To convert the callback-based function to promise-based, we use the Promise constructor. A promise represents the eventual result of an asynchronous operation, either resolved (successful) or rejected (failed).

function getUsers() {
  return new Promise((resolve, reject) => {
    // Perform an asynchronous operation, e.g. a network request
    setTimeout(() => {
      const users = ["John", "Mary", "Bob"];
      resolve(users);
    }, 1000);
  });
}

Breakdown:

  • new Promise((resolve, reject) => {...}): This creates a new promise. The resolve and reject functions are passed as arguments to the promise constructor. They are called to resolve or reject the promise, respectively.

  • resolve(users): When the asynchronous operation is successful, we call resolve with the result, which in this case is the array of users. This resolves the promise.

  • reject(error): If the asynchronous operation fails, we call reject with the error. This rejects the promise.

Usage:

To use the promise-based function, we can chain a .then() method to handle the resolved value and a .catch() method to handle any errors.

getUsers()
  .then((users) => {
    // Handle the resolved promise with the users array
    console.log(users);
  })
  .catch((error) => {
    // Handle any errors during the asynchronous operation
    console.log(error);
  });

Benefits of Promises:

Promises provide several benefits over callbacks:

  • Improved readability and maintainability: Promises use a more intuitive and concise syntax, making it easier to understand and debug asynchronous code.

  • Error handling: Promises provide a built-in way to handle errors, making it easier to manage and respond to failures.

  • Immutability: Promises are immutable, meaning they cannot be modified once they are resolved or rejected. This ensures that the state of the promise is consistent and predictable.


Date Range Generator

Problem Statement:

Given a start date and an end date, generate a list of all dates within that range.

Best Solution:

function dateRangeGenerator(startDate, endDate) {
  const dates = [];

  const start = new Date(startDate);
  const end = new Date(endDate);

  while (start <= end) {
    dates.push(new Date(start));
    start.setDate(start.getDate() + 1);
  }

  return dates;
}

Breakdown:

  1. Initialize an array to store the dates.

  2. Create Date objects for the start and end dates.

  3. Enter a while loop that continues until the start date is greater than the end date.

  4. Inside the loop:

    • Push a new Date object for the start date into the array.

    • Increment the start date by one day.

  5. Return the array of dates.

Example Usage:

const startDate = '2023-01-01';
const endDate = '2023-01-10';

const dates = dateRangeGenerator(startDate, endDate);

console.log(dates);
// Output: [
//   '2023-01-01',
//   '2023-01-02',
//   '2023-01-03',
//   '2023-01-04',
//   '2023-01-05',
//   '2023-01-06',
//   '2023-01-07',
//   '2023-01-08',
//   '2023-01-09',
//   '2023-01-10'
// ]

Real-World Applications:

This function can be used in various scenarios, such as:

  • Scheduling appointments: Generate a list of available dates for an appointment.

  • Tracking project timelines: Determine the duration of a project by calculating the difference between the start and end dates.

  • Creating calendars: Populate a calendar with the dates for a specific month or year.


Query Batching

Problem:

LeetCode Problem 836: Rectangle Overlap

Given two axis-aligned rectangles, determine if they overlap.

Example:

Example 1:

rect1 = [0, 0, 2, 2]
rect2 = [1, 1, 3, 3]
isOverlap = true

Example 2:

rect1 = [0, 0, 1, 1]
rect2 = [1, 0, 2, 1]
isOverlap = false

Solution 1: Brute Force

const isRectangleOverlap = (rect1, rect2) => {
  // Check if the x-axes intersect
  const xOverlap = (rect1[0] <= rect2[0] && rect1[2] >= rect2[0]) || (rect2[0] <= rect1[0] && rect2[2] >= rect1[0]);

  // Check if the y-axes intersect
  const yOverlap = (rect1[1] <= rect2[1] && rect1[3] >= rect2[1]) || (rect2[1] <= rect1[1] && rect2[3] >= rect1[1]);

  // If both x and y axes intersect, the rectangles overlap
  return xOverlap && yOverlap;
};

Explanation:

This brute force solution checks the intersections of the rectangles on both the x and y axes. If both axes intersect, the rectangles overlap.

Solution 2: Simplified Brute Force

const isRectangleOverlap = (rect1, rect2) => {
  return !(
    rect1[2] < rect2[0] ||
    rect1[0] > rect2[2] ||
    rect1[3] < rect2[1] ||
    rect1[1] > rect2[3]
  );
};

Explanation:

This simplified brute force solution checks if any of the following conditions are true:

  • The right edge of rect1 is to the left of the left edge of rect2

  • The left edge of rect1 is to the right of the right edge of rect2

  • The top edge of rect1 is below the bottom edge of rect2

  • The bottom edge of rect1 is above the top edge of rect2

If any of these conditions are true, the rectangles do not overlap. Otherwise, they overlap.

Potential Applications

  • Determining if two objects intersect in a game or simulation

  • Checking for collisions between elements on a web page

  • Identifying overlapping time slots in a calendar


Create Hello World Function

Hello World Function

Problem Statement: Write a function that prints "Hello World" to the console.

Solution:

// Function declaration
function helloWorld() {
  // Print "Hello World" to the console
  console.log("Hello World");
}

// Call the function
helloWorld();

Breakdown:

  • Function Declaration: The function keyword is used to declare a function. The function's name is helloWorld.

  • Function Parameters: The function takes no parameters, so the parentheses are empty.

  • Function Body: The function body contains the code that will be executed when the function is called. In this case, it prints "Hello World" to the console.

  • Console Log: The console.log() function is used to print output to the console. The string "Hello World" is passed as an argument to the function.

  • Function Call: The helloWorld() function is called at the end of the code. This executes the function and prints "Hello World" to the console.

Real-World Applications:

  • Printing output to the console is used for debugging purposes.

  • Logging information to the console can help you track the progress of a program or identify errors.

  • Console output can be used to communicate with users or display messages to them.


Differences Between Two Objects

Given LeetCode Problem and Implementation

Problem: Given two objects, determine which properties are different between them.

Implementation:

const object1 = {
  name: 'John',
  age: 30,
  city: 'New York'
};

const object2 = {
  name: 'John',
  age: 31,
  city: 'London'
};

const compareObjects = (obj1, obj2) => {
  // Create an empty array to store the differences
  const differences = [];

  // Loop through the keys of object1
  for (const key in obj1) {
    // If the key does not exist in object2, or the values are different, add the key to the differences array
    if (!(key in obj2) || obj1[key] !== obj2[key]) {
      differences.push(key);
    }
  }

  // Return the array of differences
  return differences;
};

const differentProperties = compareObjects(object1, object2);
console.log(differentProperties); // Output: ['age', 'city']

Breakdown and Explanation:

1. Create an Empty Array for Differences: We create an empty array called differences to store the keys of properties that are different between the objects.

2. Loop Through the Keys of Object1: We use a for-in loop to iterate through all the keys in object1.

3. Check for Key in Object2 and Value Equality: For each key in object1, we check if the same key exists in object2. If not, or if the values for the corresponding keys are different, we add the key to the differences array.

4. Return the Array of Differences: After checking all the keys in object1, we return the differences array, which contains the keys of the properties that are different between the two objects.

Applications in Real World:

This function can be useful in various real-world scenarios:

  • Data Validation: Comparing two objects to verify that they contain the expected values or have undergone any unauthorized changes.

  • Object Merging: Identifying differences between two similar objects to create a merged object with the most up-to-date information.

  • Object Comparison: Determining the similarities and differences between objects, such as products in an e-commerce system.


Parallel Execution of Promises for Individual Results Retrieval

Problem:

Given an array of promises, retrieve the results of each promise in parallel and return them in an array.

Solution:

Using Promise.allSettled():

const getResults = async (promises) => {
  const results = await Promise.allSettled(promises);
  return results.map((result) => result.value);
};

Breakdown:

  • Promise.allSettled() takes an array of promises as input and returns a promise that resolves to an array of objects.

  • Each object in the array contains two properties:

    • status: Indicates whether the promise was fulfilled ("fulfilled") or rejected ("rejected").

    • value: The result of the promise if it was fulfilled, or the reason for rejection if it was rejected.

  • The .map() method is then used to extract the values from the results array, returning an array of fulfilled promise values.

Example:

const promises = [
  Promise.resolve("A"),
  Promise.resolve("B"),
  Promise.reject("Error")
];

getResults(promises).then((results) => {
  console.log(results); // ["A", "B"]
});

Real-World Applications:

  • Concurrent API calls: Retrieve data from multiple APIs simultaneously.

  • Parallel data processing: Process large datasets in parallel for faster execution.

  • Caching: Retrieve cached data in parallel to improve performance.


Delay the Resolution of Each Promise

Problem:

Given an array of promises, delay the resolution of each promise by a specified number of milliseconds.

Implementation:

const delayPromises = (promises, delayMs) => {
  // Create an array to store the delayed promises
  const delayedPromises = [];

  // Iterate over the input promises
  promises.forEach((promise) => {
    // Create a new Promise that will delay the resolution of the input promise
    delayedPromises.push(
      new Promise((resolve) => {
        // After the specified delay, resolve the delayed promise with the value of the input promise
        setTimeout(() => {
          promise.then((result) => {
            resolve(result);
          });
        }, delayMs);
      })
    );
  });

  // Return the array of delayed promises
  return delayedPromises;
};

Breakdown:

  • We create an empty array to store the delayed promises.

  • We iterate over the input promises, creating a new Promise for each one.

  • The new Promise resolves after the specified delay, with the value of the input promise.

  • We collect the new promises in the delayedPromises array.

  • Finally, we return the delayedPromises array.

Usage:

// Array of promises
const promises = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3),
];

// Delay by 100ms
const delayMs = 100;

// Delay the resolutions of the promises
const delayedPromises = delayPromises(promises, delayMs);

// Wait for the delayed promises to resolve
Promise.all(delayedPromises).then((results) => {
  // The array `results` will contain the values of the input promises after the delay
  console.log(results); // [1, 2, 3]
});

Real-World Application:

  • Debouncing: Delaying button clicks to prevent multiple requests.

  • Throttling: Limiting the number of API calls within a specific time window.

  • Animation: Controlling the timing of animations to create visual effects.


Bind Function to Context

Problem Statement:

Given a function foo and an object bar, bind the foo function to the bar object so that when foo is called, it uses bar as its this context.

Solution using JavaScript's built-in bind() method:

// Bind the foo function to the bar object
const boundFoo = foo.bind(bar);

// Call the boundFoo function
boundFoo();

// This will call the foo function with the bar object as its this context

Breakdown and Explanation:

  • bind() is a built-in JavaScript method that creates a new function that has the same code as the original function but with a different this context.

  • In the above example, foo.bind(bar) creates a new function called boundFoo that has the same code as foo but with its this context set to bar.

  • When boundFoo is called, it will use bar as its this context, which means that any references to this inside boundFoo will refer to bar.

  • This is useful for situations where you want to use a function with a different this context than the one it was originally defined with.

Real-World Applications:

  • Event handling: Bind event handlers to specific DOM elements to ensure that the this context is set to the correct element.

  • Constructor functions: Bind constructor functions to specific objects to ensure that the this context is set to the correct object.

  • Callbacks: Bind callbacks to specific objects to ensure that the this context is set to the correct object when the callback is invoked.


Group By

Group By

Problem Statement:

Given an array of objects, group them based on a specific property.

Javascript Implementation:

const groupBy = (arr, property) => {
  const groups = {};
  for (const obj of arr) {
    const key = obj[property];
    if (!groups[key]) {
      groups[key] = [];
    }
    groups[key].push(obj);
  }
  return groups;
};

Breakdown:

  • groupBy function takes two arguments:

    • arr: The array of objects to group.

    • property: The property to group by.

  • The function initializes an empty object called groups.

  • It iterates through the array of objects and for each object:

    • Extracts the value of the specified property.

    • Checks if a group exists for that property in groups.

    • If the group doesn't exist, it creates a new array for that property.

    • Otherwise, it pushes the object into the existing group.

  • Finally, it returns the groups object, which contains property values as keys and arrays of objects as values.

Example:

const objects = [
  { id: 1, name: "John" },
  { id: 2, name: "Jane" },
  { id: 3, name: "John" },
  { id: 4, name: "Mary" }
];

const groupedObjects = groupBy(objects, "name");

console.log(groupedObjects);
// {
//   John: [{ id: 1, name: "John" }, { id: 3, name: "John" }],
//   Jane: [{ id: 2, name: "Jane" }],
//   Mary: [{ id: 4, name: "Mary" }]
// }

Real-World Applications:

  • Data Visualization: Creating charts and graphs by grouping data based on categories.

  • Database Optimization: Partitioning data into groups can improve query performance.

  • Machine Learning: Clustering data points based on common features.

  • User Interface: Organizing data in user interfaces based on relevance or context.


Interval Cancellation

Interval Cancellation

Problem:

You have a list of intervals, where each interval is represented as a tuple [start, end]. Each interval represents an event that starts at start and ends at end. You want to find the minimum number of intervals that need to be removed to ensure that no two remaining intervals overlap.

Example:

Input: intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]
Output: 2
Explanation:
Remove intervals [2, 6] and [15, 18].

Solution:

The key idea is to sort the intervals based on their starting points. This allows us to consider the intervals in chronological order and make removal decisions efficiently. Here's the algorithm:

  1. Sort the intervals in ascending order of their starting points.

  2. Initialize a pointer to point to the current interval.

  3. Iterate over the remaining intervals:

    • If the current interval overlaps with the next interval, remove the current interval and move the pointer to the next interval.

    • Otherwise, move the pointer to the next interval.

Javascript Implementation:

/**
 * @param {number[][]} intervals
 * @return {number}
 */
const eraseOverlapIntervals = function (intervals) {
  if (!intervals.length) {
    return 0;
  }

  // Sort intervals by starting points.
  intervals.sort((a, b) => a[0] - b[0]);

  let count = 0; // Count of removed intervals.
  let prevEnd = intervals[0][1]; // Previous interval end point.

  for (let i = 1; i < intervals.length; i++) {
    const [start, end] = intervals[i];

    // If overlap, remove current interval.
    if (start < prevEnd) {
      count++;
    } else {
      prevEnd = end;
    }
  }

  return count;
};

Explanation:

  1. Sort the intervals using intervals.sort((a, b) => a[0] - b[0]) to arrange them in ascending order based on their starting points.

  2. Initialize count to 0 to track the number of removed intervals and prevEnd to the end point of the first interval.

  3. Iterate through the remaining intervals (starting from the second interval) using a loop (for (let i = 1; i < intervals.length; i++)).

  4. Inside the loop, check if the current interval overlaps with the previous interval. This is done by comparing the starting point of the current interval to the end point of the previous interval (if (start < prevEnd)).

  5. If overlap occurs, remove the current interval and increment the count (count++).

  6. Otherwise, update prevEnd to the end point of the current interval to check for future overlaps.

  7. After iterating through all intervals, return count, the total number of removed intervals.

Real-World Applications:

This algorithm can be applied in various scenarios where you need to manage overlapping events or resources:

  • Scheduling: Optimizing a schedule to avoid conflicts between events or tasks.

  • Resource Allocation: Distributing resources to maximize utilization while minimizing overlapping usage.

  • Scheduling Algorithms: Designing algorithms that efficiently allocate time slots or resources to avoid conflicts and maximize efficiency.


Cache With Time Limit

Cache with Time Limit

Problem:

Implement a cache that stores data for a limited amount of time. When data is accessed, it should be returned if it's not expired, otherwise, it should be removed from the cache.

Solution:

1. Using a Map:

We can use a JavaScript Map to store the cache data. Each key in the map will represent the data key, and the value will be an object containing both the data and its expiration timestamp.

class TimeLimitedCache {
  constructor(expirationTime) {
    this.cache = new Map();
    this.expirationTime = expirationTime;
  }

  set(key, value) {
    this.cache.set(key, {
      data: value,
      expiration: Date.now() + this.expirationTime
    });
  }

  get(key) {
    const cachedValue = this.cache.get(key);
    if (!cachedValue) return null;

    if (cachedValue.expiration <= Date.now()) {
      this.cache.delete(key);
      return null;
    }

    return cachedValue.data;
  }
}

How it works:

  • constructor initializes the cache and sets the expiration time.

  • set stores the data in the cache with its expiration timestamp.

  • get checks if the data is in the cache and not expired. If so, it returns the data; otherwise, it removes the data from the cache and returns null.

2. Using a Timeout Function:

This approach uses a timeout function to automatically remove expired data after a set period.

class TimeLimitedCache {
  constructor(expirationTime) {
    this.cache = {};
    this.expirationTime = expirationTime;
  }

  set(key, value) {
    this.cache[key] = {
      data: value,
      timeout: setTimeout(() => { delete this.cache[key]; }, this.expirationTime)
    };
  }

  get(key) {
    const cachedValue = this.cache[key];
    if (!cachedValue) return null;

    return cachedValue.data;
  }
}

How it works:

  • constructor initializes the cache and sets the expiration time.

  • set stores the data in the cache and schedules a timeout function to expire the data later.

  • get returns the data if it's in the cache, otherwise it returns null.

Example Usage:

const cache = new TimeLimitedCache(60000); // 1 minute expiration time

cache.set("name", "John");
console.log(cache.get("name")); // "John"

// After 1 minute, the data expires and is removed from the cache
console.log(cache.get("name")); // null

Applications:

  • Caching API responses to improve performance

  • Storing user sessions for a limited duration

  • Managing temporary data in databases or other systems


Array Prototype ForEach

Array Prototype ForEach

Overview:

The forEach() method allows you to execute a provided function once for each element in an array. It doesn't return any value and is often used for iterating over arrays to perform specific operations on each element.

Syntax:

array.forEach(callbackFunction);

Parameters:

  • callbackFunction: A function that takes three parameters:

    • currentValue: The current element being processed.

    • currentIndex: The index of the current element.

    • array: The original array being iterated over.

Example:

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((number, index) => {
  console.log(`Element at index ${index}: ${number}`);
});

Output:

Element at index 0: 1
Element at index 1: 2
Element at index 2: 3
Element at index 3: 4
Element at index 4: 5

Real-World Applications:

  • Iterating over arrays: ForEach can be used to iterate over arrays and perform various operations on each element, such as filtering, sorting, or modifying the array.

  • Processing datasets: ForEach can be used to process large datasets by splitting the data into chunks and applying a function to each chunk.

  • Event handling: ForEach can be used to iterate over elements in an array and add event listeners to each element, such as click or hover events.

Tips:

  • The callback function can be an arrow function, a regular function, or a function expression.

  • ForEach doesn't return any value, so be careful not to use it where a return value is expected.

  • ForEach iterates over the original array, so any modifications made to the array within the callback function will affect the original array.

  • ForEach doesn't work on sparse arrays, meaning arrays that have empty elements or "holes."


Allow One Function Call

Problem Statement:

Given a string, return the count of the number of substrings that contain only one distinct letter.

Example 1:

Input: "aaabb"
Output: 9
Explanation: There are 9 substrings: "a", "a", "aa", "aa", "b", "b", "bb", "bb", and "bb".

Example 2:

Input: "abcabc"
Output: 10
Explanation: There are 10 substrings: "a", "a", "b", "b", "c", "c", "a", "b", "c", and "c".

Solution:

We can use a sliding window approach to solve this problem. The idea is to keep track of the left and right boundaries of a window that contains only one distinct letter. We can then move the window right by one character at a time, and check if the new character is the same as the previous character. If it is, we can extend the window by one character. If it is not, we can move the left boundary of the window to the position of the new character.

Here is the detailed algorithm:

  1. Initialize the left and right boundaries of the window to 0.

  2. While the right boundary of the window is less than the length of the string:

    • Check if the character at the right boundary of the window is the same as the character at the left boundary of the window.

      • If it is, extend the window by one character by moving the right boundary of the window to the right by one.

      • If it is not, move the left boundary of the window to the position of the new character.

  3. Return the number of characters in the window.

Here is the Python code for the solution:

def count_substrings_with_one_distinct_letter(s):
  left = 0
  right = 0
  count = 0

  while right < len(s):
    if s[right] == s[left]:
      right += 1
      count += right - left
    else:
      left = right
    
  return count

Real World Applications:

This problem can be applied to various real-world scenarios, such as:

  • Character recognition: Identifying characters in an image that contains only one distinct letter.

  • DNA analysis: Identifying regions of DNA that contain only one distinct nucleotide.

  • Analysis of programming code: Identifying variables or method names that contain only one distinct letter.


Flatten Deeply Nested Array

Problem:

Given a deeply nested array, flatten it into a single array.

For example:

[1, [2, [3, 4]], 5]

Should be flattened to:

[1, 2, 3, 4, 5]

Solution:

One of the best and most performant solutions is to use a recursive function to traverse the array and collect all the elements into a single array.

Here is the code implementation:

function flattenDeeplyNestedArray(arr) {
  if (!Array.isArray(arr)) {
    return [arr];
  }

  const flattenedArray = [];

  for (let i = 0; i < arr.length; i++) {
    const element = arr[i];

    if (Array.isArray(element)) {
      flattenedArray.push(...flattenDeeplyNestedArray(element));
    } else {
      flattenedArray.push(element);
    }
  }

  return flattenedArray;
}

Explanation:

  1. The function first checks if the input array is actually an array. If it is not, then it simply returns the array as it is.

  2. If the input array is indeed an array, it creates a new empty array called flattenedArray.

  3. The function then iterates over each element in the input array.

  4. For each element, it checks if it is an array itself. If it is, then the function recursively calls itself with that element as the input. This ensures that all nested arrays are flattened.

  5. If the element is not an array, then it is simply pushed into the flattenedArray.

  6. Finally, the function returns the flattenedArray.

Real World Applications:

  • Flattening deeply nested data structures is useful in a variety of real-world applications.

  • For example, it can be used to flatten JSON objects, which are often deeply nested.

  • It can also be used to flatten file paths, which can be useful for creating file lists or directories.

  • Additionally, it can be used to flatten data structures in databases, which can improve performance and simplify queries.


Apply Transform Over Each Element in Array

Problem Statement:

Given an array of elements, apply a given transformation to each element.

Implementation:

/**
 * Applies a transformation to each element in an array.
 *
 * @param {array} arr The array to transform.
 * @param {function} transform The transformation function.
 * @returns {array} The transformed array.
 */
const transformArray = (arr, transform) => {
  const transformedArr = [];
  for (let i = 0; i < arr.length; i++) {
    transformedArr.push(transform(arr[i]));
  }
  return transformedArr;
};

Breakdown:

  • The transformArray function takes two arguments:

    • arr: The array to transform.

    • transform: The transformation function.

  • The function iterates over the array using a for loop.

  • For each element in the array, it applies the transformation function and pushes the result into a new array called transformedArr.

  • Finally, the transformed array is returned.

Example:

const arr = [1, 2, 3, 4, 5];
const transform = (x) => x * x;

const transformedArr = transformArray(arr, transform);

console.log(transformedArr); // [1, 4, 9, 16, 25]

Real-World Applications:

This function can be used in various real-world scenarios:

  • Data manipulation: Transforming data into a different format for analysis or processing.

  • Image editing: Applying filters or transformations to images.

  • Machine learning: Preprocessing data or extracting features from raw data.

  • Data mining: Filtering or transforming data to extract insights and patterns.


Array Reduce Transformation

Problem Statement: Given an array of integers, return a new array where each element is the product of all the other elements in the input array.

Example: Input: [1, 2, 3, 4] Output: [24, 12, 8, 6]

Approach 1: Brute Force This approach iterates over the input array twice. For each element, it multiplies it with the product of all the other elements in the array.

const productExceptSelf = (nums) => {
  const result = [];
  for (let i = 0; i < nums.length; i++) {
    let product = 1;
    for (let j = 0; j < nums.length; j++) {
      if (i === j) continue;
      product *= nums[j];
    }
    result.push(product);
  }
  return result;
};

Complexity:

  • Time Complexity: O(n^2), where n is the length of the input array.

  • Space Complexity: O(n), for the result array.

Approach 2: Using Prefix and Suffix Products This approach uses prefix and suffix arrays to efficiently compute the product of all other elements in the input array.

const productExceptSelf = (nums) => {
  const prefixProducts = [];
  const suffixProducts = [];

  // Calculate prefix products
  for (let i = 0; i < nums.length; i++) {
    prefixProducts.push((i > 0 ? prefixProducts[i - 1] : 1) * nums[i]);
  }

  // Calculate suffix products
  for (let i = nums.length - 1; i >= 0; i--) {
    suffixProducts.unshift((i < nums.length - 1 ? suffixProducts[0] : 1) * nums[i]);
  }

  // Calculate the result
  const result = [];
  for (let i = 0; i < nums.length; i++) {
    result.push((i > 0 ? prefixProducts[i - 1] : 1) * (i < nums.length - 1 ? suffixProducts[i + 1] : 1));
  }

  return result;
};

Complexity:

  • Time Complexity: O(n), where n is the length of the input array.

  • Space Complexity: O(n), for the prefix and suffix products arrays.

Applications:

  • Calculating the Gini coefficient, which measures income inequality.

  • Identifying unusual observations in a dataset.

  • Finding the most influential nodes in a network.


Nested Array Generator

Nested Array Generator

Problem:

Given a number n, generate a nested array of size n, where each element in the array is an array of n elements, and all elements are initially set to 0.

Example:

n = 2
Output:
[[0, 0],
 [0, 0]]

Solution:

We can use a nested loop to create the nested array. The outer loop iterates n times to create the outer array, and the inner loop iterates n times to create the inner array.

Implementation:

/**
 * Generates a nested array of size n, where each element is an array of n elements, and all elements are initially set to 0.
 *
 * @param {number} n The size of the nested array to generate.
 * @returns {number[][]} The nested array generated.
 */
const generateNestedArray = (n) => {
  // Create an empty outer array.
  const outerArray = new Array(n);

  // Iterate over the outer array to create the inner arrays.
  for (let i = 0; i < n; i++) {
    // Create an empty inner array.
    const innerArray = new Array(n);

    // Iterate over the inner array to set all elements to 0.
    for (let j = 0; j < n; j++) {
      innerArray[j] = 0;
    }

    // Add the inner array to the outer array.
    outerArray[i] = innerArray;
  }

  // Return the nested array.
  return outerArray;
};

Breakdown:

  • The generateNestedArray function takes a single parameter, n, which is the size of the nested array to generate.

  • The function first creates an empty outer array using the new Array(n) syntax. This array will store the inner arrays.

  • The function then iterates over the outer array using a for loop. For each iteration of the outer loop, the function creates an empty inner array using the new Array(n) syntax.

  • The function then iterates over the inner array using a nested for loop. For each iteration of the inner loop, the function sets the current element of the inner array to 0.

  • After the inner loop has completed, the function adds the inner array to the outer array.

  • After the outer loop has completed, the function returns the nested array.

Applications:

  • Nested arrays can be used to represent data in a hierarchical structure. For example, a nested array could be used to represent a file system, where each inner array represents a directory and the elements of the inner array represent the files in that directory.

  • Nested arrays can also be used to represent matrices. For example, a nested array could be used to represent a 2D array, where each inner array represents a row of the matrix.

  • Nested arrays can be used in a variety of other applications, such as:

    • Representing graphs

    • Creating data structures such as trees and queues

    • Solving optimization problems

    • Performing data analysis


Array Upper Bound

Problem Statement:

Given an array of integers, find the largest integer in the array.

Solution:

We can iterate through the array and keep track of the largest integer encountered so far. Here's how we can do it in JavaScript:

const findMax = (arr) => {
  let max = arr[0]; // Initialize max to the first element

  for (let i = 1; i < arr.length; i++) { // Iterate through the array starting from index 1
    if (arr[i] > max) { // Check if the current element is greater than max
      max = arr[i]; // If so, update max
    }
  }

  return max; // Return the final max value
};

Simplified Explanation:

  • We loop through the array, starting from the second element (index 1).

  • For each element, we check if it is greater than the current max.

  • If it is greater, we update max to be the new element.

  • At the end of the loop, max will contain the largest integer in the array.

Real-World Applications:

  • Finding the maximum score in a list of test results.

  • Identifying the highest-priced item in a shopping list.

  • Determining the tallest building in a city.


Deep Object Filter

Problem: Deep Object Filter

Description: The problem requires you to filter objects in a nested object structure based on a given condition. In other words, you need to find all objects that satisfy a specific criteria.

Solution: The most performant and elegant solution for this problem is to use a recursive function:

const deepObjectFilter = (obj, condition) => {
  const result = {};
  for (const key in obj) {
    if (condition(obj[key])) {
      result[key] = obj[key];
    } else if (typeof obj[key] === "object") {
      result[key] = deepObjectFilter(obj[key], condition);
    }
  }
  return result;
};

Breakdown:

  • The deepObjectFilter function takes two parameters:

    • obj: The object you want to filter

    • condition: A function that checks whether an object satisfies the filter criteria. It takes a single parameter, the value of the object.

  • The function iterates over all properties (key) of the object.

  • For each property, it checks whether the value satisfies the condition using condition(obj[key]). If it does, the property and its value are added to the result object.

  • If the value is another object, the function recursively calls deepObjectFilter on that object, passing the same condition function. This allows the filtering to continue through nested objects.

  • The function returns the result object, which contains only the properties that satisfy the filter condition.

Example:

Let's say we have the following nested object:

const obj = {
  name: "John Doe",
  age: 30,
  address: {
    street: "Main Street",
    city: "New York"
  },
  hobbies: ["sports", "reading", "music"]
}

And we want to filter out all properties with string values. We can use the following condition function:

const condition = (value) => typeof value === "string";

Using the deepObjectFilter function, we can get the filtered object:

const result = deepObjectFilter(obj, condition);

The result will contain the following properties:

{
  name: "John Doe",
  age: 30,
  address: {
    street: "Main Street",
    city: "New York"
  }
}

Notice that the hobbies property is not included in the result object because its values are not strings.

Applications:

This technique can be used in various real-world applications:

  • Data Extraction: Extracting specific data from complex nested objects, such as extracting customer information from a JSON file.

  • Object Validation: Ensuring that objects meet certain criteria before processing them, such as validating that a form submission contains all the required fields.

  • Data Transformation: Modifying or filtering objects to prepare them for different purposes, such as converting an object to a different format.


Promise Time Limit

Problem: Given a promise, implement a function to limit its execution time. If the promise takes longer than the specified time, reject the promise with a timeout error.

Example:

const promise = new Promise((resolve, reject) =>
  setTimeout(() => resolve('Success!'), 1000)
);

limitTime(promise, 500).then(result => console.log(result)).catch(err => console.log(err));

Output:

Error: Timeout

Solution:

const limitTime = (promise, time) => {
  const timeout = new Promise((resolve, reject) => setTimeout(reject, time, 'Timeout'));
  return Promise.race([promise, timeout]);
};

Breakdown:

  • Promise.race(): Takes an array of promises and returns a promise that resolves or rejects as soon as the first promise resolves or rejects.

  • setTimeout(): Sets a timer that executes a callback after a specified number of milliseconds. In this case, it rejects the promise after time milliseconds.

  • The limitTime() function returns a new promise that runs the given promise in a race against the timeout promise. If the given promise resolves before the timeout, its result will be returned. Otherwise, the timeout promise will reject, and the limitTime() promise will reject with a timeout error.

Real-World Applications:

  • Limiting API requests to prevent server overload.

  • Ensuring database queries or network requests don't take too long to avoid freezing user interfaces.

  • Controlling the execution time of long-running tasks to avoid affecting other processes.