Lesson 4
String Manipulation with TypeScript: Finding Substring Occurrences
Introduction

Hello, and welcome to our analysis lesson. In this lesson, we will be tackling a common problem in the field of string manipulations with TypeScript. We will learn how to find all occurrences of a substring within a larger string. The techniques you will master today can be utilized in numerous situations, such as text processing and data analysis. Are you ready to get started? Let's jump right in!

Task Statement and Description

Here is this unit's task: We have two lists of strings, both of identical lengths — the first containing the "original" strings and the second containing the substrings. Our goal is to detect all occurrences of each substring within its corresponding original string and, finally, return a list that contains the starting indices of these occurrences. Remember, the index counting should start from 0.

Example

Let's consider the following lists:
Original List: { "HelloWorld", "LearningTypeScript", "GoForBroke", "BackToBasics" }
Substring List: { "loW", "ear", "o", "Ba" }.

The following are the expected outputs:
In "HelloWorld", "loW" starts at index 3.
In "LearningTypeScript", "ear" starts at index 1.
In "GoForBroke", "o" appears at indices 1, 3, and 7.
In "BackToBasics", "Ba" starts at indices 0 and 6.

Thus, when findSubString(["HelloWorld", "LearningTypeScript", "GoForBroke", "BackToBasics"], ["loW", "ear", "o", "Ba"]) is invoked, the function should return:

Plain text
1[ 2 "The substring 'loW' was found in the original string 'HelloWorld' at position(s) 3.", 3 "The substring 'ear' was found in the original string 'LearningTypeScript' at position(s) 1.", 4 "The substring 'o' was found in the original string 'GoForBroke' at position(s) 1, 3, 7.", 5 "The substring 'Ba' was found in the original string 'BackToBasics' at position(s) 0, 6." 6]

Although this task may seem fairly straightforward, it can prove challenging. However, don't worry! We will break it down step by step.

Step-by-Step Solution: Step 1, Creating the Output List

Initially, we need to create a space to store our results. Can you think of a TypeScript data type that would be ideal for this task? That's right! An array of strings, with explicit typing, would be perfect!

TypeScript
1function findSubString(origStrs: string[], substrs: string[]): string[] { 2 let result: string[] = []; 3}
Step 2: Pairing Strings and Locating First Occurrence

To pair original strings with their substrings, we use a simple for loop. In our case, both lists share the same length, so we can use their indices to pair them correctly. To find the first occurrence of each substring in the corresponding original string, we utilize the indexOf method:

TypeScript
1function findSubString(origStrs: string[], substrs: string[]): string[] { 2 let result: string[] = []; 3 //Step 2 4 for (let i = 0; i < origStrs.length; i++) { 5 let start_pos: number = origStrs[i].indexOf(substrs[i]); 6}
Step 3: Locating Subsequent Occurrences

The next step is to find the subsequent instances of the substring in the original.

To do this, we will use a while loop. But when should we stop looking for more occurrences? When our indexOf function returns -1, it indicates there are no more matches to be found.

In string.indexOf(substr, startPos), the startPos parameter specifies the position in the string at which to start the search. By updating startPos after each match, we ensure that the search continues from the correct position for subsequent occurrences.

Each time we locate a match, we record its starting index in the match_indices array, adjust the start_pos, and begin the search anew:

TypeScript
1function findSubString(origStrs: string[], substrs: string[]): string[] { 2 let result: string[] = []; 3 4 for (let i = 0; i < origStrs.length; i++) { 5 let start_pos: number = origStrs[i].indexOf(substrs[i]); 6 //Step 3 7 let match_indices: number[] = []; 8 while (start_pos !== -1) { 9 match_indices.push(start_pos); 10 start_pos = origStrs[i].indexOf(substrs[i], start_pos + substrs[i].length); 11 } 12}

In this step, the while loop iteratively locates all occurrences of the substring in the original string:

  • The loop continues as long as start_pos is not -1, indicating there are more matches to find.
  • The second parameter in indexOf specifies the position to start searching. This is updated with start_pos + substrs[i].length to ensure the next search begins after the current match, avoiding overlapping results.
  • Every time a match is found, its starting index is added to the `match_indices`` array, ensuring all match positions are recorded.
  • This step ensures the function can handle substrings appearing multiple times in a string, such as "o" in "GoForBroke", where indices 1, 3, and 7 are captured.
Step 4: Formatting and Storing the Results

Finally, we can format the result using template literals for improved readability and add it to the result list:

TypeScript
1function findSubString(origStrs: string[], substrs: string[]): string[] { 2 let result: string[] = []; 3 4 for (let i = 0; i < origStrs.length; i++) { 5 let start_pos: number = origStrs[i].indexOf(substrs[i]); 6 7 let match_indices: number[] = []; 8 while (start_pos !== -1) { 9 match_indices.push(start_pos); 10 start_pos = origStrs[i].indexOf(substrs[i], start_pos + substrs[i].length); 11 } 12 //Step 4 13 if (match_indices.length > 0) { 14 let positions: string = match_indices.join(", "); 15 let resultString: string = `The substring '${substrs[i]}' was found in the original string '${origStrs[i]}' at position(s) ${positions}.`; 16 result.push(resultString); 17 } 18 } 19 return result; 20}

This step is the last step to complete the function. In next section we will see this function in action.

Example Usage

After discussing all the steps, let's see the example usage:

TypeScript
1// Call the function 2let result: string[] = findSubString( 3 ["HelloWorld", "LearningTypeScript", "GoForBroke", "BackToBasics"], 4 ["loW", "ear", "o", "Ba"] 5); 6result.forEach(res => console.log(res)); 7// Output: 8// The substring 'loW' was found in the original string 'HelloWorld' at position(s) 3. 9// The substring 'ear' was found in the original string 'LearningTypeScript' at position(s) 1. 10// The substring 'o' was found in the original string 'GoForBroke' at position(s) 1, 3, 7. 11// The substring 'Ba' was found in the original string 'BackToBasics' at position(s) 0, 6.

It is essential to consider edge cases to ensure the robustness of the function. In the current solution, if no matches are found for a given substring, the match_indices array will remain empty. The check if (match_indices.length > 0) prevents an empty result from being included in the final output. If the array is empty, the function skips formatting and does not push anything to the result array.

Additionally, if the substring is an empty string (which can technically be found at every position in the original string), this would result in a continuous loop. To prevent this, the solution should include a guard clause to handle such cases, like: if (substrs[i] === "") continue; This simple check can prevent unnecessary processing and ensure that only valid substrings are considered.

Lesson Summary

Well done! You've mastered a central operation in string manipulations using TypeScript — finding all occurrences of a substring in another string. With TypeScript, you gain the benefit of type safety, which helps prevent errors and makes your code easier to understand and maintain. Keep in mind that this algorithm has numerous applications in real-world scenarios. Now that we have intricately dissected the problem and provided a detailed solution, I encourage you to practice more. Future exercises will help you hone your skills further. Keep on coding and exploring!

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