ACTIVITY 9:  DATA STRUCTURE IN TYPESCRIPT | Aligan, Rhed N.

ACTIVITY 9: DATA STRUCTURE IN TYPESCRIPT | Aligan, Rhed N.

  1. Research and Study Data Structures in TypeScript:

  • Understand the commonly used data structures in TypeScript and how they are implemented.

  • Focus on how TypeScript’s strong typing system enhances the use of data structures.

  1. Explain Each Data Structure in TypeScript:

    For each data structure, provide the following details:

  • Definition: A brief explanation of the data structure.

    • Data structures in typescript is very essential especially in holding of bunch of data. From the word data structure, commonly storing and holding bunch of data that need to be structured so it can be organized, and easily read and avoid code redundancy that may cause of slow latency in web development.
  • Key Features: The important characteristics and behaviors of the data structure.

    • The important characteristics and behaviors of the data structure especially in web development is make our code or data be use organized, stored, and manipulated with a different approaches or method. It’s very useful not only to avoid code redundancy is because when we are accessing the specific value in our variable it’s easy to access for retrieve, display, and manipulating by using its index.
  • Use Cases: Where and why this data structure is typically used.

    • Most cases scenario use of data structure when you hold a bunch of data, and you don’t want to slow the run time especially in web development so, data structure is always best practice it. For instances, if you are storing collection of elements example of list of shoes, you can use array and access by its index starting in 0. It’s efficient when the collection of data was given in array.

    • Example of data structure using Array

        Shoes: Strings [] = ["Nike","Adidas","Puma","Vans"];
      
    • In angular, you can use this you can use a *ngFor in html to access all elements and display, or *ngIf you want based on the input index number based. Read more for more detailed example.

  • Time Complexity: Analyze the performance of each data structure (Big-O notation) for common operations like insert, delete, and search.

    • In Array, insert data mostly inserting at the beginning or in the middle of the data collection, in delete operations, delete in beginning or in the middle, and search is in linear search based on their indices.

    • In Tuple, insert data in the middle, otherwise, it will be added the data insert or in short append. Delete operations deleting in the middle or in the beginning. and search is based on the linear search thorugh indices.

    • In ArrayList(Dynamic Arrays), Inserting data can be amortized or to expand the array but in the end or insert in the specific position, delete can remove in specific position, and search is also a linear search through indices.

    • In Stack, in inserting data, need to push() method which adds element to the end of the stack in O(1) time. O(1) means constantly amount of time regardless of how many you input data. Delete or pop() method use to remove last element to the stack in the O(1) time. and search using indexOf() which checks each element sequentially that lead to O(n) time complexity.

    • In Queue, using the Enqueue (called insert in Queue) push() method, add element of the queue, in O(1) time. Dequeue (called delete in Queue) shift() method removes the first element from the queue in O(1) time. and also, indexOf() use to check each element sequentially, leading of O(n) time complexity.

    • In LinkedList, Insert using insertAtHead() method inserts a new node at the beginning of the list in O(1) time. Delete (by value) The Deletenode() method can delete a node if its position is known (O(1) for the head) or requires O(n) to find the node first. Search The search() method iterates through the list, resulting in O(n) time complexity.

    • In HashMap(or Object/Map), The set() method adds a new key-value pair to the hashmap in O(1) time on average. In The get() method retrieves the value associated with a given key in O(1) time on average. Also, The delete() method removes a key-value pair from the hashmap in O(1) time on average. While the average time complexities for insert, delete, and search operations are O(1), it's essential to note that in the worst case such as many collisions of data, these operations can degrade to O(n). However, good hash functions and resizing strategies can mitigate this.

    • In Set, in inserting, the add() method adds a new element to the set in O(1) time on average. Delete The delete() method removes an element from the set in O(1) time on average. and for search, the has() method checks for the presence of an element in the set in O(1) time on average.

    • In Tree, to Insert, The insert() method adds a new node to the tree. In balanced trees, it can be done in O(log n) time, but in an unbalanced tree, it could degrade to O(n) if the tree becomes a linear chain. In delete operations, The delete() method removes a node from the tree. Similar to insertion, deletion in balanced trees takes O(log n), while it may take O(n) in unbalanced trees. and search is using search() method looks for a value in the tree. In balanced trees, this is efficient at O(log n), while in unbalanced trees, it can take O(n). In analyzing performance, Balanced trees maintain a specific structure that ensures the height remains logarithmic relative to the number of nodes, ensuring efficient operations. Unbalanced trees can degrade to linear structures, leading to poor performance.

  • Example Code in TypeScript: Provide a TypeScript code snippet demonstrating how to use each data structure.

  • Array

import { Component } from '@angular/core';

@Component({
  selector: 'app-array-example',
  template: `<p>{{ numbers }}</p>`
})
export class ArrayExampleComponent {
  numbers: number[] = [1, 2, 3, 4, 5];

  constructor() {
    this.numbers.push(6); // Add an element to the array
  }
}
  • Arrays in TypeScript are used to store multiple values in a single variable. In this example, we define an array called numbers that initially contains five integers. The push method is used to add another element, 6, to the end of the array. This allows dynamic manipulation of the list of numbers.

  • Tuple

import { Component } from '@angular/core';

@Component({
  selector: 'app-tuple-example',
  template: `<p>{{ person }}</p>`
})
export class TupleExampleComponent {
  person: [number, string] = [1, "John"];
}
  • Tuples are a special type of array where each element can be of a different type. In this case, we create a tuple called person that contains a number (an ID) and a string (a name). This is useful for representing fixed collections of related values, maintaining their types within the same variable.

  • ArrayList (Dynamic Arrays)

import { Component } from '@angular/core';

@Component({
  selector: 'app-arraylist-example',
  template: `<p>{{ dynamicArray }}</p>`
})
export class ArrayListExampleComponent {
  dynamicArray: number[] = [];

  constructor() {
    this.dynamicArray.push(1); // Adding elements dynamically
    this.dynamicArray.push(2);
  }
}
  • Dynamic arrays, or ArrayLists, allow for the addition and removal of elements at runtime. In this example, we initialize an empty array dynamicArray and use the push method to add two elements, 1 and 2. This structure is useful for cases where the number of elements is not known in advance.

  • Stack

import { Component } from '@angular/core';

@Component({
  selector: 'app-stack-example',
  template: `<p>{{ poppedItem }}</p>`
})
export class StackExampleComponent {
  private items: number[] = [];
  poppedItem: number | undefined;

  push(item: number) {
    this.items.push(item); // Add an item to the stack
  }

  pop() {
    return this.items.pop(); // Remove and return the top item
  }

  constructor() {
    this.push(1); // Pushing items onto the stack
    this.push(2);
    this.poppedItem = this.pop(); // Pops the last item added
  }
}

Stacks are based on the LIFO (Last In First Out) principle, meaning the last item added is the first one to be removed. In this example, we define a stack using an array and provide push and pop methods. We add two numbers and then remove the last one added. This structure is useful for scenarios like managing function calls or undo operations.

  • Queue
import { Component } from '@angular/core';

@Component({
  selector: 'app-queue-example',
  template: `<p>{{ dequeuedItem }}</p>`
})
export class QueueExampleComponent {
  private items: number[] = [];
  dequeuedItem: number | undefined;

  enqueue(item: number) {
    this.items.push(item); // Add an item to the queue
  }

  dequeue() {
    return this.items.shift(); // Remove and return the first item
  }

  constructor() {
    this.enqueue(1); // Adding items to the queue
    this.enqueue(2);
    this.dequeuedItem = this.dequeue(); // Dequeues the first item added
  }
}

Queues operate on a FIFO (First In First Out) basis, where the first item added is the first one to be removed. In this example, we implement a simple queue using an array with enqueue and dequeue methods. This is often used in scenarios such as scheduling tasks or managing requests in applications.

  • LinkedList
import { Component } from '@angular/core';

class Node {
  value: number;
  next: Node | null = null;

  constructor(value: number) {
    this.value = value; // Initialize node with a value
  }
}

@Component({
  selector: 'app-linkedlist-example',
  template: `<p>{{ head?.value }}</p>`
})
export class LinkedListExampleComponent {
  head: Node | null = null;

  add(value: number) {
    const newNode = new Node(value); // Create a new node
    if (!this.head) {
      this.head = newNode; // If list is empty, set head
    } else {
      let current = this.head;
      while (current.next) {
        current = current.next; // Traverse to the end of the list
      }
      current.next = newNode; // Add new node at the end
    }
  }

  constructor() {
    this.add(1); // Adding nodes to the linked list
    this.add(2);
  }
}

Linked lists consist of nodes that contain data and a reference to the next node. This structure allows for dynamic memory allocation and efficient insertions/deletions. In this example, we create a linked list by defining a Node class and a component that manages the list. Nodes are added to the end of the list, demonstrating how you can build a dynamic data structure.

  • HashMap (Object/Map)
import { Component } from '@angular/core';

@Component({
  selector: 'app-hashmap-example',
  template: `<p>{{ map.get('one') }}</p>`
})
export class HashMapExampleComponent {
  map: Map<string, number> = new Map(); // Initializing a Map

  constructor() {
    this.map.set("one", 1); // Adding key-value pairs
    this.map.set("two", 2);
  }
}

HashMaps, or Maps, allow storage of key-value pairs for quick retrieval based on keys. In this example, we use the Map class to create a hashmap that associates strings (keys) with numbers (values). This structure is very useful for situations where you need to access elements by unique identifiers efficiently.

  • Set
import { Component } from '@angular/core';

@Component({
  selector: 'app-set-example',
  template: `<p>{{ set.size }}</p>`
})
export class SetExampleComponent {
  set: Set<number> = new Set(); // Initializing a Set

  constructor() {
    this.set.add(1); // Adding elements to the set
    this.set.add(2);
    this.set.add(1); // Duplicates are ignored
  }
}

Sets are collections of unique values. They automatically ignore duplicate entries, ensuring all values are distinct. In this example, we initialize a Set and add numbers to it. The size property shows how many unique elements are in the set, demonstrating its usefulness for tracking unique items.

  • Tree
import { Component } from '@angular/core';

class TreeNode {
  value: number;
  children: TreeNode[] = []; // List of child nodes

  constructor(value: number) {
    this.value = value; // Initialize node with a value
  }

  addChild(node: TreeNode) {
    this.children.push(node); // Add a child node
  }
}

@Component({
  selector: 'app-tree-example',
  template: `<p>{{ root.value }}</p>`
})
export class TreeExampleComponent {
  root = new TreeNode(1); // Creating the root of the tree

  constructor() {
    const child = new TreeNode(2); // Creating a child node
    this.root.addChild(child); // Adding child to the root
  }
}

Trees are hierarchical structures composed of nodes, with each node potentially having multiple children. This structure allows for efficient data representation and retrieval. In this example, we define a Treenode class and build a simple tree with a root and a child. Trees are widely used in scenarios like organizing hierarchical data example are file systems.

  1. Data Structures to Cover:

Arrays:

Arrays in TypeScript are a fundamental data structure that allows you to store a collection of items. They can hold elements of any type, including primitives (like numbers and strings) and objects. In Angular, arrays are commonly used for handling lists of data, managing state, and facilitating user interactions.

  • Provide examples of operations like adding, removing, and accessing elements.
import { Component } from '@angular/core';

@Component({
  selector: 'app-fruit-list',
  template: `appfruitlist.component.html`
})
export class FruitListComponent {
  fruits: string[] = ['apple', 'banana', 'cherry'];
  indexToAccess: number | undefined; // To hold the user input index
  accessedFruit: string | undefined; // To hold the accessed fruit

  addFruit(fruit: string) {
    this.fruits.push(fruit);
  }

  removeFruit() {
    this.fruits.pop();
  }

  accessFruit() {
    // Check if the index is valid
    if (this.indexToAccess !== undefined && this.indexToAccess >= 0 && this.indexToAccess < this.fruits.length) {
      this.accessedFruit = this.fruits[this.indexToAccess];
    } else {
      this.accessedFruit = undefined; // Reset if index is invalid
    }
  }
}
    <h2>Fruit List</h2>
    <ul>
      <li *ngFor="let fruit of fruits">{{ fruit }}</li>
    </ul>
    <button (click)="addFruit('orange')">Add Orange</button>
    <button (click)="removeFruit()">Remove Last Fruit</button>
    <div>
      <input type="number" [(ngModel)]="indexToAccess" placeholder="Enter index to access" />
      <button (click)="accessFruit()">Access Fruit</button>
      <p *ngIf="accessedFruit !== undefined">Accessed Fruit: {{ accessedFruit }}</p>
    </div>

Assumption this is example of creating an array in typescript in angular

Using push method

addFruit(fruit: string) {
  this.fruits.push(fruit);
}

This method takes a string parameter fruit and adds it to the fruits array using the push() method. It dynamically updates the list of fruits displayed in the template.

Remove or delete the last fruit,

removeFruit() {
  this.fruits.pop();
}

This method removes the last fruit from the fruits array using the pop() method. This operation also updates the displayed list of fruits.

Accessing Elements,

accessFruit() {
  // Check if the index is valid
  if (this.indexToAccess !== undefined && this.indexToAccess >= 0 && this.indexToAccess < this.fruits.length) {
    this.accessedFruit = this.fruits[this.indexToAccess];
  } else {
    this.accessedFruit = undefined; // Clear when the indices are invalid input
  }
}

This method checks whether the user-provided indextoAccess is valid. It ensures indextoAccess is not undefined, is greater than or equal to 0, and is less than the length of the fruits array. If valid, it assigns the corresponding fruit from the fruits array to the accessedfruit property. If invalid, it resets accessedfruit to undefined, which means no fruit is accessed.

Tuple:

Tuples are a special type of array that allows you to express an array with a fixed number of elements whose types are known. Unlike regular arrays, where elements can be of any type and can change in number, tuples have a defined structure where each position in the tuple has a specific type.

  • Provide an example of defining and accessing tuple elements.
import { Component } from '@angular/core';

@Component({
  selector: 'app-user-info',
  templateUrl: './user-info.component.html',
  styleUrls: ['./user-info.component.css']
})
export class UserInfoComponent {
  // Defining a tuple with a string and a number
  user: [string, number] = ['Alice', 30];

  // Accessing tuple elements
  name: string = this.user[0]; // Accessing the name
  age: number = this.user[1];   // Accessing the age

  // Method to return user info
  getUserInfo(): string {
    return `Name: ${this.name}, Age: ${this.age}`;
  }
}

In code snippets, this is how defining and accessing the elements using tuples.

// Defining a tuple with a string and a number
  user: [string, number] = ['Alice', 30];

This line defines a tuple named user in TypeScript, which is a fixed-size array that contains two elements: a string (the name "Alice") and a number (the age 30). The tuple ensures that the first element is always a string and the second element is always a number.

 // Accessing tuple elements
  name: string = this.user[0]; // Accessing the name
  age: number = this.user[1];   // Accessing the age

In this accessing, using name: string = this.user[0]; retrieves the first element of the user tuple, which is the name ("Alice"), and assigns it to the variable name, specifying that it should be of type string. Also, age: number = this.user[1]; retrieves the second element of the user tuple, which is the age (30), and assigns it to the variable age, specifying that it should be of type number.

// Method to return user info
  getUserInfo(): string {
    return Name: ${this.name}, Age: ${this.age};
  }

getUserInfo(): string specifies that this method returns a value of type string. Inside the method, the return statement constructs and returns a formatted string that includes the user's name and age, using template literals. This interpolates the name and age variables, resulting in a string like "Name: Alice, Age: 30."

ArrayList (Dynamic Arrays):

Dynamic arrays can be created using the built-in Array type. Unlike static arrays, which have a fixed size, dynamic arrays can grow or shrink as needed. You can manage dynamic arrays by adding, removing, or modifying elements using various array methods.

  • Provide an example of resizing or manipulating dynamic arrays.
import { Component } from '@angular/core';

@Component({
  selector: 'app-dynamic-array',
  templateUrl: './dynamic-array.component.html',
  styleUrls: ['./dynamic-array.component.css']
})
export class DynamicArrayComponent {
  // Initialize a dynamic array
  numbers: number[] = [];

  // Method to add a number to the array
  addNumber(num: number): void {
    this.numbers.push(num); // Adds the number to the end of the array
  }

  // Method to remove the last number from the array
  removeLastNumber(): void {
    this.numbers.pop(); // Removes the last element
  }

  // Method to clear the entire array
  clearArray(): void {
    this.numbers = []; // Resets the array to empty
  }
}

This is the typescript in angular

<div>
  <h2>Dynamic Array Management</h2>
  <input #numberInput type="number" placeholder="Enter a number" />
  <button (click)="addNumber(numberInput.valueAsNumber)">Add Number</button>
  <button (click)="removeLastNumber()">Remove Last Number</button>
  <button (click)="clearArray()">Clear Array</button>

  <h3>Current Numbers:</h3>
  <ul>
    <li *ngFor="let num of numbers">{{ num }}</li>
  </ul>
</div>

This is the code snippets in html connected to our typescript. In this explanation

  • A dynamic array number is initialized to hold numeric values.

  • The addNumber method adds a number to the array using push().

  • The removeLastNumber method removes the last element using pop().

  • The clearArray method resets the array to empty.

  • The template provides an interface to add, remove, and clear numbers from the dynamic array.

Stack:

  • Define how to implement a stack in TypeScript using an array or a class.

  • Explain the Last In First Out (LIFO) principle and common operations (push, pop, peek).

Queue:

A stack is a data structure that follows the Last In First Out (LIFO) principle, meaning that the last element added to the stack is the first one to be removed. You can think of it like a stack of plates, tama? the last plate placed on top is the first one you take off.

  • Explain the First in First Out (FIFO) principle and common operations (enqueue, dequeue)

      import { Component } from '@angular/core';
    
      class Stack<T> {
        private items: T[] = []; // Array to hold stack elements
    
        // Push: Add an element to the top of the stack
        push(item: T): void {
          this.items.push(item);
        }
    
        // Pop: Remove the top element from the stack and return it
        pop(): T | undefined {
          return this.items.pop();
        }
    
        // Peek: Return the top element without removing it
        peek(): T | undefined {
          return this.items[this.items.length - 1];
        }
    
        // Check if the stack is empty
        isEmpty(): boolean {
          return this.items.length === 0;
        }
    
        // Get the size of the stack
        size(): number {
          return this.items.length;
        }
    
        // Get all items in the stack (for display purposes)
        getItems(): T[] {
          return this.items;
        }
      }
    
      @Component({
        selector: 'app-stack-example',
        templateUrl: './stack-example.component.html',
        styleUrls: ['./stack-example.component.css']
      })
      export class StackExampleComponent {
        stack: Stack<number> = new Stack<number>(); // Create a new stack instance
        inputNumber: number | null = null; // Variable to hold user input
        poppedValue: number | undefined; // To store popped value
        peekedValue: number | undefined; // To store peeked value
    
        // Method to push a number onto the stack
        addNumber(): void {
          if (this.inputNumber !== null) {
            this.stack.push(this.inputNumber);
            this.inputNumber = null; // Clear input after pushing
          }
        }
    
        // Method to pop a number from the stack
        removeNumber(): void {
          this.poppedValue = this.stack.pop();
        }
    
        // Method to peek at the top number of the stack
        checkTop(): void {
          this.peekedValue = this.stack.peek();
        }
    
        // Get current stack items for display
        getCurrentStack(): number[] {
          return this.stack.getItems();
        }
      }
    
<div>
  <h2>Stack Example</h2>
  <input type="number" [(ngModel)]="inputNumber" placeholder="Enter a number" />
  <button (click)="addNumber()">Push to Stack</button>
  <button (click)="removeNumber()">Pop from Stack</button>
  <button (click)="checkTop()">Peek at Top</button>

  <h3>Current Stack:</h3>
  <ul>
    <li *ngFor="let num of getCurrentStack()">{{ num }}</li>
  </ul>

  <h3>Peeked Value: {{ peekedValue }}</h3>
  <h3>Popped Value: {{ poppedValue }}</h3>
</div>

In this explanation of each, Using push(item: T): void, This method adds a new element (item) to the top of the stack.

  • Parameters:

    • item: The element to be added, which is of type T (generic type).
  • Returns: Nothing (void).

  1. pop(): T | undefined

    • Description: This method removes and returns the top element of the stack.

    • Returns: The removed element of type T or undefined if the stack is empty.

  2. peek(): T | undefined

    • Description: This method returns the top element of the stack without removing it.

    • Returns: The top element of type T or undefined if the stack is empty.

  3. isEmpty(): boolean

    • Description: This method checks whether the stack is empty.

    • Returns: true if the stack has no elements; otherwise, false.

  4. size(): number

    • Description: This method returns the current number of elements in the stack.

    • Returns: The size of the stack as a number.

  5. getItems(): T[]

    • Description: This method returns all elements currently in the stack.

    • Returns: An array of elements of type T.

StackExampleComponent Methods

  1. addNumber(): void

    • Description: This method takes the user-input number and pushes it onto the stack.

    • Functionality:

      • Checks if inputNumber is not null.

      • Calls the push method of the Stack instance to add the number.

      • Resets inputNumber to null after pushing to clear the input field.

    • Returns: Nothing (void).

  2. removeNumber(): void

    • Description: This method removes the top element from the stack and stores the removed value.

    • Functionality:

      • Calls the pop method of the Stack instance and assigns the returned value to poppedValue.
    • Returns: Nothing (void).

  3. checkTop(): void

    • Description: This method checks the top element of the stack without removing it and stores the peeked value.

    • Functionality:

      • Calls the peek method of the Stack instance and assigns the returned value to peekedValue.
    • Returns: Nothing (void).

  4. getCurrentStack(): number[]

    • Description: This method returns the current elements in the stack for display purposes.

    • Returns: An array of numbers that are currently in the stack, retrieved using the getItems method of the Stack instance.

LinkedList:

Creating a linked list in TypeScript involves defining a Node class to represent each node and a LinkedList class to manage the nodes. Here's how you can create both singly and doubly linked lists, along with examples of adding, removing, and traversing nodes.

  • Provide an example of adding, removing, and traversing nodes.

Linked List Implementation

class Node<T> {
  value: T;
  next: Node<T> | null;

  constructor(value: T) {
    this.value = value;
    this.next = null;
  }
}

class LinkedList<T> {
  head: Node<T> | null;

  constructor() {
    this.head = null;
  }

  // Add a node to the end of the list
  add(value: T): void {
    const newNode = new Node(value);
    if (!this.head) {
      this.head = newNode;
      return;
    }
    let current = this.head;
    while (current.next) {
      current = current.next;
    }
    current.next = newNode;
  }

  // Remove a node by value
  remove(value: T): void {
    if (!this.head) return;

    // If the head needs to be removed
    if (this.head.value === value) {
      this.head = this.head.next;
      return;
    }

    let current = this.head;
    while (current.next) {
      if (current.next.value === value) {
        current.next = current.next.next;
        return;
      }
      current = current.next;
    }
  }

  // Traverse the list and print values
  traverse(): void {
    let current = this.head;
    while (current) {
      console.log(current.value);
      current = current.next;
    }
  }
}

// Example usage
const linkedList = new LinkedList<number>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);

console.log('Linked List after adding nodes:');
linkedList.traverse();

linkedList.remove(2);
console.log('Linked List after removing node with value 2:');
linkedList.traverse();

Explanation

  • Node Class: Represents a single node in the linked list, containing a value and a pointer to the next node.

  • LinkedList Class: Manages the linked list with methods to add, remove, and traverse nodes.

    • add(value: T): Adds a new node with the specified value to the end of the list.

    • remove(value: T): Removes the first node that matches the specified value.

    • traverse(): Logs the values of all nodes in the list.

Integration into an Angular Component

To integrate this into an Angular component, you could create a component and include the linked list functionality in it. Here’s a basic setup:

import { Component } from '@angular/core';

@Component({
  selector: 'app-linked-list',
  template: `<h1>Linked List Example</h1>`,
})
export class LinkedListComponent {
  // Include the LinkedList implementation here

  constructor() {
    const linkedList = new LinkedList<number>();
    linkedList.add(1);
    linkedList.add(2);
    linkedList.add(3);

    console.log('Linked List after adding nodes:');
    linkedList.traverse();

    linkedList.remove(2);
    console.log('Linked List after removing node with value 2:');
    linkedList.traverse();
  }
}

Usage

  • You can use this LinkedListComponent in your Angular application. When the component is initialized, it will demonstrate adding and removing nodes, as well as traversing the list. Adjust the LinkedList class as needed to suit your requirements!

HashMap (or Object/Map):

In TypeScript, you can use either Map or plain objects to create a key-value pair data structure. Below, I'll demonstrate both approaches along with examples for inserting, deleting, and searching for values by keys.

  • Provide examples of inserting, deleting, and searching for values by keys.

  • Using Map

    The Map object is a collection of key-value pairs where keys can be of any type, including objects.

    Creating and Using a Map

    
      // Creating a Map
      const mapExample = new Map<string, number>();
    
      // Inserting key-value pairs
      mapExample.set('one', 1);
      mapExample.set('two', 2);
      mapExample.set('three', 3);
    
      console.log('Map after inserting values:');
      console.log(mapExample);
    
      // Searching for a value by key
      const valueTwo = mapExample.get('two');
      console.log('Value for key "two":', valueTwo);
    
      // Deleting a key-value pair
      mapExample.delete('two');
      console.log('Map after deleting key "two":');
      console.log(mapExample);
    
      // Checking if a key exists
      const hasTwo = mapExample.has('two');
      console.log('Does key "two" exist?', hasTwo);
    

    OUTPUT

  •   Map after inserting values:
      Map(3) { 'one' => 1, 'two' => 2, 'three' => 3 }
      Value for key "two": 2
      Map after deleting key "two":
      Map(2) { 'one' => 1, 'three' => 3 }
      Does key "two" exist? false
    

Using Plain Object

You can also use a plain object to create a key-value structure, where keys are strings (or symbols), and values can be any type.

Creating and Using an Object

// Creating an object
const objExample: { [key: string]: number } = {};

// Inserting key-value pairs
objExample['one'] = 1;
objExample['two'] = 2;
objExample['three'] = 3;

console.log('Object after inserting values:');
console.log(objExample);

// Searching for a value by key
const valueTwoObj = objExample['two'];
console.log('Value for key "two":', valueTwoObj);

// Deleting a key-value pair
delete objExample['two'];
console.log('Object after deleting key "two":');
console.log(objExample);

// Checking if a key exists
const hasTwoObj = 'two' in objExample;
console.log('Does key "two" exist?', hasTwoObj);

Output

Object after inserting values:
{ one: 1, two: 2, three: 3 }
Value for key "two": 2
Object after deleting key "two":
{ one: 1, three: 3 }
Does key "two" exist? false

Summary

  • Map:

    • Allows keys of any type.

    • Maintains insertion order.

    • Provides built-in methods like set, get, delete, and has.

  • Object:

    • Only allows string or symbol keys.

    • Keys are unordered.

    • Accessing keys is done via bracket notation or dot notation.

You can choose either method based on your specific use case and requirements. For complex key types or if you need to maintain order, Map is usually the better option. For simpler use cases, a plain object may suffice.

SET:

To create a Set in Angular (or JavaScript) that stores unique elements, you can use the Set constructor. By instantiating a Set, you establish a collection that only allows distinct values, meaning any duplicates will not be added. For example, you can create an empty Set with const mySet = new Set<number>(), where number indicates the type of elements it will hold. Additionally, you can initialize a Set with existing values by passing an array to the constructor, such as const mySetWithValues = new Set<number>([1, 2, 3]), which adds the numbers 1, 2, and 3 to the set while ensuring uniqueness.

  • Show how to add, remove, and check for elements in a set

1. Adding Elements in Angular

To add elements to a Set in Angular, you use the add method. This method allows you to insert a value into the Set, and it ensures that each value is unique duplicates will not be added.

import { Component } from '@angular/core';

@Component({
  selector: 'app-set-example',
  template: `<button (click)="addElement(1)">Add 1</button>
             <button (click)="addElement(2)">Add 2</button>
             <p>Current Set: {{ currentSet }}</p>`,
})
export class SetExampleComponent {
  mySet: Set<number> = new Set<number>();
  currentSet: string = '';

  addElement(value: number): void {
    this.mySet.add(value);
    this.updateCurrentSet();
  }

  private updateCurrentSet(): void {
    this.currentSet = Array.from(this.mySet).join(', ');
  }
}

In this Angular component, mySet is initialized as an empty Set that stores numbers. The addElement method is called when the "Add" buttons are clicked, adding the specified number to mySet using the add method. The updateCurrentSet method is called to update the display of the current set, converting the set to an array and joining the elements into a string format.

  1. Removing Elements in Angular

    To remove elements from a Set, you can use the delete method. This method takes a value as an argument and removes it from the set if it exists.

<button (click)="removeElement(1)">Remove 1</button>
<button (click)="removeElement(2)">Remove 2</button>

removeElement(value: number): void {
  this.mySet.delete(value);
  this.updateCurrentSet();
}

In this part of the Angular component, two buttons allow the user to remove values from mySet. The removeElement method uses the delete method to attempt to remove the specified number. After removing (or attempting to remove) the element, updateCurrentSet is called to refresh the displayed set content.

  1. Checking for Elements in Angular

    To check if an element exists in a Set, you use the has method. This method returns true if the value is found and false if it is not.

<button (click)="checkElement(1)">Check 1</button>
<button (click)="checkElement(2)">Check 2</button>

checkElement(value: number): void {
  const exists = this.mySet.has(value);
  alert(`Does ${value} exist in the set? ${exists}`);
}

Here, buttons trigger the checkElement method, which checks for the existence of specified numbers in mySet. The has method is used to determine whether the number is present, and an alert displays the result to the user. This allows users to quickly verify if specific values exist in the set.

import { Component } from '@angular/core';

@Component({
  selector: 'app-set-example',
  template: `
    <button (click)="addElement(1)">Add 1</button>
    <button (click)="addElement(2)">Add 2</button>
    <button (click)="removeElement(1)">Remove 1</button>
    <button (click)="removeElement(2)">Remove 2</button>
    <button (click)="checkElement(1)">Check 1</button>
    <button (click)="checkElement(2)">Check 2</button>
    <p>Current Set: {{ currentSet }}</p>
  `,
})
export class SetExampleComponent {
  mySet: Set<number> = new Set<number>();
  currentSet: string = '';

  addElement(value: number): void {
    this.mySet.add(value);
    this.updateCurrentSet();
  }

  removeElement(value: number): void {
    this.mySet.delete(value);
    this.updateCurrentSet();
  }

  checkElement(value: number): void {
    const exists = this.mySet.has(value);
    alert(`Does ${value} exist in the set? ${exists}`);
  }

  private updateCurrentSet(): void {
    this.currentSet = Array.from(this.mySet).join(', ');
  }
}
  • Adding Elements: Use the add method to insert unique values into the Set. Duplicates are ignored.

  • Removing Elements: The delete method removes a specific value from the Set, leaving it unchanged if the value is not found.

  • Checking for Elements: The has method checks for the presence of a value in the Set, returning a boolean indicating whether the value exists.

This example illustrates how to effectively manage unique elements in a Set within an Angular components.

Tree:

Binary trees and binary search trees (BST) can be implemented in TypeScript using classes to define the tree structure and its operations. A binary tree consists of nodes, where each node has a maximum of two children, typically referred to as the left and right children. A binary search tree is a special kind of binary tree where the left child contains values less than the parent node, and the right child contains values greater than the parent node.

Implementing a Binary Search Tree (BST)

Here’s how you can implement a binary search tree in TypeScript, along with examples of inserting nodes, traversing the tree, and searching for elements.

Provide an example showing how to insert nodes, traverse the tree, and search for elements.

1. Node Class

First, define a Node class to represent each node in the tree.

typescriptCopy codeclass Node {
  value: number;
  left: Node | null;
  right: Node | null;

  constructor(value: number) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

Explanation: The Node class has three properties: value (the value stored in the node), left (a reference to the left child node), and right (a reference to the right child node). The left and right properties are initialized to null by default.

2. Binary Search Tree Class

Next, create a BinarySearchTree class that manages the nodes and operations.

typescriptCopy codeclass BinarySearchTree {
  root: Node | null;

  constructor() {
    this.root = null;
  }

  // Insert a new value into the BST
  insert(value: number): void {
    const newNode = new Node(value);
    if (this.root === null) {
      this.root = newNode;
    } else {
      this.insertNode(this.root, newNode);
    }
  }

  private insertNode(node: Node, newNode: Node): void {
    if (newNode.value < node.value) {
      if (node.left === null) {
        node.left = newNode;
      } else {
        this.insertNode(node.left, newNode);
      }
    } else {
      if (node.right === null) {
        node.right = newNode;
      } else {
        this.insertNode(node.right, newNode);
      }
    }
  }
}

Explanation: The BinarySearchTree class has a root property to store the root node of the tree. The insert method is used to add a new value. If the root is null, it assigns the new node as the root; otherwise, it calls the insertNode method to find the correct position for the new node. The insertNode method recursively places the new node in the left or right subtree based on its value.

3. Traversing the Tree

You can traverse the tree in various ways, such as in-order, pre-order, or post-order. Here’s an example of in-order traversal, which visits the left child, the current node, and then the right child.

typescriptCopy codeinOrderTraversal(node: Node | null): number[] {
  const result: number[] = [];
  if (node !== null) {
    result.push(...this.inOrderTraversal(node.left));
    result.push(node.value);
    result.push(...this.inOrderTraversal(node.right));
  }
  return result;
}

Explanation: The inOrderTraversal method recursively visits the nodes. It checks if the current node is not null, then recursively traverses the left subtree, adds the current node's value to the result array, and finally traverses the right subtree. The method returns an array of values in sorted order.

4. Searching for Elements

To search for an element in the BST, use the following method:

typescriptCopy codesearch(value: number): boolean {
  return this.searchNode(this.root, value);
}

private searchNode(node: Node | null, value: number): boolean {
  if (node === null) {
    return false;  // Value not found
  }
  if (value === node.value) {
    return true;  // Value found
  } else if (value < node.value) {
    return this.searchNode(node.left, value);  // Search left subtree
  } else {
    return this.searchNode(node.right, value);  // Search right subtree
  }
}

Explanation: The search method initiates the search process by calling searchNode. The searchNode method checks if the current node is null. If it is, the value is not found, so it returns false. If the current node’s value matches the search value, it returns true. If the search value is less than the current node’s value, it recursively searches the left subtree; otherwise, it searches the right subtree.

Complete Example

Here’s a complete implementation of a binary search tree in TypeScript:

typescriptCopy codeclass Node {
  value: number;
  left: Node | null;
  right: Node | null;

  constructor(value: number) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

class BinarySearchTree {
  root: Node | null;

  constructor() {
    this.root = null;
  }

  insert(value: number): void {
    const newNode = new Node(value);
    if (this.root === null) {
      this.root = newNode;
    } else {
      this.insertNode(this.root, newNode);
    }
  }

  private insertNode(node: Node, newNode: Node): void {
    if (newNode.value < node.value) {
      if (node.left === null) {
        node.left = newNode;
      } else {
        this.insertNode(node.left, newNode);
      }
    } else {
      if (node.right === null) {
        node.right = newNode;
      } else {
        this.insertNode(node.right, newNode);
      }
    }
  }

  inOrderTraversal(node: Node | null): number[] {
    const result: number[] = [];
    if (node !== null) {
      result.push(...this.inOrderTraversal(node.left));
      result.push(node.value);
      result.push(...this.inOrderTraversal(node.right));
    }
    return result;
  }

  search(value: number): boolean {
    return this.searchNode(this.root, value);
  }

  private searchNode(node: Node | null, value: number): boolean {
    if (node === null) {
      return false;  // Value not found
    }
    if (value === node.value) {
      return true;  // Value found
    } else if (value < node.value) {
      return this.searchNode(node.left, value);  // Search left subtree
    } else {
      return this.searchNode(node.right, value);  // Search right subtree
    }
  }
}

Summary

  • Node Class: Represents each node with a value and references to left and right children.

  • Binary Search Tree Class: Manages the nodes and provides methods for inserting values, traversing the tree, and searching for elements.

  • Insert Operation: Adds a new value to the correct position in the tree while maintaining the BST property.

  • Traversal Operation: The inOrderTraversal method retrieves values in sorted order.

  • Search Operation: The search method checks if a specific value exists in the BST.

This implementation provides a basic understanding of how binary search trees work in TypeScript.

References:

TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript (typescriptlang.org)

Linked Lists in Typescript (Typescript Data Structures Part 1) (youtube.com)

Data Structures Tutorial - GeeksforGeeks