Lesson 2
Senior Software Engineer
I like JS, React, movies, music and I'm a big fan of LOTR and Star Wars ð€
May the Force be with you!
about 6 years with GlobalLogic
about 8 years in Web Development
Speaker and mentor at GL JS community
Part of the program committee at Fwdays
Inna Ivashchuk
A data structure is a specialized format for organizing, processing, retrieving and storing data.
Storing data (DB)
Managing resources and services (OS)
Data Exchange (TCP/IP)
Ordering and sorting
Indexing
Scalebility (Apache Spark)
and many other places
Data Types
Primitive
Non-Primitive
null
undefined
number
boolean
string
symbol
Object
An array is a collection of items stored at contiguous memory locations. The idea is to store multiple items of the same type together.
Can be one and multi-dimensional.
0 1 2 3
20
10
3
13
9
0
1
2
3
4
20
10
3
13
9
How we perceive an array:
How it is stored in memory
Consecutive memory locations
RAM
Some data types
An array
20
10
3
13
9
0
1
2
3
4
20
10
3
13
9
2
11
9
3
9
0
1
2
3
4
20
10
3
13
9
2
11
9
3
9
0
1
2
3
4
100
1
33
3
One-dimensional array
Multidimensional array
Two-dimensional array
0
1
2
0
1
// One-dimensional const movies = [ "Star Wars", "The Lord of the Rings", "Harry Potter", "The Matrix", "Dune", ]; console.log(movies[3]); // Two-dimensional const matrix = [ [1, 2, 3], [4, 5, 6] ]; console.log(matrix[1][1]); // Multidimensional const terrains = [ ['desert', 'desert', 'grass', 'grass'], ['desert', 'grass', 'water', 'grass'], ['grass', 'grass', 'water', 'water'], ['grass', 'grass', 'grass', 'grass'] ]; console.log(terrains[1][0]);
Stack is a linear data structure that follows a particular order in which the operations are performed. The order may be LIFO(Last In First Out) or FILO(First In Last Out).
// Stack class class Stack { // Array is used to implement stack constructor() { this.items = []; } // push method push(element) { // push element into the items this.items.push(element); } // pop method pop() { // return top most element in the stack // and removes it from the stack // Underflow if stack is empty if (this.items.length == 0) return "Underflow"; return this.items.pop(); } // peek method peek() { // return the top most element from the stack // but does'nt delete it. return this.items[this.items.length - 1]; } // isEmpty method isEmpty() { // return true if stack is empty return this.items.length == 0; } // printStack method printStack() { var str = ""; for (var i = 0; i < this.items.length; i++) str += this.items[i] + " "; return str; } } const stack = new Stack(); // Adding element to the stack stack.push(10); stack.push(20); stack.push(30); // Printing the stack element // prints [10, 20, 30] console.log(stack.printStack()); // returns 30 console.log(stack.peek()); // returns 30 and remove it from stack console.log(stack.pop()); // returns [10, 20] console.log(stack.printStack());
A Queue is a linear structure that follows a particular order in which the operations are performed. The order is First In First Out (FIFO)
// Queue class class Queue { // Array is used to implement a Queue constructor() { this.items = []; } // Functions to be implemented // enqueue method enqueue(element) { // adding element to the queue this.items.push(element); } // dequeue method dequeue() { // removing element from the queue // returns underflow when called // on empty queue if (this.isEmpty()) return "Underflow"; return this.items.shift(); } // front method front() { // returns the Front element of // the queue without removing it. if (this.isEmpty()) return "No elements in Queue"; return this.items[0]; } // isEmpty method isEmpty() { // return true if the queue is empty. return this.items.length == 0; } // printQueue method printQueue() { var str = ""; for (var i = 0; i < this.items.length; i++) str += this.items[i] + " "; return str; } } // creating object for queue class const queue = new Queue(); // Testing dequeue and pop on an empty queue // returns Underflow console.log(queue.dequeue()); // returns true console.log(queue.isEmpty()); // Adding elements to the queue // queue contains [10, 20, 30, 40, 50] queue.enqueue(10); queue.enqueue(20); queue.enqueue(30); queue.enqueue(40); queue.enqueue(50); queue.enqueue(60); // returns 10 console.log(queue.front()); // removes 10 from the queue // queue contains [20, 30, 40, 50, 60] console.log(queue.dequeue()); // returns 20 console.log(queue.front()); // removes 20 // queue contains [30, 40, 50, 60] console.log(queue.dequeue()); // printing the elements of the queue // prints [30, 40, 50, 60] console.log(queue.printQueue());
Last In First Out
LIFO
FIFO
First In First Out
A Linked List stores a collection of items in a linear order. Each element, or node, in a Linked list, contains a data item, as well as a reference, or link, to the next item in the list.
Node
class Node { constructor(value, next) { this.value = value; this.next = null; } }
class Node { constructor(value, next) { this.value = value; this.next = null; } } class List { constructor() { this.head = null; this.tail = null; } getNode(index) { if (index < 0) return 0; if (index === 0 && this.head) return this.head; let currentNode = this.head; while (index > 0 && currentNode && currentNode.next) { currentNode = currentNode.next; index--; } if (index !== 0) { return null; } return currentNode; } get(index) { return this.getNode(index).value; } push(value) { if (!this.head) { this.head = new Node(value); this.tail = this.head; return this; } this.tail.next = new Node(value); this.tail = this.tail.next; return this; } pop() { if (!this.head) { return null; } if (!this.head.next) { const temp = this.head.value; this.head = this.tail = null; return temp; } let currentNode = this.head; while (currentNode.next.next !== null) { currentNode = currentNode.next; } const temp = this.tail.value; this.tail = currentNode; currentNode.next = null; return temp; } remove(index) { const prev = this.getNode(index - 1); const current = this.getNode(index); if (prev && current && current.next) { prev.next = current.next; } else if (!prev && current) { this.head = current.next; } return current; } shift() { if (this.head) { const temp = this.head.value; if (this.head.next === null) { this.tail = null; } this.head = this.head.next; return temp; } return null; } unshift(value) { const newNode = new Node(value); newNode.next = this.head; this.head = newNode; return this; } toString() { let result = ''; let current = this.head; while (current) { result += `${current.value}${current.next ? ', ' : ''}`; current = current.next; } return result; } } const list = new List(); list.push(1).push(2).push(3).push(4); console.log(list.toString()); console.log(list.remove(1)); console.log(list.toString()); console.log(list.remove(0)); console.log(list.toString()); console.log(list.remove(333)); console.log(list.toString());
A Hash table (also known as a Hash map) - stores a collection of items in an associative array that plots keys to values. A Hash table uses a hash function to convert an index into an array of buckets that contain the desired data item.
const WEIRD_NUMBER = 23; function hash(str, limit = 53) { let result = 0; for(let i=0; i < Math.min(str.length, 100); i++) { result += (str.charCodeAt(i) - 96) * WEIRD_NUMBER; } return Math.abs(result % limit); } class HashTable { constructor() { this.array = new Array(10); } set(key, value) { const keyHash = hash(key, this.array.length); if(!this.array[keyHash]) { this.array[keyHash] = []; } const bucket = this.array[keyHash]; bucket.push([key, value]); } get(key) { const keyHash = hash(key, this.array.length); const bucket = this.array[keyHash]; for(let i = 0; i < bucket.length; i++) { const entries = bucket[i]; if (entries[0] === key) { return entries[1]; } } return; } keys() { const result = []; for (let i = 0; i < this.array.length; i++) { if(this.array[i]) { this.array[i].forEach(entry => { result.push(entry[0]); }); } } return result; } } const test = new HashTable(); test.set('black', '#000'); test.set('white', '#fff'); test.set('red', '#f00'); test.set('blue', '#00f'); test.set('green', '#0f0'); console.log(test); console.log(test.get('green')); console.log(test.get('black')); console.log(test.get('red')); console.log(test.get('white')); console.log(test.keys());
Map is a data collection type (in a more fancy way â abstract data structure type), in which, data is stored in a form of pairs, which contains a unique key and value mapped to that key. And because of the uniqueness of each stored key, there is no duplicate pair stored.
const map1 = new Map(); map1.set('a', 1); map1.set('b', 2); map1.set('c', 3); console.log(map1.get('a')); // Expected output: 1 map1.set('a', 97); console.log(map1.get('a')); // Expected output: 97 console.log(map1.size); // Expected output: 3 map1.delete('b'); console.log(map1.size); // Expected output: 2
A JavaScript Set is a collection of unique values of any type, whether primitive values or object references. The main things to know:
'Ron'
25
'QA'
'QA lead'
MAP
Keys
'name'
'age'
'job'
'title'
Values
'Ron'
'John'
'Alex'
'Juli'
SET
Indices
0
1
2
3
Values
A Tree stores a collection of items in an abstract, hierarchical way. Each node is associated with a key-value, with parent nodes linked to child nodes - or subnodes. There is one root node that is the ancestor of all the nodes in the tree.
Root (onle one)
Child
Leaf (no children)
class Node { constructor(value) { this.value = value; this.left = null; this.right = null; } } class BinarySearchTree { constructor() { this.root = null; } // Add new Node with {value} add(value) { if(!this.root) { this.root = new Node(value); return this; } let temp = this.root; while(true) { if(temp.value < value) { if (!temp.right) { temp.right = new Node(value); return this; } temp = temp.right; } else { if(!temp.left) { temp.left = new Node(value); return this; } temp = temp.left; } } } // Find Node with {value} find(value) { let temp = this.root; if(!temp) { return null; } while(true) { if(temp.value === value) { return temp; } else if (temp.value < value) { if(!temp.right) { return null; } temp = temp.right; } else { if (!temp.left) { return null; } temp = temp.left; } } } // Check if Node with {value} exists contains(value) { let node = this.find(value); if (node) { return true; } return false; } // Traversing // B // A C // A, B, C traverseInOrder(node = this.root) { let res = []; function traverse(node) { if (node.left) { traverse(node.left) } res.push(node.value) if (node.right) { traverse(node.right) } } traverse(node); return res; } // B, A, C traversePreorder(node = this.root) { return node ? [node.value, ...this.traversePreorder(node.left), ...this.traversePreorder(node.right)] : []; } // A, C, B traversePostorder() { let res = []; function traverse(node) { if (node.left) { traverse(node.left) } if (node.right) { traverse(node.right) } res.push(node.value) } traverse(this.root); return res; } } const tree = new BinarySearchTree(); tree.add(5).add(2).add(-1).add(6).add(100); // console.log(tree); // 5 // 2 6 // 100 console.log(tree.find(2)); console.log(tree.find(100)); console.log(tree.find(1000000)); console.log(tree.contains(2)); console.log(tree.contains(17)); console.log('Preorder ', tree.traversePreorder()); console.log('Inorder ', tree.traverseInOrder()); console.log('Postorder ', tree.traversePostorder());
A Heap is a tree-based structure in which each parent node's associated key value is greater than or equal to the key values of any of its children's key values.
Heap is a special case of balanced binary tree data structure where the root-node key is compared with its children and arranged accordingly.
Max-Heap: In a Max-Heap the key present at the root node must be greatest among the keys present at all of itâs children. The same property must be recursively true for all sub-trees in that Binary Tree.
Min-Heap: In a Min-Heap the key present at the root node must be minimum among the keys present at all of itâs children. The same property must be recursively true for all sub-trees in that Binary Tree.
function Heap(data = []) { this.data = data; this.size = data.length; if (data.length > 1) { this.buildMaxHeap(); } } Heap.prototype = { swap: function(i, j) { let temp = this.data[i]; this.data[i] = this.data[j]; this.data[j] = temp; }, maxHeapify: function(i) { let leftIdx = 2*i + 1; let rightIdx = 2*i + 2; let largest = i; if ( leftIdx < this.size && this.data[leftIdx] > this.data[largest]) { largest = leftIdx; } if (rightIdx < this.size && this.data[rightIdx] > this.data[largest]) { largest = rightIdx; } if (largest !== i) { this.swap(largest, i); this.maxHeapify(largest); } }, buildMaxHeap: function() { for(let i = Math.floor(this.size / 2); i >= 0; i--) { this.maxHeapify(i); } }, bubbleUp: function() { let curIdx = this.size - 1; while(curIdx > 0) { // O(log(n)) as we deviding parent index by 2 every iteration let parentIdx = Math.floor((curIdx - 1)/ 2); let lastVal = this.data[curIdx]; let parentData = this.data[parentIdx]; if (parentData >= lastVal) { return; } else { this.data[parentIdx] = lastVal; this.data[curIdx] = parentData; curIdx = parentIdx; } } }, push: function(val) { this.data.push(val); this.size++; this.bubbleUp(); }, decrementSize: function() { this.size--; }, print: function() { console.log(this.data) } } const heap = new Heap(); heap.push(20); heap.push(10); heap.push(30); heap.push(1); heap.push(100); heap.print(); /* Output: [ 100, 30, 20, 1, 10 ] The tree looks like: 100 / \ 30 20 / \ 1 10 */
A Graph stores a collection of items in a nonlinear fashion. Graphs are made up of a finite set of nodes, also known as vertices, and lines that connect them, also known as edges. These are useful for representing real-world systems such as computer networks.
class PriorityQueue { constructor(){ this.values = []; } enqueue(val, priority) { this.values.push({val, priority }); this.sort(); } dequeue() { return this.values.shift(); } sort() { this.values.sort((a, b) => a.priority - b.priority); }; } class WeightedGraph { /** * In graph theory and computer science, an adjacency list is a collection of unordered lists used to represent a finite graph */ constructor() { this.adjacencyList = {}; } /** * "Vertex" is a synonym for a node of a graph, i.e., one of the points on which the graph is defined and which may be connected by graph edges. * * Time complexity: O(1) */ addVertex(vertex) { if(this.adjacencyList[vertex]) { console.warn(`"${vertex}" vertex already present into adjacency list. Overriding ${vertex} `); } this.adjacencyList[vertex] = []; } /** * For an undirected graph, an unordered pair of nodes that specify a line joining these two nodes are said to form an edge. * * Time complexity: O(1) */ addEdge(vertex1, vertex2, weight) { if(!this.adjacencyList[vertex1]) { console.info(`Creating vertex "${vertex1}"`) this.addVertex(vertex1); } if(!this.adjacencyList[vertex2]) { console.info(`Creating vertex "${vertex2}"`) this.addVertex(vertex2); } this.adjacencyList[vertex1].push({ node: vertex2, weight }); this.adjacencyList[vertex2].push({ node: vertex1, weight }); } // O(|E|) removeEdge(vertex1, vertex2) { this.adjacencyList[vertex1] = this.adjacencyList[vertex1].filter(val => val !== vertex2); this.adjacencyList[vertex2] = this.adjacencyList[vertex2].filter(val => val !== vertex1); } // O(|V| + |E|) removeVertex(vertex) { this.adjacencyList[vertex] .forEach(vertex2 => this.removeEdge(vertex, vertex2)); delete this.adjacencyList[vertex]; } /** * Breadth-first search (BFS) is an algorithm for traversing or searching tree or graph data structures. * It starts at the tree root and explores all of the neighbor nodes at the present depth prior to moving on to the nodes at the next depth level. */ breadthFirstSearch() { const res = []; const keys = Object.keys(this.adjacencyList); const queue = [keys[0]]; const visited = { [keys[0]]: true }; while(queue.length) { const node = queue.shift(); res.push(node); this.adjacencyList[node].forEach(vrt => { if(!visited[vrt.node]) { visited[vrt.node] = true; queue.push(vrt.node); } }); } return res; } /** * Depth-first search (DFS) is an algorithm for traversing or searching tree or graph data structures. * The algorithm starts at the root node (selecting some arbitrary node as the root node in the case of a graph) * and explores as far as possible along each branch before backtracking. */ depthFirstSearch() { const res = []; const keys = Object.keys(this.adjacencyList); const visited = {}; const traverse = node => { if (!visited[node]) { visited[node] = true; res.push(node); this.adjacencyList[node].forEach(vrt => traverse(vrt.node)) } } traverse(keys[0]); return res; } /** * * Dijkstra's algorithm (or Dijkstra's Shortest Path First algorithm, SPF algorithm)[1] is an algorithm for finding the shortest paths * between nodes in a graph, which may represent, for example, road networks. * * It was conceived by computer scientist Edsger W. Dijkstra in 1956 and published three years later. */ dijkstra(vertex1, vertex2) { const previous = {}; const dist = {}; const keys = Object.keys(this.adjacencyList); const priorityQueue = new PriorityQueue(); let smallest; // init keys.forEach(key => { if (key === vertex1) { dist[key] = 0; priorityQueue.enqueue(key, 0); } else { dist[key] = Infinity; priorityQueue.enqueue(key, Infinity); } previous[key] = null; }); // take smallest dist vertex while(priorityQueue.values.length) { smallest = priorityQueue.dequeue().val; if(smallest === vertex2) { // wohoo const res = []; let curr = previous[vertex2] while(curr) { res.push(curr); curr = previous[curr]; } return res.reverse().concat(vertex2); } this.adjacencyList[smallest].forEach(el => { // calc dist to neighbour node let candidate = el.weight + dist[smallest]; if (dist[el.node] > candidate) { dist[el.node] = candidate; previous[el.node] = smallest; priorityQueue.enqueue(el.node, candidate) } }) } } } /** * Example: * * 7 * C --- D * 1 / 3 \ 2 * A ------- B * 3 | 2 | 5 * F ------- K * 4 \ 1 / 1 * J ---- I * */ const graph = new WeightedGraph(); graph.addVertex('A'); graph.addVertex('B'); graph.addVertex('C'); graph.addVertex('D'); graph.addVertex('F'); graph.addVertex('K'); graph.addVertex('J'); graph.addVertex('I'); graph.addEdge('C', 'D', 7); graph.addEdge('A', 'B', 3); graph.addEdge('F', 'K', 2); graph.addEdge('J', 'I', 1); graph.addEdge('C', 'A', 1); graph.addEdge('A', 'F', 3); graph.addEdge('F', 'J', 4); graph.addEdge('D', 'B', 2); graph.addEdge('B', 'K', 5); graph.addEdge('K', 'I', 1); console.log(graph.breadthFirstSearch()); console.log(graph.depthFirstSearch()); console.log('A->D', graph.dijkstra('A', 'D')); console.log('A->K', graph.dijkstra('A', 'K')); console.log('A->I', graph.dijkstra('A', 'I'));
A Trie, also known as a keyword tree, is a data structure that stores strings as data items that can be organized in a visual graph.
const movies = [ 'LoTR', 'Back to the Future', ]; // .push() - add a new element values.push('Star Wars', 'Matrix'); // .pop() - remove the last one values.pop(); // .reverse() - reverse an array values.reverse(); // .unshift() // add a new elemnt // to the beginning of an array values.unshift('Avatar'); // .sort() values.sort(); // .filter() values.filter();
=> O(1)
=> O(1)
=> O(n)
=> O(n)
=> O(n log (n))
=> O(n)
Time complexity Big 0 for Javascript Array methods and examples can be found in this article
Keep working, learning, developing and definitely donate and help our Ukrainian Army and volunteers