Lesson 5
Introduction to Efficient Queries Using TypeScript
Introduction to Efficient Queries Using TypeScript

Greetings, aspiring coders! Today, we're going to delve deep into the complexities of data structures, specifically how to handle queries efficiently using TypeScript. This is a common problem often encountered in numerous data science and algorithmic challenges. So let's gear up to unravel the mysteries of managing sorted sets and get our hands dirty with some interactive problem-solving!

Simulating Sorted Set Operations and Time Complexity

Before delving into the task, let's understand how we can achieve sorted set functionality in TypeScript. Although TypeScript, like JavaScript, does not have a built-in data structure specifically for sorted sets, we can use arrays and objects to simulate this behavior with additional type safety.

To maintain a sorted set in TypeScript:

We can store elements in an array and keep it sorted upon every insertion or deletion. This approach will involve operations like using a binary search to find the correct position for insertion or deletion:

  • Inserting an element directly at the correct position using binary search takes O(N) in the worst case.
  • Removing an element also involves finding its position using binary search, which takes O(N) in the worst case.
  • Finding the smallest element greater than or equal to a given value can be achieved through a binary search, making this lookup operation O(log N).

Understanding these operations can help us utilize arrays and objects efficiently while leveraging TypeScript's strong type system.

Task Statement

We are tasked with designing a TypeScript function named processQueries that can process a series of distinct requests or queries efficiently. The queries comprise a list of two integers — the type of operation and the operand.

There are three types of operations we'll handle:

  • Adding an integer to the set (operation type 0)
  • Removing an integer from the set (operation type 1). Whenever this operation is invoked, we can guarantee that the integer exists in the set.
  • Finding the smallest integer that is greater than or equal to a given value (operation type 2).

The function should return an array of numbers where each element is the current size of the set for operation types 0 or 1, and the smallest possible integer for operation type 2. If such an integer does not exist, the function should return -1.

Ensure each part of the function has correct type annotations for function parameters and return types in TypeScript.

Initializing Data Structures

To start, we'll initialize our set as an empty array. We'll also create an empty array labeled results to store the outputs for each request, with both declared using TypeScript's type declarations.

TypeScript
1function processQueries(queries: [number, number][]): number[] { 2 const sortedSet: number[] = []; 3 const results: number[] = [];
Processing Addition and Removal Queries

Next, we'll implement a for loop to traverse through all the queries. For an operation type of 0 or 1, we either add or remove the provided value from our set, maintaining ordered insertion and deletion using a binary search to find the appropriate index. We then append the size of the current set to results.

First, we'll implement a helper function for binary search with explicit type annotations to find the appropriate index where the element should be inserted or could be found for removal.

TypeScript
1function binarySearchIndex(arr: number[], value: number): number { 2 let left: number = 0; 3 let right: number = arr.length - 1; 4 5 while (left <= right) { 6 const mid: number = left + Math.floor((right - left) / 2); 7 if (arr[mid] < value) { 8 left = mid + 1; 9 } else if (arr[mid] > value) { 10 right = mid - 1; 11 } else { 12 return mid; // Value found 13 } 14 } 15 16 return left; // Appropriate insert position 17}

With the helper function, we can efficiently handle insertion and deletion operations with type safety:

TypeScript
1function processQueries(queries: [number, number][]): number[] { 2 const sortedSet: number[] = []; 3 const results: number[] = []; 4 5 for (const query of queries) { 6 const operation: number = query[0]; 7 const value: number = query[1]; 8 9 if (operation === 0) { 10 const index: number = binarySearchIndex(sortedSet, value); 11 sortedSet.splice(index, 0, value); 12 results.push(sortedSet.length); 13 } else if (operation === 1) { 14 const index: number = binarySearchIndex(sortedSet, value); 15 if (index !== sortedSet.length && sortedSet[index] === value) { 16 sortedSet.splice(index, 1); 17 } 18 results.push(sortedSet.length); 19 } 20 } 21 22 return results; 23}
Handling Minimum Bound Queries

Lastly, when the operation type is 2, we need to find the minimum bound — the smallest value greater than or equal to our provided value in the set. We perform this using a binary search algorithm with explicit type annotations. If such a value does not exist, we append -1 to results.

TypeScript
1function processQueries(queries: [number, number][]): number[] { 2 const sortedSet: number[] = []; 3 const results: number[] = []; 4 5 for (const query of queries) { 6 const operation: number = query[0]; 7 const value: number = query[1]; 8 9 if (operation === 0) { 10 const index: number = binarySearchIndex(sortedSet, value); 11 sortedSet.splice(index, 0, value); 12 results.push(sortedSet.length); 13 } else if (operation === 1) { 14 const index: number = binarySearchIndex(sortedSet, value); 15 if (index !== sortedSet.length && sortedSet[index] === value) { 16 sortedSet.splice(index, 1); 17 } 18 results.push(sortedSet.length); 19 } else if (operation === 2) { 20 let left: number = 0; 21 let right: number = sortedSet.length - 1; 22 let result: number = -1; 23 24 while (left <= right) { 25 const mid: number = Math.floor((left + right) / 2); 26 if (sortedSet[mid] >= value) { 27 result = sortedSet[mid]; 28 right = mid - 1; 29 } else { 30 left = mid + 1; 31 } 32 } 33 34 results.push(result); 35 } 36 } 37 return results; 38} 39 40// Example usage 41const queries: [number, number][] = [ 42 [0, 10], 43 [2, 10], 44 [0, 20], 45 [1, 10], 46 [2, 10] 47]; 48 49console.log(processQueries(queries)); // Output: [1, 10, 2, 1, 20]
Lesson Summary

Well done! You've successfully navigated the complexities of sorted set operations and developed an understanding of how to handle various types of queries efficiently using TypeScript. By incorporating type annotations, you've created safer and more reliable code that can help prevent errors and improve maintainability.

The next step in your learning journey involves tackling similar challenges on your own using the concepts that you've just learned. Be sure to review this lesson as needed, and always remember: practice and apply these concepts to harness the full power of TypeScript. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.