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