├── .gitignore ├── README.md ├── code ├── Lecture1 │ └── main.cpp ├── Lecture10 │ ├── map_analysis.cpp │ ├── string_umap_analysis.cpp │ ├── unordered_map_analysis.cpp │ └── unordered_map_collision_analysis.cpp ├── Lecture11 │ ├── count_monte_cristo.txt │ ├── improved_word_count.cpp │ └── word_count.cpp ├── Lecture2 │ ├── modular │ │ ├── main.cpp │ │ ├── vector2D.cpp │ │ └── vector2D.h │ ├── oop │ │ ├── main.cpp │ │ ├── vector2d.cpp │ │ └── vector2d.h │ └── procedural │ │ └── vector2d_struct.c ├── Lecture3 │ ├── class_anatomy.cpp │ ├── improved_vector_class.cpp │ └── inheritance.cpp ├── Lecture6_7 │ ├── snake.cpp │ ├── vector.cpp │ ├── vector_deep_copy.cpp │ ├── vector_shallow_copy.cpp │ ├── vector_shallow_copy_improved.cpp │ └── vector_template.cpp └── Lecture9 │ ├── dynamic_array.h │ ├── linked_list.h │ ├── my_algorithms.cpp │ └── my_iterator_algorithms.cpp ├── images ├── layered_architecture.png └── modular_diagram.jpeg ├── lectures ├── Lecture1.md ├── Lecture10.md ├── Lecture11.md ├── Lecture2_3.md ├── Lecture3.md ├── Lecture4.md ├── Lecture5.md ├── Lecture6.md ├── Lecture7.md └── Lecture8.md └── resources └── talk_back_to_basics_classic_stl__bob_steagall__cppcon_2021_1_compressed.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | *.i 2 | *.o 3 | *.out 4 | *.s 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data-Structure-Lab 2 | Content for Data structure labs 3 | 4 | 5 | ## Table of contents 6 | 1. [Lecture 1](lectures/Lecture1.md) 7 | - Why study data structures? 8 | - Game plan 9 | - Compilation process(revision) 10 | 2. [Lecture 2](lectures/Lecture2_3.md) 11 | - Layered Architecture 12 | - Programming paradigms 13 | - Procedural vs Modular vs OOP 14 | - functions 15 | - Namespaces 16 | - multifile compilation 17 | - Classes 18 | 3. [Lecture 3](lectures/Lecture3.md) 19 | - Anatomy of a class 20 | - Inheritance 21 | - Data encapsulation 22 | - Operator overloading 23 | - Friend functions 24 | 4. [Lecture 4](lectures/Lecture4.md) 25 | - Dynamic memory allocation 26 | 5. [Lecture 5](lectures/Lecture5.md) 27 | - Function overloading 28 | - Function templates 29 | 6. [Lecture 6](lectures/Lecture6.md) 30 | - Arrays 31 | - Multidimensional arrays 32 | - Dynamic arrays 33 | 7. [Lecture 7](lectures/Lecture7.md) 34 | - Generic Programming 35 | 8. [Lecture 8](lectures/Lecture8.md) 36 | - Copy Constructor and Assignment operator 37 | - Deep copying vs Shallow copying 38 | - Linked lists 39 | 9. [Lecture 9](resources/talk_back_to_basics_classic_stl__bob_steagall__cppcon_2021_1_compressed.pdf) 40 | - STL 41 | - Containers 42 | - Iterators 43 | - [Example code](code/Lecture9) 44 | 10. [Lecture 10](lectures/Lecture10.md) 45 | - Associative Containers 46 | - [Example code](code/Lecture10) 47 | 11. [Lecture 11](lectures/Lecture11.md) 48 | - Heap/Priority Queue 49 | - [Example code](code/Lecture11) 50 | -------------------------------------------------------------------------------- /code/Lecture1/main.cpp: -------------------------------------------------------------------------------- 1 | // main.cpp 2 | // locate header file using: find /usr/include -name iostream -type f -print 3 | #include 4 | 5 | int main() 6 | { 7 | std::cout << "hello world"<< std::endl; 8 | return 1; 9 | } 10 | -------------------------------------------------------------------------------- /code/Lecture10/map_analysis.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct Node { 7 | int key; 8 | int value; 9 | Node* left; 10 | Node* right; 11 | }; 12 | 13 | void printBST(Node* root, int space = 0, int indent = 4) { 14 | if (root == nullptr) return; 15 | 16 | space += indent; 17 | printBST(root->right, space); 18 | 19 | std::cout << std::endl; 20 | for (int i = indent; i < space; i++) std::cout << " "; 21 | std::cout << root->key << "(" << root->value << ")\n"; 22 | 23 | printBST(root->left, space); 24 | } 25 | 26 | Node* insertBST(Node* root, int key, int value) { 27 | if (root == nullptr) return new Node{key, value, nullptr, nullptr}; 28 | if (key < root->key){ 29 | root->left = insertBST(root->left, key, value); 30 | 31 | }else{ 32 | root->right = insertBST(root->right, key, value); 33 | } 34 | return root; 35 | } 36 | 37 | int main() { 38 | std::map map; 39 | Node* root = nullptr; 40 | 41 | std::cout << "Inserting elements into std::map (ordered map):" << std::endl; 42 | int keys[] = {31, 21, 1, 41, 11, }; 43 | for (int i = 0; i < 5; ++i) { 44 | map[keys[i]] = keys[i] * 10; 45 | root = insertBST(root, keys[i], keys[i] * 10); 46 | } 47 | 48 | std::cout << "\nElements in map (ordered by key):" << std::endl; 49 | for (const auto& pair : map) { 50 | std::cout << "(" << pair.first << ", " << pair.second << ") "; 51 | } 52 | std::cout << std::endl; 53 | 54 | std::cout << "\nVisual representation of the BST used in std::map:" << std::endl; 55 | printBST(root); 56 | 57 | // loop over map 58 | std::cout << "\nLooping over map (ordered by key):" << std::endl; 59 | for (const auto& pair : map) { 60 | std::cout << "(" << pair.first << ", " << pair.second << ") "; 61 | } 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /code/Lecture10/string_umap_analysis.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | std::unordered_map map; 7 | std::hash str_hash; 8 | 9 | std::vector keys = {"apple", "banana", "cherry", "date", "elderberry", "fig", "grape"}; 10 | 11 | for (const auto& key : keys) { 12 | map[key] = key.length(); 13 | } 14 | 15 | std::cout << "\nBucket count: " << map.bucket_count() << "\n"; 16 | 17 | for (size_t i = 0; i < map.bucket_count(); ++i) { 18 | if (map.bucket_size(i) > 0) { 19 | std::cout << "Bucket " << i << " contains: "; 20 | for (const auto& pair : map) { 21 | if (map.bucket(pair.first) == i) { 22 | std::cout << "(" << pair.first << ", " << pair.second << ", Hash: " << str_hash(pair.first) << ") "; 23 | } 24 | } 25 | std::cout << "\n"; 26 | } 27 | } 28 | 29 | return 0; 30 | } -------------------------------------------------------------------------------- /code/Lecture10/unordered_map_analysis.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | std::unordered_map map; 7 | 8 | std::cout << "Initial bucket count: " << map.bucket_count() << std::endl; 9 | std::cout << "Max bucket count: " << map.max_bucket_count() << std::endl; 10 | 11 | for (int i = 0; i < 30; ++i) { 12 | // log time taken to insert element 13 | auto start = std::chrono::high_resolution_clock::now(); 14 | map[i] = i * 10; 15 | auto end = std::chrono::high_resolution_clock::now(); 16 | auto duration = std::chrono::duration_cast(end - start); 17 | std::cout << "Time taken to insert " << (i + 1) << " element: " << duration.count() << " ns" << std::endl; 18 | 19 | std::cout << "\nAfter inserting " << (i + 1) << " elements:" << std::endl; 20 | std::cout << "Bucket count: " << map.bucket_count() << std::endl; 21 | std::cout << "Load factor: " << map.load_factor() << std::endl; 22 | 23 | if (!map.empty()) { 24 | // print bucket corresponding to each element 25 | for (int j = 0; j < map.bucket_count(); ++j) { 26 | std::cout << "Bucket " << j << ": "; 27 | for (auto it = map.cbegin(j); it != map.cend(j); ++it) { 28 | std::cout << it->first << " "; 29 | } 30 | std::cout << std::endl; 31 | } 32 | std::cout << std::endl; 33 | } 34 | } 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /code/Lecture10/unordered_map_collision_analysis.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | std::unordered_map map; 6 | 7 | std::cout << "Initial bucket count: " << map.bucket_count() << std::endl; 8 | std::cout << "Max bucket count: " << map.max_bucket_count() << std::endl; 9 | 10 | // Insert values that may collide (assuming a simple hash function behavior) 11 | // int collision_keys[] = {1, 14, 40,53,73, }; 12 | for (int i = 1; i < 14; ++i) { 13 | map[i*13+1] = i*10; 14 | } 15 | 16 | std::cout << "\nAfter inserting potential collision elements:" << std::endl; 17 | std::cout << "Bucket count: " << map.bucket_count() << std::endl; 18 | 19 | // Print bucket distribution and elements in each bucket 20 | for (size_t j = 0; j < map.bucket_count(); ++j) { 21 | // if (map.bucket_size(j) > 1) { // Checking collision (more than one element in the bucket) 22 | std::cout << "Bucket " << j << " (Collision) -> " << map.bucket_size(j) << " elements: "; 23 | for (auto it = map.begin(); it != map.end(); ++it) { 24 | if (map.bucket(it->first) == j) { 25 | std::cout << "(" << it->first << ", " << it->second << ") "; 26 | } 27 | } 28 | std::cout << std::endl; 29 | // } 30 | } 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /code/Lecture11/improved_word_count.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | // Define a comparator for the priority queue 13 | struct Compare { 14 | bool operator()(const pair& a, const pair& b) { 15 | // If frequencies are the same, use lexicographical order (min-heap) 16 | if (a.second == b.second) { 17 | return a.first > b.first; 18 | } 19 | // Return true if 'a' should be after 'b' in the priority queue 20 | return a.second > b.second; // for min-heap by frequency 21 | } 22 | }; 23 | 24 | int main(int argc, char *argv[]) { 25 | if (argc != 2) { 26 | cerr << "Usage: " << argv[0] << " " << endl; 27 | return 1; 28 | } 29 | 30 | int k = stoi(argv[1]); 31 | size_t bucket_count = 10000; 32 | unordered_map wordCount(bucket_count); 33 | string line, word; 34 | 35 | // calculate using chrono::high_resolution_clock::now() to measure time it takes to read text from standard input 36 | 37 | auto start = std::chrono::high_resolution_clock::now(); 38 | 39 | // Read text from standard input 40 | while (getline(cin, line)) { 41 | stringstream ss(line); 42 | while (ss >> word) { 43 | ++wordCount[word]; 44 | } 45 | } 46 | 47 | auto end = std::chrono::high_resolution_clock::now(); 48 | auto duration = std::chrono::duration_cast(end - start); 49 | std::cout << "Time taken to read text: " << duration.count()/1e9 << " s" << std::endl; 50 | // Use a min-heap to keep the top k elements 51 | priority_queue, vector>, Compare> pq; 52 | 53 | // calculate using chrono::high_resolution_clock::now() to measure time it takes to insert elements into the priority queue 54 | 55 | auto start2 = std::chrono::high_resolution_clock::now(); 56 | for (const auto& entry : wordCount) { 57 | // cout << "pq size: " << pq.size() << endl; 58 | // cout << entry.first << ": " << entry.second << endl; 59 | if (pq.size() < k) { 60 | 61 | pq.push(entry); 62 | // cout << pq.top().first << ": " << pq.top().second << endl; 63 | 64 | } else if (entry.second > pq.top().second) { 65 | // If the new entry has a higher frequency, replace the top 66 | pq.pop(); 67 | pq.push(entry); 68 | // cout << "pq size: " << pq.size() << endl; 69 | // cout << entry.first << ":" << entry.second << "replaced " << pq.top().first << ":" << pq.top().second << endl; 70 | } 71 | // else{ 72 | // cout << entry.first << ":" << entry.second << " didnt replace " << pq.top().first << ":" << pq.top().second << endl; 73 | 74 | // } 75 | } 76 | 77 | auto end2 = std::chrono::high_resolution_clock::now(); 78 | auto duration2 = std::chrono::duration_cast(end2 - start2); 79 | std::cout << "Time taken to insert elements into the priority queue: " << duration2.count()/1e9 << " s" << std::endl; 80 | // Collect results into a vector (in sorted order) 81 | vector> result; 82 | while (!pq.empty()) { 83 | result.emplace_back(pq.top()); 84 | pq.pop(); 85 | } 86 | 87 | // Print the top k frequent words in correct order (highest frequency first) 88 | // sort(result.rbegin(), result.rend(), Compare()); 89 | for (const auto& entry : result) { 90 | cout << entry.first << ": " << entry.second << endl; 91 | } 92 | 93 | return 0; 94 | } -------------------------------------------------------------------------------- /code/Lecture11/word_count.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | bool customCompare(const pair& a, const pair& b) { 13 | // Sort primarily by frequency in descending order 14 | if (a.second != b.second) { 15 | return a.second > b.second; 16 | } 17 | // If frequencies are the same, sort alphabetically 18 | return a.first < b.first; 19 | } 20 | 21 | int main(int argc, char *argv[]) { 22 | if (argc != 2) { 23 | cerr << "Usage: " << argv[0] << " " << endl; 24 | return 1; 25 | } 26 | 27 | int k = stoi(argv[1]); 28 | // map wordCount; // solution1 : use ordered map 29 | // unordered_map wordCount; // solution2 : use unordered map 30 | // solution 3: use unordered map with predefined bucket count to avoid rehasing and memory allocation 31 | size_t bucket_count = 10000; 32 | unordered_map wordCount(bucket_count); 33 | string line, word; 34 | 35 | // calculate using chronos time in seconds to measure time it takes to read text from standard input 36 | auto start = chrono::high_resolution_clock::now(); 37 | // Read text from standard input. Complexity O(N) 38 | while (getline(cin, line)) { 39 | stringstream ss(line); 40 | while (ss >> word) { 41 | // Increment the word count 42 | ++wordCount[word]; 43 | } 44 | } 45 | 46 | auto end = chrono::high_resolution_clock::now(); 47 | auto duration = chrono::duration_cast(end - start); 48 | cout << "Time taken to read text: " << duration.count()/1e9 << " s" << endl; 49 | 50 | // Move pairs to a vector 51 | vector> freqVector(wordCount.begin(), wordCount.end()); 52 | 53 | // Sort the vector using customCompare function Complexity O(NlogN) 54 | auto start2 = chrono::high_resolution_clock::now(); 55 | sort(freqVector.begin(), freqVector.end(), customCompare); 56 | auto end2 = chrono::high_resolution_clock::now(); 57 | auto duration2 = chrono::duration_cast(end2 - start2); 58 | cout << "Time taken to sort vector: " << duration2.count()/1e9 << " s" << endl; 59 | 60 | // Print the top k frequent words 61 | for (int i = 0; i < k && i < freqVector.size(); ++i) { 62 | cout << freqVector[i].first << ": " << freqVector[i].second << endl; 63 | } 64 | 65 | return 0; 66 | } -------------------------------------------------------------------------------- /code/Lecture2/modular/main.cpp: -------------------------------------------------------------------------------- 1 | #include "vector2D.h" 2 | 3 | // using namespace VectorOperations; 4 | 5 | int main() { 6 | VectorOperations::Vector2D v1 = {3.0, 4.0}; 7 | VectorOperations::Vector2D v2 = {1.0, 2.0}; 8 | 9 | std::cout << "Vector 1: "; 10 | VectorOperations::displayVector(v1); 11 | std::cout << "Vector 2: "; 12 | VectorOperations::displayVector(v2); 13 | 14 | VectorOperations::Vector2D sum = VectorOperations::addVectors(v1, v2); 15 | std::cout << "Sum: "; 16 | displayVector(sum); 17 | 18 | VectorOperations::Vector2D difference = VectorOperations::subtractVectors(v1, v2); 19 | std::cout << "Difference: "; 20 | displayVector(difference); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /code/Lecture2/modular/vector2D.cpp: -------------------------------------------------------------------------------- 1 | #include "vector2D.h" 2 | 3 | namespace VectorOperations { 4 | 5 | // note that functions are defined here with full implementation 6 | // Function to add two vectors 7 | Vector2D addVectors(Vector2D v1, Vector2D v2) { 8 | Vector2D result; 9 | result.x = v1.x + v2.x; 10 | result.y = v1.y + v2.y; 11 | return result; 12 | } 13 | 14 | // Function to subtract two vectors 15 | Vector2D subtractVectors(Vector2D v1, Vector2D v2) { 16 | Vector2D result; 17 | result.x = v1.x - v2.x; 18 | result.y = v1.y - v2.y; 19 | return result; 20 | } 21 | 22 | // Function to display a vector 23 | void displayVector(Vector2D v) { 24 | printf("(%.2f, %.2f)\n", v.x, v.y); 25 | } 26 | 27 | } // namespace VectorOperations 28 | -------------------------------------------------------------------------------- /code/Lecture2/modular/vector2D.h: -------------------------------------------------------------------------------- 1 | // Vector2D.h 2 | #ifndef VECTOR2D_H 3 | #define VECTOR2D_H 4 | 5 | #include 6 | 7 | namespace VectorOperations { 8 | 9 | // define a struct to represent a 2D vector 10 | typedef struct { 11 | float x; 12 | float y; 13 | } Vector2D; 14 | 15 | // note that functions are only declared here with signatures(argument types + return type) 16 | // implementation elsewhere 17 | 18 | // Function to add two vectors 19 | Vector2D addVectors(Vector2D v1, Vector2D v2); 20 | 21 | // Function to subtract two vectors 22 | Vector2D subtractVectors(Vector2D v1, Vector2D v2); 23 | 24 | // Function to display a vector 25 | void displayVector(Vector2D v); 26 | 27 | } // namespace VectorOperations 28 | 29 | #endif // VECTOR2D_H 30 | -------------------------------------------------------------------------------- /code/Lecture2/oop/main.cpp: -------------------------------------------------------------------------------- 1 | #include "vector2d.h" 2 | 3 | using namespace VectorOperations; 4 | 5 | int main() { 6 | Vector2D v1(3.0, 4.0); 7 | Vector2D v2(1.0, 2.0); 8 | 9 | std::cout << "Vector 1: "; 10 | v1.displayVector(); 11 | std::cout << "Vector 2: "; 12 | v2.displayVector(); 13 | 14 | Vector2D sum = v1.addVectors(v2); 15 | std::cout << "Sum: "; 16 | sum.displayVector(); 17 | 18 | Vector2D difference = v1.subtractVectors(v2); 19 | std::cout << "Difference: "; 20 | difference.displayVector(); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /code/Lecture2/oop/vector2d.cpp: -------------------------------------------------------------------------------- 1 | #include "vector2d.h" 2 | 3 | namespace VectorOperations { 4 | 5 | // constructor 6 | Vector2D::Vector2D(float x, float y){ 7 | this->x = x; 8 | this->y=y; 9 | } 10 | 11 | // default constructor 12 | Vector2D::Vector2D(){ 13 | this->x = 0; 14 | this->y = 0; 15 | } 16 | 17 | Vector2D Vector2D::addVectors(Vector2D v) { 18 | return Vector2D(x + v.x, y + v.y); 19 | } 20 | 21 | Vector2D Vector2D::subtractVectors(Vector2D v){ 22 | return Vector2D(x - v.x, y - v.y); 23 | } 24 | 25 | void Vector2D::displayVector(){ 26 | std::cout << "(" << x << ", " << y << ")" << std::endl; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /code/Lecture2/oop/vector2d.h: -------------------------------------------------------------------------------- 1 | // Vector2D.h 2 | #ifndef VECTOR2D_H 3 | #define VECTOR2D_H 4 | 5 | #include 6 | 7 | namespace VectorOperations { 8 | 9 | class Vector2D { 10 | public: 11 | float x, y; 12 | 13 | // declare constructor function 14 | Vector2D(float x, float y); 15 | 16 | // default constructor 17 | Vector2D(); 18 | 19 | // Function to add two vectors 20 | Vector2D addVectors(Vector2D v); 21 | 22 | // Function to subtract two vectors 23 | Vector2D subtractVectors(Vector2D v); 24 | 25 | // Function to display a vector 26 | void displayVector(); 27 | }; 28 | 29 | } // namespace VectorOperations 30 | 31 | #endif // VECTOR2D_H 32 | -------------------------------------------------------------------------------- /code/Lecture2/procedural/vector2d_struct.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // define a struct to represent a 2D vector 4 | typedef struct { 5 | float x; 6 | float y; 7 | } Vector2D; 8 | 9 | // Function to add two vectors 10 | Vector2D addVectors(Vector2D v1, Vector2D v2) { 11 | Vector2D result; 12 | result.x = v1.x + v2.x; 13 | result.y = v1.y + v2.y; 14 | return result; 15 | } 16 | 17 | // Function to subtract two vectors 18 | Vector2D subtractVectors(Vector2D v1, Vector2D v2) { 19 | Vector2D result; 20 | result.x = v1.x - v2.x; 21 | result.y = v1.y - v2.y; 22 | return result; 23 | } 24 | 25 | // Function to display a vector 26 | void displayVector(Vector2D v) { 27 | printf("(%.2f, %.2f)\n", v.x, v.y); 28 | } 29 | 30 | int main() { 31 | Vector2D v1 = {3.0, 4.0}; 32 | Vector2D v2 = {1.0, 2.0}; 33 | 34 | printf("Vector 1: "); 35 | displayVector(v1); 36 | printf("Vector 2: "); 37 | displayVector(v2); 38 | 39 | Vector2D sum = addVectors(v1, v2); 40 | printf("Sum: "); 41 | displayVector(sum); 42 | 43 | Vector2D difference = subtractVectors(v1, v2); 44 | printf("Difference: "); 45 | displayVector(difference); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /code/Lecture3/class_anatomy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // define vector class 3 | 4 | class Vector2D { 5 | private: 6 | float x, y; 7 | public: 8 | Vector2D(float x, float y){ 9 | std::cout << "constructor called" << "(" << x << ", " << y << ")" << std::endl; 10 | this->x = x; 11 | this->y = y; 12 | } 13 | // default constructor 14 | Vector2D(){ 15 | std::cout << "default constructor called" << std::endl; 16 | this->x = 0; 17 | this->y = 0; 18 | } 19 | 20 | // destructor 21 | ~Vector2D(){ 22 | 23 | std::cout << "destructor called" << "(" << x << ", " << y << ")" << std::endl; 24 | 25 | } 26 | 27 | // Function to add two vectors 28 | Vector2D addVectors(Vector2D v) const { 29 | return Vector2D(this->x + v.x, this->y + v.y); 30 | } 31 | 32 | // Function to subtract two vectors 33 | Vector2D subtractVectors(Vector2D v) { 34 | return Vector2D(this->x - v.x, this->y - v.y); 35 | } 36 | 37 | void displayVector(){ 38 | std::cout << "(" << x << ", " << y << ")" << std::endl; 39 | } 40 | }; 41 | 42 | int main(int argc, char const *argv[]) 43 | { 44 | 45 | // initialise vector v1 46 | Vector2D v1(3.0, 4.0); 47 | std::cout << "Vector 1: "; 48 | v1.displayVector(); 49 | 50 | 51 | 52 | // initialise vector v2 53 | Vector2D v2(1.0, 2.0); 54 | std::cout << "Vector 2: "; 55 | v2.displayVector(); 56 | 57 | std::cout << "calling addVectors" << std::endl; 58 | Vector2D sum = v1.addVectors(v2); 59 | std::cout << "called addVectors" << std::endl; 60 | std::cout << "Sum: "; 61 | sum.displayVector(); 62 | 63 | 64 | // Vector2D difference = v1.subtractVectors(v2); 65 | // std::cout << "Difference: "; 66 | // difference.displayVector(); 67 | 68 | 69 | Vector2D *v3 = new Vector2D(5, 7); 70 | // std::cout << "calling addVectors" << std::endl; 71 | // Vector2D sum2 = v3->addVectors(v1); 72 | // std::cout << "called addVectors" << std::endl; 73 | 74 | // v1 used last time 75 | // std::cout << "Sum2: "; 76 | // sum2.displayVector(); 77 | delete v3; 78 | 79 | // Vector2D v1_copy = v1; 80 | // Vector2D v2_copy = v2; 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /code/Lecture3/improved_vector_class.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Vector2D { 4 | private: 5 | float x, y; 6 | 7 | public: 8 | // Parameterized constructor 9 | Vector2D(float x, float y) : x(x), y(y) { 10 | std::cout << "Constructor called (" << x << ", " << y << ")" << std::endl; 11 | } 12 | 13 | // Default constructor 14 | Vector2D() : x(0), y(0) { 15 | std::cout << "Default constructor called" << std::endl; 16 | } 17 | 18 | // Destructor 19 | ~Vector2D() { 20 | std::cout << "Destructor called (" << x << ", " << y << ")" << std::endl; 21 | } 22 | 23 | // Overloading '+' operator for vector addition 24 | Vector2D operator+(const Vector2D& v) const { 25 | return Vector2D(x + v.x, y + v.y); 26 | } 27 | 28 | // Overloading '-' operator for vector subtraction 29 | Vector2D operator-(const Vector2D& v) const { 30 | return Vector2D(x - v.x, y - v.y); 31 | } 32 | 33 | // Overloading '*' operator for scalar multiplication 34 | Vector2D operator*(float scalar) const { 35 | return Vector2D(x * scalar, y * scalar); 36 | } 37 | 38 | friend Vector2D operator*(int scalar, Vector2D& vec) { 39 | return vec * scalar; 40 | } 41 | 42 | // Overloading '<' operator for vector comparison (based on magnitude) 43 | bool operator<(const Vector2D& v) const { 44 | return (x * x + y * y) < (v.x * v.x + v.y * v.y); 45 | } 46 | 47 | // Display vector 48 | void displayVector() const { 49 | std::cout << "(" << x << ", " << y << ")" << std::endl; 50 | } 51 | }; 52 | 53 | // Example usage 54 | int main() { 55 | Vector2D v1(3, 4); 56 | Vector2D v2(1, 2); 57 | 58 | Vector2D v3 = v1 + v2; 59 | std::cout << "v1 + v2 = "; 60 | v3.displayVector(); 61 | 62 | Vector2D v4 = v1 - v2; 63 | std::cout << "v1 - v2 = "; 64 | v4.displayVector(); 65 | 66 | Vector2D v5 = v1 * 2; 67 | std::cout << "v1 * 2 = "; 68 | v5.displayVector(); 69 | 70 | Vector2D v6 = 2 * v1 ; 71 | std::cout << "2 * v1 = "; 72 | v5.displayVector(); 73 | 74 | if (v1 < v2) { 75 | std::cout << "v1 is smaller than v2" << std::endl; 76 | } else { 77 | std::cout << "v1 is not smaller than v2" << std::endl; 78 | } 79 | 80 | return 0; 81 | } -------------------------------------------------------------------------------- /code/Lecture3/inheritance.cpp: -------------------------------------------------------------------------------- 1 | // C++ program to demonstrate inheritance 2 | 3 | #include 4 | using namespace std; 5 | 6 | // base class 7 | class Animal { 8 | 9 | public: 10 | void eat() { 11 | cout << "I can eat!" << endl; 12 | } 13 | 14 | void sleep() { 15 | cout << "I can sleep!" << endl; 16 | } 17 | }; 18 | 19 | // derived class 20 | class Dog : public Animal { 21 | 22 | public: 23 | void bark() { 24 | cout << "I can bark! Woof woof!!" << endl; 25 | } 26 | }; 27 | 28 | int main() { 29 | // Create object of the Dog class 30 | Dog dog1; 31 | 32 | // Calling members of the base class 33 | dog1.eat(); 34 | dog1.sleep(); 35 | 36 | // Calling member of the derived class 37 | dog1.bark(); 38 | 39 | return 0; 40 | } -------------------------------------------------------------------------------- /code/Lecture6_7/snake.cpp: -------------------------------------------------------------------------------- 1 | # include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int main(int argc, char const *argv[]) 8 | { 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /code/Lecture6_7/vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // to use std::out_of_range 3 | 4 | 5 | class IntVector { 6 | private: 7 | int* data; // Pointer to the first element of the vector 8 | size_t size; // Number of elements in the vector 9 | size_t capacity; // Current allocated capacity of the vector 10 | 11 | void resize() { 12 | size_t new_capacity = capacity == 0? 1: capacity *2; 13 | reserve(new_capacity); 14 | } 15 | 16 | public: 17 | IntVector(){ 18 | data = nullptr; 19 | size = 0; 20 | capacity = 0; 21 | } 22 | 23 | ~IntVector() { 24 | // deallocate underlying memory 25 | } 26 | 27 | size_t getSize() const { 28 | // return current size of the vector 29 | return size; 30 | } 31 | 32 | size_t getCapacity() const { 33 | // return current capacity of the vector 34 | return capacity; 35 | } 36 | 37 | void reserve(size_t new_capacity) { 38 | if(new_capacity > capacity) { 39 | int *new_data = new int[new_capacity]; 40 | for(size_t i=0;i size raise exception 70 | if (index >= size) { 71 | throw std::out_of_range("Index out of range"); 72 | } 73 | 74 | if(size==capacity){ 75 | resize(); 76 | } 77 | 78 | size++; 79 | 80 | // shift all elements after index to the right 81 | for(size_t i=size;i>=index;i--){ 82 | data[i] = data[i-1]; 83 | } 84 | 85 | data[index] = value; 86 | // insert value 87 | } 88 | 89 | void erase(size_t index) { 90 | if (index >= size) { 91 | throw std::out_of_range("Index out of range"); 92 | } 93 | // move all elements after index to the left 94 | for(size_t i=index;i= size) { 107 | throw std::out_of_range("Index out of range"); 108 | } 109 | // apply index operator on the underlying data 110 | return data[index]; 111 | } 112 | 113 | // disallow copy constructor and assignment operator 114 | IntVector(const IntVector&) = delete; 115 | IntVector& operator=(const IntVector&) = delete; 116 | }; 117 | 118 | 119 | int main() { 120 | IntVector vec; 121 | vec.push_back(0); 122 | vec.push_back(1); 123 | vec.push_back(2); 124 | 125 | 126 | std::cout << "Size: " << vec.getSize() << std::endl; 127 | std::cout << "Capacity: " << vec.getCapacity() << std::endl; 128 | 129 | vec.insert(1, 4); 130 | std::cout << "After insert(1,4): "; 131 | for (size_t i = 0; i < vec.getSize(); ++i) { 132 | std::cout << vec[i] << " "; 133 | } 134 | std::cout << std::endl; 135 | 136 | vec.erase(2); 137 | std::cout << "After erase(2): "; 138 | for (size_t i = 0; i < vec.getSize(); ++i) { 139 | std::cout << vec[i] << " "; 140 | } 141 | std::cout << std::endl; 142 | 143 | // IntVector vec2 = vec; 144 | // std::cout << "After copy constructor: "; 145 | // for (size_t i = 0; i < vec2.getSize(); ++i) { 146 | // std::cout << vec2[i] << " "; 147 | // } 148 | // std::cout << std::endl; 149 | 150 | // IntVector vec3 = vec; 151 | 152 | vec.clear(); 153 | std::cout << "After clear, size: " << vec.getSize() << std::endl; 154 | 155 | return 0; 156 | } -------------------------------------------------------------------------------- /code/Lecture6_7/vector_deep_copy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // to use std::out_of_range 3 | 4 | 5 | class IntVector { 6 | private: 7 | int* data; // Pointer to the first element of the vector 8 | size_t size; // Number of elements in the vector 9 | size_t capacity; // Current allocated capacity of the vector 10 | 11 | void resize() { 12 | size_t new_capacity = capacity == 0? 1: capacity *2; 13 | reserve(new_capacity); 14 | } 15 | 16 | public: 17 | IntVector(){ 18 | data = nullptr; 19 | size = 0; 20 | capacity = 0; 21 | } 22 | 23 | ~IntVector() { 24 | // deallocate underlying memory 25 | delete[] data; 26 | } 27 | 28 | size_t getSize() const { 29 | // return current size of the vector 30 | return size; 31 | } 32 | 33 | size_t getCapacity() const { 34 | // return current capacity of the vector 35 | return capacity; 36 | } 37 | 38 | void reserve(size_t new_capacity) { 39 | if(new_capacity > capacity) { 40 | int *new_data = new int[new_capacity]; 41 | for(size_t i=0;i size raise exception 71 | if (index >= size) { 72 | throw std::out_of_range("Index out of range"); 73 | } 74 | 75 | if(size==capacity){ 76 | resize(); 77 | } 78 | 79 | size++; 80 | 81 | // shift all elements after index to the right 82 | for(size_t i=size;i>=index;i--){ 83 | data[i] = data[i-1]; 84 | } 85 | 86 | data[index] = value; 87 | // insert value 88 | } 89 | 90 | void erase(size_t index) { 91 | if (index >= size) { 92 | throw std::out_of_range("Index out of range"); 93 | } 94 | // move all elements after index to the left 95 | for(size_t i=index;i= size) { 108 | throw std::out_of_range("Index out of range"); 109 | } 110 | // apply index operator on the underlying data 111 | return data[index]; 112 | } 113 | 114 | // allow deep copying 115 | IntVector(const IntVector& other) { 116 | size = other.size; 117 | capacity = other.capacity; 118 | data = new int[capacity]; 119 | for(size_t i=0;i vec2 = vec; 168 | // std::cout << "After copy constructor: "; 169 | // for (size_t i = 0; i < vec2.getSize(); ++i) { 170 | // std::cout << vec2[i] << " "; 171 | // } 172 | // std::cout << std::endl; 173 | 174 | // IntVector vec3 = vec; 175 | 176 | vec.clear(); 177 | std::cout << "After clear, size: " << vec.getSize() << std::endl; 178 | 179 | return 0; 180 | } -------------------------------------------------------------------------------- /code/Lecture6_7/vector_shallow_copy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // to use std::out_of_range 3 | #include // to use std::shared_ptr 4 | 5 | class IntVector { 6 | private: 7 | std::shared_ptr data; // Use shared_ptr for the data 8 | size_t size; // Number of elements in the vector 9 | size_t capacity; // Current allocated capacity of the vector 10 | 11 | void resize() { 12 | size_t new_capacity = capacity == 0 ? 1 : capacity * 2; 13 | reserve(new_capacity); 14 | } 15 | 16 | public: 17 | IntVector() 18 | : data(nullptr), size(0), capacity(0) {} 19 | 20 | // Explicit destructor is no longer needed due to shared_ptr handling memory 21 | 22 | size_t getSize() const { 23 | return size; 24 | } 25 | 26 | size_t getCapacity() const { 27 | return capacity; 28 | } 29 | 30 | void reserve(size_t new_capacity) { 31 | /* 32 | When std::shared_ptr is reassigned to point to the new array (new_data), the reference count of 33 | the old array is decremented. When the reference count reaches zero, the memory used by the 34 | original array decreases. If the reference count reaches zero, the original array is 35 | automatically deleted using the custom deleter std::default_delete, which properly calls 36 | delete[] to free the memory. 37 | */ 38 | 39 | if (new_capacity > capacity) { 40 | std::shared_ptr new_data(new int[new_capacity], std::default_delete()); 41 | for (size_t i = 0; i < size; i++) { 42 | new_data[i] = data[i]; 43 | } 44 | data = new_data; 45 | capacity = new_capacity; 46 | } 47 | } 48 | 49 | void push_back(const int& value) { 50 | if (size == capacity) { 51 | resize(); 52 | } 53 | data[size] = value; 54 | size++; 55 | } 56 | 57 | void pop_back() { 58 | if (size == 0) { 59 | throw std::out_of_range("Cannot pop_back from an empty vector"); 60 | } 61 | size--; 62 | } 63 | 64 | void insert(size_t index, const int& value) { 65 | if (index > size) { 66 | throw std::out_of_range("Index out of range"); 67 | } 68 | 69 | if (size == capacity) { 70 | resize(); 71 | } 72 | 73 | for (size_t i = size; i > index; i--) { 74 | data[i] = data[i - 1]; 75 | } 76 | 77 | data[index] = value; 78 | size++; 79 | } 80 | 81 | void erase(size_t index) { 82 | if (index >= size) { 83 | throw std::out_of_range("Index out of range"); 84 | } 85 | for (size_t i = index; i < size - 1; i++) { 86 | data[i] = data[i + 1]; 87 | } 88 | size--; 89 | } 90 | 91 | void clear() { 92 | size = 0; 93 | } 94 | 95 | int operator[](size_t index) { 96 | if (index >= size) { 97 | throw std::out_of_range("Index out of range"); 98 | } 99 | return data[index]; 100 | } 101 | 102 | // Enable the copy constructor 103 | IntVector(const IntVector& other) 104 | : data(other.data), size(other.size), capacity(other.capacity) {} 105 | 106 | // Enable the assignment operator 107 | IntVector& operator=(const IntVector& other) { 108 | if (this != &other) { 109 | data = other.data; 110 | size = other.size; 111 | capacity = other.capacity; 112 | } 113 | return *this; 114 | } 115 | }; 116 | 117 | int main() { 118 | IntVector vec; 119 | vec.push_back(0); 120 | vec.push_back(1); 121 | vec.push_back(2); 122 | 123 | std::cout << "Size: " << vec.getSize() << std::endl; 124 | std::cout << "Capacity: " << vec.getCapacity() << std::endl; 125 | 126 | vec.insert(1, 4); 127 | std::cout << "vec: After insert(1,4): "; 128 | for (size_t i = 0; i < vec.getSize(); ++i) { 129 | std::cout << vec[i] << " "; 130 | } 131 | std::cout << std::endl; 132 | 133 | vec.erase(2); 134 | std::cout << "vec: After erase(2): "; 135 | for (size_t i = 0; i < vec.getSize(); ++i) { 136 | std::cout << vec[i] << " "; 137 | } 138 | std::cout << std::endl; 139 | 140 | IntVector vec2 = vec; 141 | std::cout << "vec2: After assignment constructor: "; 142 | for (size_t i = 0; i < vec2.getSize(); ++i) { 143 | std::cout << vec2[i] << " "; 144 | } 145 | std::cout << std::endl; 146 | vec2.erase(1); 147 | 148 | std::cout << "vec after erase(1) on vec2: "; 149 | for (size_t i = 0; i < vec.getSize(); ++i) { 150 | std::cout << vec[i] << " "; 151 | } 152 | 153 | std::cout << std::endl; 154 | 155 | vec.clear(); 156 | std::cout << "After clear, size: " << vec.getSize() << std::endl; 157 | 158 | return 0; 159 | } 160 | -------------------------------------------------------------------------------- /code/Lecture6_7/vector_shallow_copy_improved.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class IntVector { 6 | private: 7 | struct SharedData { 8 | std::shared_ptr data; 9 | size_t size; 10 | size_t capacity; 11 | }; 12 | 13 | std::shared_ptr sharedData; 14 | 15 | void resize() { 16 | size_t new_capacity = sharedData->capacity == 0 ? 1 : sharedData->capacity * 2; 17 | reserve(new_capacity); 18 | } 19 | 20 | public: 21 | IntVector(){ 22 | sharedData = std::make_shared(); 23 | sharedData->data = nullptr; 24 | sharedData->size = 0; 25 | sharedData->capacity = 0; 26 | } 27 | 28 | size_t getSize() const { 29 | return sharedData->size; 30 | } 31 | 32 | size_t getCapacity() const { 33 | return sharedData->capacity; 34 | } 35 | 36 | void reserve(size_t new_capacity) { 37 | if (new_capacity > sharedData->capacity) { 38 | std::shared_ptr new_data(new int[new_capacity], std::default_delete()); 39 | for (size_t i = 0; i < sharedData->size; ++i) { 40 | new_data[i] = sharedData->data[i]; 41 | } 42 | sharedData->data = new_data; 43 | sharedData->capacity = new_capacity; 44 | } 45 | } 46 | 47 | void push_back(const int& value) { 48 | if (sharedData->size == sharedData->capacity) { 49 | resize(); 50 | } 51 | sharedData->data[sharedData->size] = value; 52 | sharedData->size++; 53 | } 54 | 55 | void pop_back() { 56 | if (sharedData->size == 0) { 57 | throw std::out_of_range("Cannot pop_back from an empty vector"); 58 | } 59 | sharedData->size--; 60 | } 61 | 62 | void insert(size_t index, const int& value) { 63 | if (index > sharedData->size) { 64 | throw std::out_of_range("Index out of range"); 65 | } 66 | 67 | if (sharedData->size == sharedData->capacity) { 68 | resize(); 69 | } 70 | 71 | for (size_t i = sharedData->size; i > index; i--) { 72 | sharedData->data[i] = sharedData->data[i - 1]; 73 | } 74 | 75 | sharedData->data[index] = value; 76 | sharedData->size++; 77 | } 78 | 79 | void erase(size_t index) { 80 | if (index >= sharedData->size) { 81 | throw std::out_of_range("Index out of range"); 82 | } 83 | for (size_t i = index; i < sharedData->size - 1; i++) { 84 | sharedData->data[i] = sharedData->data[i + 1]; 85 | } 86 | sharedData->size--; 87 | } 88 | 89 | void clear() { 90 | sharedData->size = 0; 91 | } 92 | 93 | int operator[](size_t index) { 94 | if (index >= sharedData->size) { 95 | throw std::out_of_range("Index out of range"); 96 | } 97 | return sharedData->data[index]; 98 | } 99 | 100 | // Enable the copy constructor 101 | IntVector(const IntVector& other) 102 | : sharedData(other.sharedData) {} 103 | 104 | // Enable the assignment operator 105 | IntVector& operator=(const IntVector& other) { 106 | if (this != &other) { 107 | sharedData = other.sharedData; 108 | } 109 | return *this; 110 | } 111 | }; 112 | 113 | int main() { 114 | IntVector vec; 115 | vec.push_back(0); 116 | vec.push_back(1); 117 | vec.push_back(2); 118 | 119 | std::cout << "Size: " << vec.getSize() << std::endl; 120 | std::cout << "Capacity: " << vec.getCapacity() << std::endl; 121 | 122 | vec.insert(1, 4); 123 | std::cout << "vec: After insert(1,4): "; 124 | for (size_t i = 0; i < vec.getSize(); ++i) { 125 | std::cout << vec[i] << " "; 126 | } 127 | std::cout << std::endl; 128 | 129 | vec.erase(2); 130 | std::cout << "vec: After erase(2): "; 131 | for (size_t i = 0; i < vec.getSize(); ++i) { 132 | std::cout << vec[i] << " "; 133 | } 134 | std::cout << std::endl; 135 | 136 | IntVector vec2 = vec; 137 | std::cout << "vec2: After copy constructor: "; 138 | for (size_t i = 0; i < vec2.getSize(); ++i) { 139 | std::cout << vec2[i] << " "; 140 | } 141 | std::cout << std::endl; 142 | 143 | vec2.erase(1); 144 | 145 | std::cout << "vec after erase(1) on vec2: "; 146 | for (size_t i = 0; i < vec.getSize(); ++i) { 147 | std::cout << vec[i] << " "; 148 | } 149 | 150 | std::cout << std::endl; 151 | 152 | vec.clear(); 153 | std::cout << "After clear, size: " << vec.getSize() << std::endl; 154 | 155 | return 0; 156 | } 157 | -------------------------------------------------------------------------------- /code/Lecture6_7/vector_template.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // to use std::out_of_range 3 | 4 | template 5 | class Vector { 6 | private: 7 | T* data; // Pointer to the first element of the vector 8 | size_t size; // Number of elements in the vector 9 | size_t capacity; // Current allocated capacity of the vector 10 | 11 | void resize() { 12 | size_t new_capacity = capacity == 0? 1: capacity *2; 13 | reserve(new_capacity); 14 | } 15 | 16 | public: 17 | Vector(){ 18 | data = nullptr; 19 | size = 0; 20 | capacity = 0; 21 | } 22 | 23 | ~Vector() { 24 | // deallocate underlying memory 25 | delete[] data; 26 | } 27 | 28 | size_t getSize() const { 29 | // return current size of the vector 30 | return size; 31 | } 32 | 33 | size_t getCapacity() const { 34 | // return current capacity of the vector 35 | return capacity; 36 | } 37 | 38 | void reserve(size_t new_capacity) { 39 | if(new_capacity > capacity) { 40 | T *new_data = new T [new_capacity]; 41 | for(size_t i=0;i size raise exception 70 | if (index >= size) { 71 | throw std::out_of_range("Index out of range"); 72 | } 73 | 74 | if(size==capacity){ 75 | resize(); 76 | } 77 | 78 | size++; 79 | 80 | // shift all elements after index to the right 81 | for(size_t i=size;i>=index;i--){ 82 | data[i] = data[i-1]; 83 | } 84 | 85 | data[index] = value; 86 | // insert value 87 | } 88 | 89 | void erase(size_t index) { 90 | if (index >= size) { 91 | throw std::out_of_range("Index out of range"); 92 | } 93 | // move all elements after index to the left 94 | for(size_t i=index;i= size) { 107 | throw std::out_of_range("Index out of range"); 108 | } 109 | // apply index operator on the underlying data 110 | return data[index]; 111 | } 112 | 113 | // disallow copy constructor and assignment operator 114 | // Vector(const Vector&) = delete; 115 | // Vector& operator=(const Vector&) = delete; 116 | }; 117 | 118 | 119 | int main() { 120 | Vector vec; 121 | vec.push_back(0); 122 | vec.push_back(1); 123 | vec.push_back(2); 124 | 125 | 126 | std::cout << "Size: " << vec.getSize() << std::endl; 127 | std::cout << "Capacity: " << vec.getCapacity() << std::endl; 128 | 129 | vec.insert(1, 4); 130 | std::cout << "After insert(1,4): "; 131 | for (size_t i = 0; i < vec.getSize(); ++i) { 132 | std::cout << vec[i] << " "; 133 | } 134 | std::cout << std::endl; 135 | 136 | vec.erase(2); 137 | std::cout << "After erase(2): "; 138 | for (size_t i = 0; i < vec.getSize(); ++i) { 139 | std::cout << vec[i] << " "; 140 | } 141 | std::cout << std::endl; 142 | 143 | // Vector vec_assign = vec; 144 | // std::cout << "After copy constructor: "; 145 | // for (size_t i = 0; i < vec2.getSize(); ++i) { 146 | // std::cout << vec2[i] << " "; 147 | // } 148 | // std::cout << std::endl; 149 | 150 | vec.clear(); 151 | std::cout << "After clear, size: " << vec.getSize() << std::endl; 152 | 153 | 154 | Vector vec2; 155 | vec2.push_back(0.1); 156 | vec2.push_back(0.2); 157 | vec2.push_back(0.3); 158 | std::cout << "Size: " << vec2.getSize() << std::endl; 159 | std::cout << "Capacity: " << vec2.getCapacity() << std::endl; 160 | return 0; 161 | } 162 | -------------------------------------------------------------------------------- /code/Lecture9/dynamic_array.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | template 5 | class Vector { 6 | private: 7 | struct SharedData { 8 | std::shared_ptr data; 9 | size_t size; 10 | size_t capacity; 11 | }; 12 | 13 | std::shared_ptr sharedData; 14 | 15 | void resize() { 16 | size_t new_capacity = sharedData->capacity == 0 ? 1 : sharedData->capacity * 2; 17 | reserve(new_capacity); 18 | } 19 | 20 | public: 21 | Vector(){ 22 | sharedData = std::make_shared(); 23 | sharedData->data = nullptr; 24 | sharedData->size = 0; 25 | sharedData->capacity = 0; 26 | } 27 | 28 | // initializer list 29 | Vector(std::initializer_list list) { 30 | sharedData = std::make_shared(); 31 | sharedData->data = nullptr; 32 | sharedData->size = 0; 33 | sharedData->capacity = 0; 34 | for (const T& value : list) { 35 | push_back(value); 36 | } 37 | } 38 | 39 | 40 | size_t getSize() const { 41 | return sharedData->size; 42 | } 43 | 44 | size_t getCapacity() const { 45 | return sharedData->capacity; 46 | } 47 | 48 | void reserve(size_t new_capacity) { 49 | if (new_capacity > sharedData->capacity) { 50 | std::shared_ptr new_data(new T[new_capacity], std::default_delete()); 51 | for (size_t i = 0; i < sharedData->size; ++i) { 52 | new_data[i] = sharedData->data[i]; 53 | } 54 | sharedData->data = new_data; 55 | sharedData->capacity = new_capacity; 56 | } 57 | } 58 | 59 | void push_back(const T& value) { 60 | if (sharedData->size == sharedData->capacity) { 61 | resize(); 62 | } 63 | sharedData->data[sharedData->size] = value; 64 | sharedData->size++; 65 | } 66 | 67 | void pop_back() { 68 | if (sharedData->size == 0) { 69 | throw std::out_of_range("Cannot pop_back from an empty vector"); 70 | } 71 | sharedData->size--; 72 | } 73 | 74 | void insert(size_t index, const T& value) { 75 | if (index > sharedData->size) { 76 | throw std::out_of_range("Index out of range"); 77 | } 78 | 79 | if (sharedData->size == sharedData->capacity) { 80 | resize(); 81 | } 82 | 83 | for (size_t i = sharedData->size; i > index; i--) { 84 | sharedData->data[i] = sharedData->data[i - 1]; 85 | } 86 | 87 | sharedData->data[index] = value; 88 | sharedData->size++; 89 | } 90 | 91 | void erase(size_t index) { 92 | if (index >= sharedData->size) { 93 | throw std::out_of_range("Index out of range"); 94 | } 95 | for (size_t i = index; i < sharedData->size - 1; i++) { 96 | sharedData->data[i] = sharedData->data[i + 1]; 97 | } 98 | sharedData->size--; 99 | } 100 | 101 | void clear() { 102 | sharedData->size = 0; 103 | } 104 | 105 | T& operator[](size_t index) { 106 | if (index >= sharedData->size) { 107 | throw std::out_of_range("Index out of range"); 108 | } 109 | return sharedData->data[index]; 110 | } 111 | 112 | // Enable the copy constructor 113 | Vector(const Vector& other) 114 | : sharedData(other.sharedData) {} 115 | 116 | // Enable the assignment operator 117 | Vector& operator=(const Vector& other) { 118 | if (this != &other) { 119 | sharedData = other.sharedData; 120 | } 121 | return *this; 122 | } 123 | 124 | 125 | // iterator related code 126 | class Iterator { 127 | private: 128 | T* ptr; 129 | public: 130 | using iterator_category = std::random_access_iterator_tag; 131 | using difference_type = std::ptrdiff_t; 132 | using value_type = T; 133 | using pointer = T*; 134 | using reference = T&; 135 | 136 | Iterator(T* ptr){ 137 | this->ptr = ptr; 138 | } 139 | 140 | reference operator*() const { 141 | return *ptr; 142 | } 143 | 144 | pointer operator->() { 145 | return ptr; 146 | } 147 | 148 | // Pre-increment 149 | Iterator& operator++() { 150 | ++ptr; 151 | return *this; 152 | } 153 | 154 | // Post-increment 155 | Iterator operator++(int) { 156 | Iterator tmp = *this; 157 | ++(*this); 158 | return tmp; 159 | } 160 | 161 | // Pre-decrement 162 | Iterator& operator--() { 163 | --ptr; 164 | return *this; 165 | } 166 | 167 | // Post-decrement 168 | Iterator operator--(int) { 169 | Iterator tmp = *this; 170 | --(*this); 171 | return tmp; 172 | } 173 | 174 | Iterator operator+(difference_type offset) const { 175 | return Iterator(ptr + offset); 176 | } 177 | 178 | Iterator operator-(difference_type offset) const { 179 | return Iterator(ptr - offset); 180 | } 181 | 182 | difference_type operator-(const Iterator& other) const { 183 | return ptr - other.ptr; 184 | } 185 | 186 | Iterator& operator+=(difference_type offset) { 187 | ptr += offset; 188 | return *this; 189 | } 190 | 191 | Iterator& operator-=(difference_type offset) { 192 | ptr -= offset; 193 | return *this; 194 | } 195 | 196 | reference operator[](difference_type offset) { 197 | return *(ptr + offset); 198 | } 199 | 200 | bool operator==(const Iterator& other) const { 201 | return ptr == other.ptr; 202 | } 203 | 204 | bool operator!=(const Iterator& other) const { 205 | return ptr != other.ptr; 206 | } 207 | 208 | bool operator<(const Iterator& other) const { 209 | return ptr < other.ptr; 210 | } 211 | 212 | bool operator>(const Iterator& other) const { 213 | return ptr > other.ptr; 214 | } 215 | 216 | bool operator<=(const Iterator& other) const { 217 | return ptr <= other.ptr; 218 | } 219 | 220 | bool operator>=(const Iterator& other) const { 221 | return ptr >= other.ptr; 222 | } 223 | }; 224 | 225 | Iterator begin() { 226 | return Iterator(sharedData->data.get()); 227 | } 228 | 229 | Iterator end() { 230 | return Iterator(sharedData->data.get() + sharedData->size); 231 | } 232 | }; 233 | -------------------------------------------------------------------------------- /code/Lecture9/linked_list.h: -------------------------------------------------------------------------------- 1 | template 2 | class ForwardList { 3 | private: 4 | 5 | struct Node { 6 | T data; 7 | Node* next; 8 | 9 | Node(const T& val){ 10 | /* 11 | In C++ the only difference between a class and a struct is that members and base classes 12 | are private by default in classes, whereas they are public by default in structs. 13 | So structs can have constructors, and the syntax is the same as for classes. 14 | */ 15 | data = val; 16 | next = nullptr; 17 | } 18 | }; 19 | 20 | size_t list_size; 21 | 22 | public: 23 | Node* head; 24 | ForwardList(): head(nullptr), list_size(0) {} 25 | 26 | ForwardList(std::initializer_list init){ 27 | /* Why use initializer_list? 28 | If there was no `std::initializer_list`, achieving the same functionality would be more cumbersome and less expressive. Here are some ways things could be achieved: 29 | 1. **Using a constructor with multiple arguments**: Instead of using an `initializer_list`, a constructor could be defined with multiple arguments, one for each element in the list. For example: 30 | ```cpp 31 | ForwardList(T a, T b, T c) { 32 | push_front(a); 33 | push_front(b); 34 | push_front(c); 35 | } 36 | ``` 37 | This approach has several limitations: 38 | 39 | * It's not flexible: the number of elements is fixed, and adding or removing elements would require changing the constructor signature. 40 | * It's not expressive: the code doesn't clearly convey the intention of initializing a list with multiple elements. 41 | 2. **Using a constructor with a `std::vector` or `std::array` argument**: Another approach would be to define a constructor that takes a `std::vector` or `std::array` as an argument. For example: 42 | ```cpp 43 | ForwardList(const std::vector& vec) { 44 | for (const T& value : vec) { 45 | push_front(value); 46 | } 47 | } 48 | ``` 49 | This approach requires creating a temporary `vector` or `array` object, which can be less efficient and more verbose than using an `initializer_list`. 50 | 3. **Using a separate initialization function**: A separate function could be defined to initialize the object with a list of elements. For example: 51 | ```cpp 52 | void initList(T* values, int size) { 53 | for (int i = 0; i < size; i++) { 54 | push_front(values[i]); 55 | } 56 | } 57 | ``` 58 | This approach requires calling a separate function after constructing the object, which can be less convenient and less expressive than using an `initializer_list`. 59 | Overall, the introduction of `std::initializer_list` in C++11 has greatly improved the expressiveness and convenience of initializing objects with a list of values. 60 | */ 61 | head = nullptr; 62 | list_size = 0; 63 | for (const T& value : init) { 64 | push_front(value); 65 | } 66 | } 67 | 68 | // copy constructor 69 | ForwardList(const ForwardList& other) { 70 | head = nullptr; 71 | list_size = 0; 72 | Node* current = other.head; 73 | while (current) { 74 | push_back(current->data); 75 | current = current->next; 76 | } 77 | } 78 | 79 | // assignment operator 80 | ForwardList& operator=(const ForwardList& other) { 81 | if (this != &other) { 82 | clear(); 83 | Node* current = other.head; 84 | while (current) { 85 | push_back(current->data); 86 | current = current->next; 87 | } 88 | } 89 | return *this; 90 | } 91 | 92 | ~ForwardList() { 93 | clear(); 94 | } 95 | 96 | void push_front(const T& value) { 97 | /* 98 | add new node to the beginning of the list 99 | */ 100 | Node* newNode = new Node(value); 101 | newNode->next = head; 102 | head = newNode; 103 | ++list_size; 104 | } 105 | 106 | void pop_front() { 107 | /* 108 | delete the first node 109 | */ 110 | if (head) { 111 | Node* temp = head; 112 | head = head->next; 113 | delete temp; 114 | --list_size; 115 | } 116 | } 117 | 118 | void push_back(const T& value) { 119 | /* 120 | add new node to the end of the list 121 | */ 122 | Node* newNode = new Node(value); 123 | if (!head) { 124 | head = newNode; 125 | } else { 126 | Node* current = head; 127 | while (current->next) { 128 | current = current->next; 129 | } 130 | current->next = newNode; 131 | } 132 | ++list_size; 133 | } 134 | 135 | void pop_back() { 136 | /* 137 | delete the last node 138 | */ 139 | if (head) { 140 | if (!head->next) { 141 | delete head; 142 | head = nullptr; 143 | } else { 144 | Node* current = head; 145 | while (current->next->next) { 146 | current = current->next; 147 | } 148 | delete current->next; 149 | current->next = nullptr; 150 | } 151 | --list_size; 152 | } 153 | } 154 | 155 | 156 | void insert_after(Node* node, const T& value) { 157 | /* 158 | add new node after the given node 159 | */ 160 | Node* newNode = new Node(value); 161 | newNode->next = node->next; 162 | node->next = newNode; 163 | ++list_size; 164 | } 165 | 166 | void erase_after(Node* node) { 167 | /* 168 | delete the node after the given node 169 | */ 170 | if (node->next) { 171 | Node* temp = node->next; 172 | node->next = temp->next; 173 | delete temp; 174 | --list_size; 175 | } 176 | } 177 | 178 | bool empty() const { 179 | return head == nullptr; 180 | } 181 | 182 | size_t size() const { 183 | return list_size; 184 | } 185 | 186 | void clear() { 187 | while (head) { 188 | pop_front(); 189 | } 190 | } 191 | 192 | void print() const { 193 | Node* current = head; 194 | while (current) { 195 | std::cout << current->data << " -> "; 196 | current = current->next; 197 | } 198 | std::cout << "nullptr\n"; 199 | } 200 | 201 | 202 | 203 | // iterator related code // 204 | class Iterator { 205 | // forward iterator implementation 206 | // supports ++, !=, *, 207 | private: 208 | Node* node; 209 | public: 210 | using iterator_category = std::forward_iterator_tag; 211 | using value_type = T; 212 | using pointer = T*; 213 | using reference = T&; 214 | 215 | Iterator(Node* ptr){ 216 | node = ptr; 217 | } 218 | 219 | reference operator*(){ 220 | return node->data; 221 | } 222 | 223 | Iterator& operator++() { 224 | node = node->next; 225 | return *this; 226 | } 227 | 228 | bool operator==(const Iterator& other) const { 229 | return node == other.node; 230 | } 231 | 232 | bool operator!=(const Iterator& other) const { 233 | return node != other.node; 234 | } 235 | 236 | 237 | }; 238 | 239 | Iterator begin() { 240 | return Iterator(head); 241 | } 242 | Iterator end(){ 243 | return Iterator(nullptr); 244 | } 245 | // iterator related code ends// 246 | }; 247 | -------------------------------------------------------------------------------- /code/Lecture9/my_algorithms.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "linked_list.h" 3 | #include "dynamic_array.h" 4 | 5 | using namespace std; 6 | 7 | template 8 | T max_array(Vector arr, size_t size) { 9 | T max = arr[0]; 10 | for (size_t i = 1; i < size; i++) { 11 | if (arr[i] > max) { 12 | max = arr[i]; 13 | } 14 | } 15 | return max; 16 | } 17 | 18 | template 19 | T max_forward_list(ForwardList fl){ 20 | if (fl.empty()) { 21 | return 0; 22 | } 23 | T max = fl.head->data; 24 | auto current = fl.head; 25 | while (current) { 26 | if (current->data > max) { 27 | max = current->data; 28 | } 29 | current = current->next; 30 | } 31 | return max; 32 | 33 | } 34 | 35 | 36 | template 37 | T min_array(Vector arr, size_t size) { 38 | T min = arr[0]; 39 | for (size_t i = 1; i < size; i++) { 40 | if (arr[i] < min) { 41 | min = arr[i]; 42 | } 43 | } 44 | return min; 45 | } 46 | 47 | template 48 | T min_forward_list(ForwardList fl){ 49 | if (fl.empty()) { 50 | return 0; 51 | } 52 | T min = fl.head->data; 53 | auto current = fl.head; 54 | while (current) { 55 | if (current->data < min) { 56 | min = current->data; 57 | } 58 | current = current->next; 59 | } 60 | return min; 61 | 62 | } 63 | 64 | template 65 | T sum_array(Vector arr, size_t size) { 66 | T sum = 0; 67 | for (size_t i = 0; i < size; i++) { 68 | sum += arr[i]; 69 | } 70 | return sum; 71 | } 72 | 73 | template 74 | T sum_forward_list(ForwardList fl){ 75 | if (fl.empty()) { 76 | return 0; 77 | } 78 | T sum = 0; 79 | auto current = fl.head; 80 | while (current) { 81 | sum += current->data; 82 | current = current->next; 83 | } 84 | return sum; 85 | 86 | } 87 | 88 | int main(int argc, char const *argv[]) 89 | { 90 | Vector arr = {1, 2, 3, 4, 5}; 91 | // max 92 | int max_of_darray = max_array(arr, arr.getSize()); 93 | cout << "max_of_darray: "<< max_of_darray << endl; 94 | ForwardList fl = {5, 4, 3, 2, 1}; 95 | int max_of_fl = max_forward_list(fl); 96 | cout << "max_of_fl: "<< max_of_fl << endl; 97 | // min 98 | int min_of_darray = min_array(arr, arr.getSize()); 99 | cout << "min_of_darray: "<< min_of_darray << endl; 100 | int min_of_fl = min_forward_list(fl); 101 | cout << "min_of_fl: "<< min_of_fl << endl; 102 | // sum 103 | int sum_of_darray = sum_array(arr, arr.getSize()); 104 | cout << "sum_of_darray: "<< sum_of_darray << endl; 105 | int sum_of_fl = sum_forward_list(fl); 106 | cout << "sum_of_fl: "<< sum_of_fl << endl; 107 | 108 | return 0; 109 | } 110 | 111 | 112 | -------------------------------------------------------------------------------- /code/Lecture9/my_iterator_algorithms.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "linked_list.h" 4 | #include "dynamic_array.h" 5 | 6 | // Assuming the Vector class is defined as before 7 | 8 | template 9 | typename Iterator::value_type max_element(Iterator begin, Iterator end) { 10 | if (begin == end) { 11 | throw std::invalid_argument("Range cannot be empty"); 12 | } 13 | 14 | auto max_val = *begin; 15 | for (Iterator iter = begin; iter != end; ++iter) { 16 | if (*iter > max_val) { 17 | max_val = *iter; 18 | } 19 | } 20 | return max_val; 21 | } 22 | 23 | // min 24 | template 25 | typename Iterator::value_type min_element(Iterator begin, Iterator end) { 26 | if (begin == end) { 27 | throw std::invalid_argument("Range cannot be empty"); 28 | } 29 | 30 | auto min_val = *begin; 31 | for (Iterator iter = begin; iter != end; ++iter) { 32 | if (*iter < min_val) { 33 | min_val = *iter; 34 | } 35 | } 36 | return min_val; 37 | } 38 | 39 | // sum 40 | template 41 | typename Iterator::value_type sum_element(Iterator begin, Iterator end) { 42 | if (begin == end) { 43 | throw std::invalid_argument("Range cannot be empty"); 44 | } 45 | 46 | auto sum_val = *begin; 47 | for (Iterator iter = begin; iter != end; ++iter) { 48 | sum_val += *iter; 49 | } 50 | return sum_val; 51 | } 52 | 53 | 54 | int main() { 55 | Vector vec = {10, 20, 30, 25, 5}; 56 | ForwardList list = {1, 2, 3, 4, 5}; 57 | // max calls 58 | try { 59 | int max_val = max_element(vec.begin(), vec.end()); 60 | std::cout << "Maximum element is: " << max_val << std::endl; 61 | } catch (const std::invalid_argument& e) { 62 | std::cerr << e.what() << std::endl; 63 | } 64 | 65 | 66 | try { 67 | int max_val = max_element(list.begin(), list.end()); 68 | std::cout << "Maximum element is: " << max_val << std::endl; 69 | } catch (const std::invalid_argument& e) { 70 | std::cerr << e.what() << std::endl; 71 | } 72 | //min calls 73 | try { 74 | int min_val = min_element(vec.begin(), vec.end()); 75 | std::cout << "Minimum element is: " << min_val << std::endl; 76 | } catch (const std::invalid_argument& e) { 77 | std::cerr << e.what() << std::endl; 78 | } 79 | try { 80 | int min_val = min_element(list.begin(), list.end()); 81 | std::cout << "Minimum element is: " << min_val << std::endl; 82 | } catch (const std::invalid_argument& e) { 83 | std::cerr << e.what() << std::endl; 84 | } 85 | 86 | // sum calls 87 | try { 88 | int sum_val = sum_element(vec.begin(), vec.end()); 89 | std::cout << "Sum of elements is: " << sum_val << std::endl; 90 | } catch (const std::invalid_argument& e) { 91 | std::cerr << e.what() << std::endl; 92 | } 93 | try { 94 | int sum_val = sum_element(list.begin(), list.end()); 95 | std::cout << "Sum of elements is: " << sum_val << std::endl; 96 | } catch (const std::invalid_argument& e) { 97 | std::cerr << e.what() << std::endl; 98 | } 99 | 100 | return 0; 101 | } -------------------------------------------------------------------------------- /images/layered_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ankush-Chander/Data-Structure-Lab/22246cda1e8c6540aedaa3f04321aa4650aea629/images/layered_architecture.png -------------------------------------------------------------------------------- /images/modular_diagram.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ankush-Chander/Data-Structure-Lab/22246cda1e8c6540aedaa3f04321aa4650aea629/images/modular_diagram.jpeg -------------------------------------------------------------------------------- /lectures/Lecture1.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - "[[2025-01-02]]" 4 | - "[[2025-01-07]]" 5 | --- 6 | 7 | #### Agenda 8 | - Why study data structures? 9 | - Game plan 10 | - Compilation process(revision) 11 | 12 | --- 13 | #### Todos: 14 | - [ ] Join [classroom](https://classroom.google.com/u/1/c/NzIyNTQ2OTM4OTM2) (code: eskvmjp) 15 | - [ ] Fill introductory survey 16 | --- 17 | 18 | #### Where are we? 19 | ![img](https://img.freepik.com/premium-photo/travel-concept-map-needle-with-marked-place-compass-point-map-routes_494741-63057.jpg?w=1060) 20 | 21 | 22 | --- 23 | #### So far: IT112: Introduction to programming 24 | - Primitive data types (int, char, float etc.) 25 | - Control Structures(for-loop, while-loop, if-else etc.), 26 | - Structured Programming(functions, switch, if-else), 27 | - Arrays, Strings, and Pointers 28 | - Dynamic memory allocation and de-allocation efficiently. 29 | 30 | --- 31 | #### Where we want to go 32 | 33 | - Apply C++ and Object Oriented Programming(OOPs) constructs to implement Data Structures 34 | 35 | 36 | --- 37 | #### Game plan 38 | 39 | - Revise important concepts common to C/C++ 40 | - Understand OOPs concepts 41 | - Classes and Objects 42 | - Encapsulation 43 | - Polymorphism 44 | - Operator Overloading 45 | - Understand C++ concepts crucial for Data structures implementation 46 | - Templates 47 | - Iterators 48 | - Containers 49 | - Follow IT205 50 | --- 51 | #### Why data structures? 52 | - **Efficient Problem-Solving:** Choice of data structures makes certain operations more efficient while others difficult(trade offs). 53 | - **Optimizing Resources:** Using the right data structure can significantly reduce the time and space complexity of a program. 54 | - **Foundations for Algorithms:** Choice of data structures allows or limits the algorithms that can be applied on a problem. 55 | [1](https://kidsmoralstories.blogspot.com/2017/10/the-fox-and-crane-short-story.html) 56 | --- 57 | #### Compilation process 58 | 59 | Programs are translated by other programs into different forms: 60 | ```cpp 61 | // main.cpp 62 | // locate header file using: find /usr/include -name iostream -type f -print 63 | 64 | #include 65 | int main() 66 | { 67 | std::cout << "hello world"<< std::endl; 68 | return 1; 69 | } 70 | ``` 71 | --- 72 | 73 | 1. **Preprocessing phase:** In this phase, the preprocessor is responsible for handling directives such as #include, #define, and #ifdef. It processes these directives and modifies the source code accordingly. It also removes comments and whitespace, and incorporates header files into the source code. 74 | ```cpp 75 | g++ -E main.cpp -o main.i 76 | ``` 77 | --- 78 | 79 | 2. **Compilation phase:** 80 | **preprocessed source code=> assembly language.** 81 | The compiler parses the code, checks it for errors, and translates it into a low-level intermediate representation. This phase also involves optimizations to improve the efficiency of the generated code. 82 | ```cpp 83 | # Compilation 84 | g++ -S main.i -o main.s 85 | ``` 86 | --- 87 | 3. **Assembly phase:** 88 | the assembly code => machine code ([[Object code]]) The assembler converts the symbolic instructions and addresses into binary code that can be understood by the computer's processor. 89 | ```cpp 90 | g++ -c main.s -o main.o 91 | ``` 92 | --- 93 | 94 | 4. **Linking phase:** In this final phase, the linker links all the Object code files generated in the compilation phase and resolves external references. It combines the object code files with any necessary system libraries and generates the final executable file. The linker also performs any necessary relocations and generates a symbol table for the program. 95 | 96 | ```cpp 97 | # linking steo 98 | g++ main.o -o main 99 | ``` 100 | --- 101 | 102 | 103 | #### Compilation steps 104 | ```bash 105 | 106 | # **Preprocessing:** 107 | g++ -E main.cpp -o main.i 108 | 109 | # Compilation 110 | g++ -S main.i -o main.s 111 | 112 | # Assembly 113 | g++ -c main.s -o main.o 114 | 115 | #Linking 116 | g++ main.o -o main 117 | 118 | # Combined 119 | g++ main.cpp -o main 120 | ``` 121 | 122 | --- 123 | 124 | #### Entry point 125 | The entry point of a C++ program is the function where execution starts. This is typically the `main function`. The main function has a special significance and specific rules regarding its declaration, return type, and parameters. 126 | 127 | --- 128 | 129 | **Parameters** 130 | - *argc (Argument Count):* An integer representing the number of command-line arguments passed to the program, including the program's name. 131 | 132 | - *argv (Argument Vector):* An array of C-strings (char*) representing the actual arguments. argv[0] is the name of the program, and argv[1] through argv[argc-1] are the additional arguments. 133 | 134 | --- 135 | **Return Type** 136 | - The return type of main is int, which allows the program to return a status code to the operating system. 137 | - By convention: 138 | return 0; indicates successful execution. 139 | Non-zero values indicate errors or abnormal termination. 140 | 141 | --- 142 | 143 | #### References 144 | 1. CSAPP - Chapter 1 -------------------------------------------------------------------------------- /lectures/Lecture10.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - "[[2025-03-28]]" 4 | - "[[2025-04-01]]" 5 | --- 6 | #### Quick recap 7 | 1. Standard Template Library 8 | --- 9 | #### Agenda 10 | 1. Associative containers 11 | 12 | --- 13 | #### map vs unordered map 14 | | Feature | `std::map` | `std::unordered_map` | 15 | | ------------------- | -------------------------------------- | ----------------------------------------- | 16 | | **Ordering** | Keys are sorted | Keys are not sorted (unordered) | 17 | | **Implementation** | *balanced binary search tree* | *hash tables* | 18 | | **Time Complexity** | O(log n) | O(1) average, (O(n) in worst case) | 19 | | **Traversal** | Elements are traversed in sorted order | Elements are traversed in arbitrary order | 20 | 21 | --- 22 | #### Inner working of `std::unordered_map` 23 | --- 24 | #### Hash tables 25 | ![](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A900%2F1*-BjbJwNd34nP6OYk2TbCdw.png&f=1&nofb=1&ipt=505fa3575cf9574c3917f414f203d27495677b9a07f0e9520c1a0ea0ebb11d52&ipo=images) 26 | --- 27 | **Buckets**: 28 | - The hash table consists of **buckets**, which are essentially slots in an array where key-value pairs are stored. 29 | - Each bucket can store one or more key-value pairs. 30 | - The goal is to distribute key-value pairs evenly among the buckets to minimize the load factor and avoid collisions. 31 | 32 | --- 33 | **Hash Function**: 34 | 35 | - The hash function is a function that takes a key as input and returns an index (hash code) corresponding to the bucket where the key-value pair should be placed. 36 | - In C++, `std::unordered_map` uses the default `std::hash` function, which is defined for common data types (e.g., integers, strings). For custom types, you can provide a custom hash function. 37 | - The hash function needs to be fast, deterministic, and should distribute keys evenly. 38 | --- 39 | **Load Factor**: 40 | - $$load factor= \frac{\text{number of elements in the hash table}}{\text{number of buckets}}$$. 41 | - A high load factor increases the likelihood of collisions, degrading performance. 42 | - A low load factor wastes space because many buckets are unused. 43 | - When the load factor exceeds a certain threshold, `std::unordered_map` automatically resizes (rehashes) the table by increasing the number of buckets to maintain performance. 44 | --- 45 | **Collision Resolution**: 46 | - **Collisions** occur when two different keys are hashed to the same bucket. 47 | - `std::unordered_map` uses **separate chaining** for collision resolution. 48 | - **Separate Chaining**: 49 | - Each bucket contains a linked list (or a similar structure like a dynamic array), and when multiple keys hash to the same bucket, they are appended to the linked list. 50 | - The lookup process will scan the list to find the desired key. 51 | --- 52 | **Rehashing** 53 | - When the load factor exceeds a predefined threshold (often 1.0), the hash table will **rehash**. 54 | - Rehashing involves: 55 | - Creating a new array of buckets 56 | - Recalculating the hash of each key and inserting it into the new bucket array. 57 | - This process is expensive, but it happens infrequently. 58 | --- 59 | #### Managing buckets 60 | 61 | | Code | Description | 62 | | ---------------------- | ----------------------------------------------------- | 63 | | `c.bucket_count()` | Number of buckets in use. | 64 | | `c.max_bucket_count()` | Largest number of buckets this container can hold. | 65 | | `c.bucket_size(n)` | Number of elements in the nth bucket. | 66 | | `c.bucket(k)` | Bucket in which elements with key `k` would be found. | 67 | 68 | --- 69 | #### Requirements on Key Type for Unordered Containers 70 | An object can be a key in an `std::unordered_map` in C++ if it meets two requirements: 71 | 72 | 1. **Hashable**: There must be a way to compute a hash value for the object. By default, `std::unordered_map` uses `std::hash` for primitive types (like `int`, `string`, etc.), but for custom objects, you need to provide a custom hash function. 73 | 74 | 2. **Equality Comparable**: The object must support comparison for equality(`operator==`). 75 | 76 | --- 77 | 78 | #### References 79 | 1. [Chapter 11. Associative Containers | C++ Primer, Fifth Edition](https://cpp-primer.pages.dev/book/106-chapter_11._associative_containers.html) 80 | 2. [How does C++ STL unordered_map resolve collisions? - Stack Overflow](https://stackoverflow.com/a/21519560/2893777) -------------------------------------------------------------------------------- /lectures/Lecture11.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - "[[2025-04-04]]" 4 | --- 5 | #### Quick recap 6 | 1. Associative Containers 7 | 8 | --- 9 | #### Agenda 10 | 1. Problem statement: Given a text from standard input, find out top k most frequently occurring words in it. 11 | --- 12 | #### `std::pair` 13 | 14 | A `pair` holds two data members. Like the containers, `pair` is a template from which we generate specific types. We must supply two type names when we create a `pair`. The data members of the `pair` have the corresponding types. There is no requirement that the two types be the same. 15 | ```c++ 16 | // holds two strings 17 | pair anon; 18 | // holds a string and an size_t 19 | pair word_count; 20 | // create a pair 21 | word_count wc = make_pair("hello", 10); 22 | wc.first // access first element 23 | wc.second // access second element 24 | ``` 25 | 26 | --- 27 | #### Operations on a map 28 | ```c++ 29 | // insertions 30 | word_count.insert(make_pair(word, 1)); 31 | word_count.insert(pair(word, 1)); 32 | 33 | // traverse over all elements of a map 34 | for(auto kv_pair: word_count){ 35 | cout << kv_pair.first << ":" << kv_pair.second << endl; 36 | } 37 | 38 | ``` 39 | --- 40 | Version 1 41 | Refer [word_count.cpp](code/Lecture11/word_count.cpp) 42 | 43 | --- 44 | #### `std::priority_queue` 45 | A `priority_queue` lets us **establish a priority among the elements** held in the queue. Newly added elements are placed ahead of all the elements with a lower priority. 46 | Eg: A restaurant that seats people according to their reservation time, regardless of when they arrive, is an example of a priority queue. 47 | 48 | --- 49 | ![](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.zenalc.com%2Fwp-content%2Fuploads%2F2021%2F01%2FminMaxHeap-1200x620.png&f=1&nofb=1&ipt=c35815d046f2041267707c7f0a6be575c66ec1719cf5b754361539e3112831e6&ipo=images) 50 | 51 | --- 52 | #### Nodes calculation 53 | 1. **Parent of a Node:** For a node at index `i`, the index of its parent is: 54 | 55 | $$Parent(i)=\lfloor{\frac{i−1}{2}}\rfloor$$ 56 | 2. **Left Child of a Node:** For a node at index `i`, the index of its left child is: 57 | 58 | $$ \text{Left Child}(i) = 2i + 1$$ 59 | 60 | 3. **Right Child of a Node:** 61 | For a node at index `i`, the index of its right child is: 62 | 63 | $$ \text{Right Child}(i) = 2i + 2$$ 64 | --- 65 | #### Comparison with Binary search tree 66 | 67 | **Advantages of binary heap over a BST** 68 | 69 | - average time insertion into a binary heap is `O(1)`, for BST is `O(log(n))`. **This** is the killer feature of heaps. 70 | - binary heaps can be efficiently implemented on top of either [dynamic arrays](https://en.wikipedia.org/wiki/Dynamic_array) or pointer-based trees, BST only pointer-based trees. So for the heap we can choose the more space efficient array implementation, if we can afford occasional resize latencies. 71 | - binary heap creation [is `O(n)` worst case](https://en.wikipedia.org/wiki/Binary_heap#Building_a_heap), `O(n log(n))` for BST. 72 | 73 | --- 74 | 75 | **Advantage of BST over binary heap** 76 | 77 | - search for arbitrary elements is `O(log(n))`. **This** is the killer feature of BSTs. 78 | For heap, it is `O(n)` in general, except for the largest element which is `O(1)`. 79 | --- 80 | 81 | | **Operation** | **Type** | **BST (*)** | **Heap** | 82 | | ------------- | -------- | ----------- | -------- | 83 | | **Insert** | Average | log(n) | 1 | 84 | | **Insert** | Worst | log(n) | log(n) | 85 | | **Find any** | Worst | log(n) | n | 86 | | **Find max** | Worst | 1 (**) | 1 | 87 | | **Create** | Worst | n log(n) | n | 88 | | **Delete** | Worst | log(n) | log(n) | 89 | 90 | --- 91 | #### Operations on a priority queue 92 | 93 | #### Initialization 94 | 95 | ```c++ 96 | // Initialization (Max-Heap) 97 | priority_queue pq; 98 | // Initialization (Min-Heap) 99 | priority_queue, greater> pq; 100 | // For user-defined objects, a custom comparator can be defined. 101 | struct compare { 102 | bool operator()(const pair& a, const pair& b) { 103 | // Min-heap: a is greater than b if a.second > b.second 104 | return a.second > b.second; 105 | } 106 | }; 107 | priority_queue, vector>, compare> pq; 108 | //(custom comparator using a `struct` or `lambda`) 109 | ``` 110 | --- 111 | ##### Operations 112 | 113 | ```c++ 114 | // Inserts an element into the priority queue. 115 | pq.push(3); 116 | // Returns the top (largest in max-heap, smallest in min-heap) element without removing it. 117 | int topElement = pq.top(); 118 | // Removes the top element (largest in max-heap, smallest in min-heap). 119 | pq.pop(); 120 | //Returns true if the priority queue is empty, otherwise false. 121 | bool isEmpty = pq.empty(); 122 | // Returns the number of elements in the priority queue. 123 | int size = pq.size(); 124 | ``` 125 | 126 | --- 127 | Refer `../code/Lecture11` 128 | 129 | --- 130 | #### References 131 | 1. [11.2.3. The `pair` Type | C++ Primer, Fifth Edition](https://cpp-primer.pages.dev/book/108-11.2._overview_of_the_associative_containers.html#filepos2758977) 132 | 2. [Priority Queue in C++ Standard Template Library (STL) - GeeksforGeeks](https://www.geeksforgeeks.org/priority-queue-in-cpp-stl) 133 | 3. [std::priority_queue - cppreference.com](https://en.cppreference.com/w/cpp/container/priority_queue) 134 | 4. [algorithm - Heap vs Binary Search Tree (BST) - Stack Overflow](https://stackoverflow.com/a/29548834/2893777 135 | 5. [Containers library - cppreference.com](https://en.cppreference.com/w/cpp/container) -------------------------------------------------------------------------------- /lectures/Lecture2_3.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - "[[2025-01-07]]" 4 | --- 5 | 6 | #### Quick recap 7 | - Compilation Process 8 | --- 9 | #### Agenda 10 | - Layered Architecture 11 | - Programming paradigms 12 | - Procedural vs Modular vs OOP 13 | 14 | 15 | --- 16 | ### Layered architecture 17 | ![](https://github.com/Ankush-Chander/Data-Structure-Lab/raw/lecture2/images/layered_architecture.png) 18 | --- 19 | ### Themes of this course 20 | 21 | | | Themes | Topics | 22 | | --- | ----------------------------------------------- | --------------------------- | 23 | | | **Programming Environment** | Compilation process | 24 | | | **Programming Languages/ Programming concepts** | Programming paradigms, OOPs | 25 | | | **Specific programming language C/C++** | syntax, features | 26 | 27 | --- 28 | #### Programming Paradigms 29 | - Procedural programming 30 | - Modular programming 31 | - Object Oriented Programming 32 | - Generic Programming 33 | --- 34 | ![](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.alpharithms.com%2Fwp-content%2Fuploads%2F557%2Fvector-matrix-addition-illustration.jpg&f=1&nofb=1&ipt=d9c737c2c7a1061b36c52929cde5f9fa9c2832e849ceabaa6a4a8f27559c3b95&ipo=images) 35 | 36 | --- 37 | #### Procedural Programming 38 | 39 | > Decide which procedures you want; 40 | > Use the best algorithms you can find. 41 | 42 | - Language supports this paradigm by providing facilities for passing arguments to functions and returning values from functions. 43 | 44 | Refer code: [[vector2d_struct.c]] 45 | 46 | 47 | --- 48 | #### Scope of improvement(1) 49 | 1. [**namespace pollution**](https://cpp-primer.pages.dev/book/172-18.2._namespaces.html#filepos4928745) Occurs when all the names of classes and functions are placed in the global namespace. Large programs that use code written by multiple independent parties often encounter collisions among names if these names are global. 50 | 2. Separation of concern: User code is not separated from 51 | 52 | --- 53 | #### Modular programming 54 | 55 | > Decide which modules you want; 56 | > partition program so that data is hidden within modules. 57 | 58 | Refer [modular code](../code/Lecture2/modular) 59 | 60 | --- 61 | #### Namespaces 62 | **[Namespaces](https://cpp-primer.pages.dev/book/175-defined_terms.html#filepos5111084)** provide a much more controlled mechanism for preventing name collisions. Namespaces partition the global namespace. A namespace is a scope. By defining a library’s names inside a namespace, library authors (and users) can avoid the limitations inherent in global names. 63 | 64 | 65 | --- 66 | ![](https://github.com/Ankush-Chander/Data-Structure-Lab/raw/lecture2/images/modular_diagram.jpeg) 67 | 68 | --- 69 | #### Multi-file compilation 70 | C++ like C supports C"s notion of separate compilation. This can be used to organise a program into a set of semi-independent fragments. 71 | 72 | --- 73 | #### How does multi compilation works? 74 | 1. **Preprocessor** processes each source file and act upon preprocessor directives(hash directives). (test.cpp => test.i) 75 | 2. **Compiler** takes in the resultant source file and compile it to assembly code. (test.i => test.s) 76 | 3. **Assembler** takes in the assembly file and convert it into a relocatable object code. (test.s => test.o) 77 | 4. **Linker** takes multiple object files and generate a single executable file (test.o => test.out) 78 | 79 | --- 80 | ![](https://github.com/Ankush-Chander/IT603-notes/raw/main/lectures/images/linker.png) 81 | Linker positioned in compilation process. 82 | Pic credits: CSAPP 83 | 84 | --- 85 | ```bash 86 | # preprocessing 87 | g++ -E main.cpp -o main.i 88 | g++ -E vector2D.cpp -o vector2D.i 89 | ## compilation 90 | g++ -S main.i -o main.s 91 | g++ -S vector2D.i -o vector2D.s 92 | # Assembly 93 | g++ -c main.s -o main.o 94 | g++ -c vector2D.s -o vector2D.o 95 | #Linking 96 | g++ main.o vector2D.o -o vector_program 97 | 98 | # or in one step 99 | g++ main.cpp vector2D.cpp -o vector_program 100 | ``` 101 | --- 102 | #### Scope of improvement(2) 103 | 1. Information organisation 104 | 2. Code reuse 105 | --- 106 | #### Object Oriented Programming 107 | Refer [oop code](../code/Lecture2/oop) 108 | 109 | --- 110 | #### Information organization 111 | ![](https://www.scaler.com/topics/images/difference-between-structure-and-class_Thumbnail.webp) 112 | --- 113 | #### Code reuse 114 | 115 | --- 116 | #### References 117 | 1. [Chapter 3: C++ Programming Language - Bjarne Stroustrup]() 118 | -------------------------------------------------------------------------------- /lectures/Lecture3.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - 2025-01-08 4 | --- 5 | #### Quick recap 6 | --- 7 | #### Agenda 8 | 1. Anatomy of a class 9 | 2. Inheritance 10 | 3. Data encapsulation 11 | --- 12 | ### Components of a class 13 | - Constructor/Destructors 14 | - Data members 15 | - Member functions 16 | 17 | Related code: [class_anatomy.py](../code/Lecture3/class_anatomy.py) 18 | 19 | --- 20 | #### Constructor 21 | - Special function called automatically when an object is created. 22 | - Same name as the class. 23 | - Can be overloaded to allow different ways of initializing an object. 24 | --- 25 | 26 | ```cpp 27 | class Vector2D { 28 | // ... 29 | public: 30 | Vector2D(float x, float y){ 31 | this->x = x; 32 | this->y = y; 33 | } 34 | // default constructor 35 | Vector2D(){ 36 | this->x = 0; 37 | this->y = 0; 38 | } 39 | // ... 40 | } 41 | 42 | ``` 43 | 44 | --- 45 | #### Destructor 46 | - Called automatically when an object goes out of scope or is deleted. 47 | - Same name as the class, preceded by `~`. 48 | - Used for cleanup tasks. 49 | --- 50 | ```cpp 51 | class Vector2D { 52 | // ... 53 | public: 54 | // destructor 55 | ~Vector2D(){ 56 | std::cout << "destructor called" << std::endl; 57 | } 58 | // ... 59 | } 60 | ``` 61 | --- 62 | #### Encapsulation 63 | - Wrapping data and methods into a single unit (class). 64 | - Restricting direct access to some components (using access specifiers). 65 | **Access specifiers** 66 | - **public**: Accessible from anywhere. 67 | - **private**: Accessible only within the class. 68 | - **protected**: Accessible within the class and its derived classes. 69 | --- 70 | ```cpp 71 | class Vector2D { 72 | // ... 73 | private: 74 | float x,y; 75 | public: 76 | // member functions 77 | //... 78 | } 79 | ``` 80 | --- 81 | #### Class instantiation 82 | ```cpp 83 | // returns the reference to the object 84 | Vector2D v2(1.0, 2.0); 85 | 86 | // class members access via dot operator 87 | v2.addVectors(v1) 88 | 89 | // using new operator 90 | Vector2D *v3 = new Vector2D(7.0, 8.0); 91 | // returns the pointer to the object 92 | ``` 93 | 94 | --- 95 | #### Inheritance 96 | 97 | - Mechanism to create a new class (subclass) from an existing class (parent class). 98 | - Subclass inherits attributes and methods from the parent class. 99 | Related code: [inheritance.py](../code/Lecture3/inheritance.py) 100 | --- 101 | #### Access Specifiers in Inheritance 102 | - **public**: Public and protected members of the parent remain public and protected in the child. 103 | - **protected**: Public and protected members of the parent become protected in the child. 104 | - **private**: All members of the parent become private in the child. 105 | --- 106 | Programming paradigm 107 | > Decide which classes you want; 108 | > provide a full set of operations for each class. 109 | > make commonality explicit by using inheritance. 110 | --- 111 | ### How to write better classes? 112 | 113 | --- 114 | #### Builtin datatypes vs User defined types 115 | 116 | | Built in datatypes | User defined datatypes | 117 | | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | 118 | | Eg: `int`, `float`, `double`, `char`, `bool`, etc. | Eg: `struct`, `class`, `union`, `enum`, and `typedef`. | 119 | | **Usage**: Built-in datatypes are used to store simple data values | **Usage**: Used when a program needs to model more complex entities with attributes and behaviors | 120 | 121 | --- 122 | #### Operator overloading 123 | **Operator overloading** in C++ is a feature that allows you to define or *redefine the behavior of operators* (such as `+`, `-`, `*`, `=`, etc.) for user-defined types (such as classes or structures). 124 | It enables the use of these operators in a way that is *natural and intuitive* for the objects of the custom types. 125 | 126 | --- 127 | ```c++ 128 | class Vector2D { 129 | private: 130 | float x, y; 131 | public: 132 | Vector2D operator+(Vector2D& v){ 133 | return Vector2D(x + v.x, y + v.y); 134 | } 135 | } 136 | // usage: Vector2D v3 = v1+v2; 137 | ``` 138 | instead of 139 | ```c++ 140 | // instead of 141 | Vector2D addVectors(Vector2D v) const { 142 | return Vector2D(this->x + v.x, this->y + v.y); 143 | } 144 | // usage: Vector2D v3 = v1.addVectors(v2); 145 | ``` 146 | --- 147 | #### The Three Basic Rules of Operator Overloading 148 | 1. _**Whenever the meaning of an operator is not obviously clear and undisputed, it should not be overloaded.**_ _Instead, provide a function with a well-chosen name._ 149 | Basically, the first and foremost rule for overloading operators, at its very heart, says: _Don’t do it_. 150 | 2. _**Always stick to the operator’s well-known semantics.**_ 151 | 3. _**Always provide all out of a set of related operations.**_ 152 | _Operators are related to each other_ and to other operations. If your type supports `a + b`, users will expect to be able to call `a += b`, too. 153 | --- 154 | #### Friend functions 155 | A **friend function** in C++ is a function that is **not a member** of a class but has access to the class's **private and protected members**. 156 | 157 | **Why friend functions?** 158 | - allow external functions to work closely with the internals of a class, often for purposes like operator overloading, accessing private data for computations, or implementing related utility functions. 159 | 160 | --- 161 | ```c++ 162 | class Vector2D { 163 | //... 164 | public: 165 | friend Vector2D operator*(int scalar, Vector2D& vec) { 166 | return vec * scalar; 167 | } 168 | //... 169 | } 170 | // makes the expression 2 * v1 possible 171 | ``` 172 | --- 173 | #### References 174 | 1. [Chapter 16: The C++ Programming Language [4th Edition] - Bjarne Stroustrup](https://github.com/unixzilla/materials/blob/master/The%20C%2B%2B%20Programming%20Language%20%5B4th%20Edition%5D%20-%20Bjarne%20Stroustrup.pdf) -------------------------------------------------------------------------------- /lectures/Lecture4.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - "[[2025-01-09]]" 4 | --- 5 | #### Quick recap 6 | - Anatomy of a class 7 | - Inheritance 8 | - Data encapsulation 9 | --- 10 | #### Agenda 11 | - Dynamic memory allocation 12 | 13 | --- 14 | 15 | Program Memory(revision) 16 | 17 | ![](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcourses.engr.illinois.edu%2Fcs225%2Ffa2023%2Fassets%2Fnotes%2Fstack_heap_memory%2Fmemory_layout.png&f=1&nofb=1&ipt=3e36e7e095de1d200726bbb8659fbf73397af5ae07c5fb33fb7f814903ce8c2d&ipo=images) 18 | --- 19 | ### Object Lifetimes and Storage Locations(1) 20 | 21 | | **Object Type** | **Storage Location** | **Lifetime** | **Example** | 22 | | ------------------ | --------------------------- | --------------------------------------------------- | --------------------------------------------------------------- | 23 | | **Static Objects** | Data segment (Program Code) | Entire program duration | Global variables, static local variables, static class members. | 24 | | **Local Objects** | Stack | Lifetime limited to the scope of the function/block | Local variables declared inside functions or blocks. | 25 | 26 | 27 | --- 28 | ### Object Lifetimes and Storage Locations(2) 29 | 30 | | **Object Type** | **Storage Location** | **Lifetime** | **Example** | 31 | | ------------------- | --------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------- | 32 | | **Global Objects** | Data segment (Program Code) | Entire program duration | Variables declared outside any function (global scope). | 33 | | **Dynamic Objects** | Heap | Manually controlled (allocated with `new` and deallocated with `delete`) | Objects allocated using dynamic memory (`new`/`delete`). | 34 | 35 | --- 36 | #### Related bash commands 37 | ```bash 38 | # return the maximum memory available to program stack(in KB) 39 | ulimit -s 40 | 41 | # return the maximim heap memory available to a program 42 | ulimit -v 43 | 44 | # set maximum heap memory to 2GB 45 | ulimit -v 2097152 46 | ``` 47 | 48 | --- 49 | #### Key operators(1) 50 | 1. **`new` Operator**: Allocates memory on the heap. 51 | 52 | ```c++ 53 | // allocating single variable 54 | int* ptr = new int; // Allocates memory for a single int 55 | *ptr = 10; // Assign value to the allocated memory 56 | ``` 57 | 58 | ```c++ 59 | // allocating an array 60 | int* arr = new int[10]; 61 | // Allocates memory for an array of 10 ints 62 | arr[0] = 1; // Assign value to the first element 63 | ``` 64 | 65 | **`delete` Operator**: Deallocates memory previously allocated using `new`. 66 | ```c++ 67 | delete ptr; // Frees the memory for the single int 68 | delete[] arr; // Frees the memory for the array 69 | 70 | ``` 71 | 72 | --- 73 | #### Example : Dynamic array allocation 74 | ```c++ 75 | #include 76 | using namespace std; 77 | 78 | int main() { 79 | int n; 80 | cout << "Enter size of array: "; 81 | cin >> n; 82 | 83 | // Dynamically allocate memory for an array of size 'n' 84 | int* arr = new int[n]; 85 | 86 | // Initialize and display the array 87 | for (int i = 0; i < n; ++i) { 88 | arr[i] = i + 1; 89 | cout << arr[i] << " "; 90 | } 91 | 92 | // Deallocate the memory 93 | delete[] arr; 94 | 95 | return 0; 96 | } 97 | 98 | ``` 99 | --- 100 | What happens if we do dynamic allocation inside a function? 101 | Stack or heap! 102 | ```c++ 103 | #include 104 | using namespace std; 105 | 106 | int* allocateMemory() { 107 | // Dynamically allocate an integer 108 | int* ptr = new int(10); // Stored in the heap 109 | return ptr; // Return the pointer to the allocated memory 110 | } 111 | 112 | int main() { 113 | int* p = allocateMemory(); // Function returns a pointer to heap memory 114 | cout << *p << endl; // Output: 10 115 | 116 | delete p;// Manually free the memory 117 | return 0; 118 | } 119 | ``` 120 | 121 | --- 122 | #### Why need dynamic memory allocation? 123 | 124 | 1. **Efficient Use of Memory**: Dynamic memory allocation allows you to request exactly the amount of memory needed when you need it, making your program more flexible and efficient. 125 | ```c++ 126 | int n; 127 | cout << "Enter size of array: "; 128 | cin >> n; 129 | int* arr = new int[n]; // Allocates 'n' integers dynamically based on user input 130 | ``` 131 | 132 | 2. **Larger Data Structures:** 133 | The **stack** has limited space, and large objects (such as large arrays or complex data structures) may exceed the stack size, leading to a **stack overflow**. The **heap** provides a much larger memory pool. 134 | Stack has a fixed size available beyond which if program try to use, leads to stack overflow error. 135 | ```bash 136 | ulimit -s # returns 8192KB 137 | ``` 138 | 139 | 3. **Data Persistence Beyond Function Scope**: 140 | Dynamically allocated memory persists even after a function ends, allowing data to live beyond the local scope of the function. This makes it useful for scenarios where you need objects to survive across function calls. 141 | ```c++ 142 | int* createInt() { 143 | int* ptr = new int(100); 144 | // Allocated on the heap 145 | return ptr; 146 | // Memory persists after function returns 147 | } 148 | ``` 149 | 150 | 4. **Complex Data Structures** 151 | **Dynamic memory allocation** is essential for constructing complex data structures such as: 152 | - **Linked Lists**: Dynamic nodes that can be inserted and deleted at runtime. 153 | - **Trees**: Nodes that grow and branch dynamically. 154 | - **Graphs**: Networks of dynamically allocated nodes. 155 | --- 156 | #### Common pitfalls 157 | 1. **Memory Leaks** 158 | A memory leak occurs when dynamically allocated memory is not freed after it's no longer needed, causing the program to consume memory without releasing it. 159 | 160 | ```cpp 161 | int* ptr = new int(10); 162 | // Forgot to free memory with delete 163 | ``` 164 | 165 | 2. **Dangling Pointers** 166 | A dangling pointer arises when memory is deallocated, but the pointer still holds the address of the freed memory. Accessing or modifying a dangling pointer can lead to **undefined behavior** or program crashes. 167 | 168 | ```cpp 169 | int* ptr = new int(10); 170 | delete ptr; // Memory is freed 171 | *ptr = 20; // Dangling pointer: accessing freed memory (undefined behavior) 172 | ``` 173 | 174 | Solution: 175 | Set the pointer to `nullptr` after deleting it to prevent accidental access: 176 | ```cpp 177 | delete ptr; 178 | ptr = nullptr; 179 | ``` 180 | 181 | 3. **Improper Use of `new[]` and `delete[]`** 182 | When allocating arrays dynamically, you need to use `new[]` to allocate and `delete[]` to deallocate. Using `delete` instead of `delete[]` can result in undefined behavior. 183 | 184 | #### Example: 185 | ```cpp 186 | int* arr = new int[10]; // Allocate array 187 | delete arr; // Incorrect deallocation (undefined behavior) 188 | ``` 189 | 190 | #### Solution: 191 | Always use `delete[]` for arrays: 192 | ```cpp 193 | delete[] arr; // Correct deallocation 194 | ``` 195 | 196 | 4. **Mixing `malloc`/`free` with `new`/`delete`** 197 | In C++, `new`/`delete` should be used for dynamic memory allocation and deallocation, whereas `malloc`/`free` come from C. Mixing them can lead to undefined behavior. 198 | #### Example: 199 | ```cpp 200 | int* ptr = (int*)malloc(sizeof(int)); // Using malloc 201 | delete ptr; // Wrong: should use free 202 | ``` 203 | 204 | #### Solution: 205 | Stick to either `new`/`delete` or `malloc`/`free` within the same program. 206 | 207 | --- 208 | #### Smart pointers 209 | Smart pointers reduce the risk of common memory management problems like **memory leaks** and **dangling pointers**. Unlike raw pointers, **smart pointers automatically handle memory deallocation** when an object is no longer in use. 210 | 211 | --- 212 | #### 1. **`std::unique_ptr`**: Ownership-Based Management 213 | 214 | - **Ownership**: `std::unique_ptr` represents **exclusive ownership** of a dynamically allocated object. Only one `unique_ptr` can point to a given object at a time. 215 | 216 | ```c++ 217 | #include 218 | #include 219 | 220 | void uniquePtrExample() { 221 | std::unique_ptr ptr = std::make_unique(10); 222 | // Allocates memory for an int 223 | std::cout << *ptr << std::endl; 224 | // Output: 10 225 | } 226 | // Memory is automatically freed when `ptr` goes out of scope 227 | 228 | ``` 229 | 230 | --- 231 | #### **`std::shared_ptr`**: Reference Counting 232 | 233 | - **Ownership**: `std::shared_ptr` allows **multiple pointers** to share ownership of a dynamically allocated object. It uses **reference counting** to keep track of how many `shared_ptr` instances are pointing to the object. 234 | 235 | ```c++ 236 | #include 237 | #include 238 | void sharedPtrExample() { 239 | std::shared_ptr ptr1 = std::make_shared(20); // Allocates memory for an int 240 | { 241 | std::shared_ptr ptr2 = ptr1; // Shared ownership 242 | std::cout << *ptr2 << std::endl; // Output: 20 243 | } 244 | // ptr2 goes out of scope, but memory is not freed because ptr1 still owns it 245 | std::cout << *ptr1 << std::endl; // Output: 20 246 | } 247 | // Memory is freed when the last shared_ptr (ptr1) goes out of scope 248 | ``` 249 | --- 250 | #### **`std::weak_ptr`**: Preventing Cyclic References 251 | 252 | - **Ownership**: `std::weak_ptr` is used alongside `std::shared_ptr` to **break circular references**. It does not contribute to the reference count, and therefore does not own the object. 253 | - **Memory Deallocation**: A `weak_ptr` does not prevent the memory from being deallocated. It can safely observe the object without extending its lifetime. 254 | --- 255 | ```c++ 256 | #include 257 | #include 258 | 259 | struct Node { 260 | std::shared_ptr next; 261 | std::weak_ptr prev; // Weak pointer prevents circular reference 262 | }; 263 | 264 | void weakPtrExample() { 265 | std::shared_ptr node1 = std::make_shared(); 266 | std::shared_ptr node2 = std::make_shared(); 267 | 268 | node1->next = node2; // Shared ownership 269 | node2->prev = node1; 270 | // Weak ownership (avoids circular reference) 271 | } 272 | ``` 273 | --- 274 | #### References 275 | 1. [Chapter 12. Dynamic Memory | C++ Primer, Fifth Edition](https://cpp-primer.pages.dev/book/113-chapter_12._dynamic_memory.html) -------------------------------------------------------------------------------- /lectures/Lecture5.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - "[[2025-01-09]]" 4 | --- 5 | ### Quick recap 6 | - Dynamic memory allocation 7 | --- 8 | ### Agenda 9 | - Function overloading 10 | - Function templates 11 | --- 12 | ### Function overloading 13 | 14 | - **Definition**: Writing multiple functions with the same name but different parameter types or counts. 15 | 16 | - **Benefits**: 17 | - Simplifies API design by using the same function name for different types. 18 | - Enables the design of functions that operate seamlessly across different data types. 19 | --- 20 | - **Example**: 21 | 22 | ```cpp 23 | 24 | int min(int a, int b){ 25 | return (b < a) ? b : a; 26 | } 27 | 28 | double min(double a, double b){ 29 | return (b < a) ? b : a; 30 | } 31 | 32 | long min(long a, long b){ 33 | return (b < a) ? b : a; 34 | } 35 | ``` 36 | 37 | --- 38 | #### How does function overloading works? 39 | C++ differentiates functions with name and parameter both. 40 | It generates a new name for each function. **The new name depends on the original C++ function name and parameters**. 41 | Given a function name of a set of parameters, it will always generate a unique name. If parameters (number of params, type of params or order of params) change then it will generate another name even if the original C++ function name is same. This process of encoding the function name is known as **name mangling**. 42 | 43 | --- 44 | #### Overload resolution(1) 45 | Overload resolution proceeds through the following steps: 46 | 1. Building the set of [candidate functions](https://en.cppreference.com/w/cpp/language/overload_resolution#Candidate_functions). 47 | 2. Trimming the set to only [viable functions](https://en.cppreference.com/w/cpp/language/overload_resolution#Viable_functions). 48 | 3. Analyzing the set to determine the single [best viable function](https://en.cppreference.com/w/cpp/language/overload_resolution#Best_viable_function) (this may involve [ranking of implicit conversion sequences](https://en.cppreference.com/w/cpp/language/overload_resolution#Ranking_of_implicit_conversion_sequences)). 49 | --- 50 | Each [type of standard conversion sequence](https://en.cppreference.com/w/cpp/language/implicit_conversion "cpp/language/implicit conversion") is assigned one of three ranks: 51 | 52 | 1) **Exact match**: no conversion required, lvalue-to-rvalue conversion, qualification conversion, function pointer conversion,(since C++17) user-defined conversion of class type to the same class 53 | 2) **Promotion**: integral promotion, floating-point promotion 54 | 3) **Conversion**: integral conversion, floating-point conversion, floating-integral conversion, pointer conversion, pointer-to-member conversion, boolean conversion, user-defined conversion of a derived class to its base 55 | 56 | --- 57 | #### 1) Exact Match 58 | - **No Conversion Required**: 59 | ```cpp 60 | int a = 10; 61 | int b = a; // Exact match, no conversion 62 | ``` 63 | 64 | - **lvalue-to-rvalue Conversion**: 65 | ```cpp 66 | int a = 10; 67 | int b = a + 5; // `a` is treated as an rvalue in the expression `a + 5` 68 | ``` 69 | 70 | - **Qualification Conversion**: 71 | ```cpp 72 | const int a = 10; 73 | int b = a; // Removing const (qualification) for compatibility 74 | ``` 75 | 76 | - **Function Pointer Conversion**: 77 | ```cpp 78 | void func(); 79 | void (*fptr)() = func; // Exact match as function pointer types match 80 | ``` 81 | 82 | --- 83 | #### 2) Promotion 84 | 85 | - **Integral Promotion**: Converts smaller integral types (e.g., `char`, `short`) to `int`. For example: 86 | 87 | ```cpp 88 | char c = 'A'; 89 | int i = c; // `char` is promoted to `int` 90 | ``` 91 | 92 | - **Floating-Point Promotion**: Converts `float` to `double`, preserving precision in floating-point arithmetic: 93 | 94 | ```cpp 95 | float f = 3.14f; 96 | double d = f; // `float` is promoted to `double` 97 | ``` 98 | --- 99 | #### 3) Conversion 100 | ```cpp 101 | // Integral Conversion 102 | int a = 65; 103 | char c = a; // `int` is converted to `char` 104 | 105 | // Floating-Point Conversion 106 | double d = 3.14159; 107 | float f = d; // `double` to `float` conversion 108 | 109 | // Floating-Integral Conversion 110 | double d = 3.14; 111 | int i = d; // `double` to `int`, truncates the decimal part 112 | 113 | // Pointer Conversion 114 | int* ptr = nullptr; // nullptr can convert to any pointer type 115 | 116 | ``` 117 | 118 | --- 119 | 120 | ```cpp 121 | // Boolean Conversion 122 | int a = 10; 123 | bool b = a; // Non-zero integer to `true` 124 | 125 | // User-Defined Conversion (Derived to Base) 126 | class Base {}; 127 | class Derived : public Base {}; 128 | 129 | Derived d; 130 | Base* b = &d; // Derived-to-Base conversion 131 | ``` 132 | 133 | --- 134 | #### Drawbacks of function overloading 135 | - Increased Maintenance Overhead 136 | More challenging to maintain the code. It’s easier to make mistakes or overlook updating all versions when modifications are required. 137 | - Potential for Code Bloat 138 | - Harder to debugging and code analysis 139 | --- 140 | #### Comparison with other languages 141 | - C does **not support function overloading** in the way C++ does. Each function must have a unique name, and its parameters must be defined at compile time. 142 | - JavaScript **doesn't have true function overloading** in the way C++ does. When defining multiple functions with the same name, the last definition will overwrite any previous ones. 143 | - Python **doesn't support function overloading** by default, but you can achieve similar functionality using default arguments, `*args`, and `**kwargs`. 144 | 145 | --- 146 | 147 | #### Function templates 148 | A function template is a recipe for generating a parametrized family of functions. 149 | **Example**: 150 | ```cpp 151 | //- Definition of function template min 152 | template 153 | T const& min(T const& a, T const& b) 154 | { 155 | return (b < a) ? b : a; 156 | } 157 | 158 | int i = 42, j = 100; 159 | double d = 3.14, e = 4.6; 160 | auto k = min(i, j); // k = 42 161 | auto f = min(d, e); // f = 3.14 162 | ``` 163 | 164 | --- 165 | #### Definition vs instantiation 166 | **Definition**: 167 | - A generic blueprint specifying a function's operations. 168 | - Occurs once, no memory allocated, no function body compiled. 169 | **Instantiation**: 170 | - The process where the compiler creates a specific version of the function: 171 | - Occurs when the function is first called with specific type arguments. 172 | - Produces a concrete function handling those types alone. 173 | 174 | --- 175 | ##### Examples 176 | ```c++ 177 | // File: "add.hpp" 178 | // Function template definition 179 | template 180 | T add(T a, T b) { 181 | return a + b; 182 | } 183 | ``` 184 | --- 185 | ##### Explicit instantiation 186 | ```c++ 187 | #include "add.hpp" 188 | #include "iostream" 189 | // Explicit instantiation 190 | template int add(int, int); 191 | template double add(double, double); 192 | 193 | int main() { 194 | int resultInt = add(10, 20); 195 | // Uses explicitly instantiated function 196 | double resultDouble = add(5.5, 3.3); 197 | // Uses explicitly instantiated function 198 | cout << "Sum of integers: " << resultInt << endl; 199 | cout << "Sum of doubles: " << resultDouble << endl; 200 | return 0; 201 | } 202 | 203 | ``` 204 | --- 205 | ##### Implicit instantiation 206 | 207 | ```c++ 208 | #include 209 | using namespace std; 210 | // Function template definition 211 | template 212 | T add(T a, T b) { 213 | return a + b; 214 | } 215 | 216 | int resultInt = add(10, 20); 217 | // Implicit instantiation with T as int 218 | int resultInt = add(30, 40); 219 | // Reuse already instantiated function 220 | double resultDouble = add(5.5, 3.3); 221 | // Implicit instantiation with T as double 222 | ``` 223 | --- 224 | #### Type deduction 225 | ```c++ 226 | template 227 | T max (T const& a, T const& b) 228 | { 229 | return b < a ? a : b; 230 | } 231 | int i = 10; 232 | int const c = 42; 233 | max(i, c); // OK: T is deduced as int 234 | max(10, c); // OK: T is deduced as int 235 | int& ir = i; 236 | max(i, ir); // OK: T is deduced as int 237 | max(4, 7.2); // ERROR: T can be deduced as int or double 238 | max(4, 7.2) // OK: 4 is type casted to float 239 | ``` 240 | --- 241 | #### Two way compilation 242 | Templates are “compiled” in two phases: 243 | 1. Without instantiation at definition time, the template code itself is checked for correctness ignoring the template parameters. 244 | - Syntax errors are discovered, such as missing semicolons, unknown names (type names, function names, …) that don’t depend on template parameters are discovered 245 | - Static assertions that don’t depend on template parameters are checked. 246 | 1. At instantiation time, all parts that depend on template parameters are double-checked. 247 | --- 248 | #### Compilation step 249 | When a function template is used in a way that triggers its instantiation, a compiler will (at some point) need to see that template’s definition. This breaks the usual compile and link distinction **for ordinary functions, when the declaration of a function is sufficient to compile its use.** 250 | **Simplest approach:** Implement each template inside a header file. 251 | 252 | --- 253 | #### Multiple template parameters 254 | Function templates have two distinct sets of parameters: 255 | 1. **Template parameters:** are declared in angle brackets before the function. 256 | 2. **Call parameters:** are declared in parentheses after the function template. 257 | ```c++ 258 | template 259 | // T is template parameter 260 | T max (T a, T b) 261 | // a and b are call parameters 262 | ``` 263 | 264 | --- 265 | **Problem statement:** define the max() template for call parameters of two potentially different types 266 | ```c++ 267 | // Method 1: 268 | template 269 | T1 max (T1 a, T2 b){ 270 | return b < a ? a : b; 271 | } 272 | auto m = ::max(4, 7.2); 273 | 274 | // Method 2: 275 | template 276 | RT max (T1 a, T2 b){ 277 | return b < a ? a : b; 278 | } 279 | ::max(4, 7.2); // okay but tedious 280 | ``` 281 | --- 282 | ```cpp 283 | // Method3 284 | template 285 | RT max (T1 a, T2 b); 286 | … 287 | ::max(4, 7.2) 288 | //OK: return type is double, T1 and T2 are deduced 289 | 290 | // method4: let compiler deduce the return type 291 | template 292 | auto max (T1 a, T2 b) 293 | { 294 | return b < a ? a : b; 295 | } 296 | ``` 297 | 298 | ```c++ 299 | // method 5: choose common type as return type 300 | #include 301 | template 302 | std::common_type_t max (T1 a, T2 b){ 303 | return b < a ? a : b; 304 | } 305 | // Thus, both ::max(4, 7.2) and ::max(7.2, 4) 306 | //yield the same value 7.2 of type double. 307 | ``` 308 | --- 309 | #### Overloading function templates 310 | - All other factors being equal, the overload resolution process prefers the non-template over one generated from the template. 311 | 312 | ```cpp 313 | // maximum of two int values: 314 | int max (int a, int b){ 315 | return b < a ? a : b; 316 | } 317 | // maximum of two values of any type: 318 | template 319 | T max (T a, T b){ 320 | return b < a ? a : b; 321 | } 322 | ``` 323 | 324 | 325 | ```cpp 326 | int main(){ 327 | ::max(7, 42); // // calls the nontemplate for two ints 328 | ::max(7.0, 42.0);// // calls max (by argument deduction) 329 | ::max(’a’, ’b’); //calls max (by argument deduction) 330 | ::max<>(7, 42); // calls max (by argument deduction) 331 | ::max(7, 42); // calls max(no argument deduction) 332 | ``` 333 | --- 334 | #### References 335 | 1. [How does a C++ compiler choose which overloaded function to use? - Quora](https://www.quora.com/How-does-a-C-compiler-choose-which-overloaded-function-to-use) 336 | 2. [Chapter 1 - C++ templates - Complete guide](https://github.com/fusying-hwang/books/blob/main/C%2B%2B%20Templates%20The%20Complete%20Guide(2nd).pdf) 337 | -------------------------------------------------------------------------------- /lectures/Lecture6.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: [] 3 | --- 4 | # Agenda 5 | 1. Arrays and multidimensional arrays 6 | 2. Vectors 7 | --- 8 | 9 | An Array is a **fixed size** collection of data of the **same data type**, stored at a **contiguous memory location**. 10 | 11 | Why do we need arrays? 12 | 1. Out of order access 13 | 2. Too many items to be named explicitly. 14 | 15 | 16 | --- 17 | Array definitions 18 | 19 | ```c++ 20 | int x = 10; 21 | // const int x = 10; 22 | int arr[x]; // global array declaration expects a constant expression. 23 | int main(int argc, char const *argv[]) 24 | { 25 | // local scope 26 | int x = 10; 27 | int arr[x]; 28 | return 0; 29 | } 30 | ``` 31 | 32 | --- 33 | #### Explicit Array initialization 34 | ```c++ 35 | const unsigned sz = 3; 36 | int ia1[sz] = {0,1,2}; // array of three ints with values 0, 1, 2 37 | int a2[] = {0, 1, 2}; // an array of dimension 3 38 | int a3[5] = {0, 1, 2}; // equivalent to a3[] = {0, 1, 2, 0, 0} 39 | string a4[3] = {"hi", "bye"}; // same as a4[] = {"hi", "bye", ""} 40 | int a5[2] = {0,1,2}; // error: too many initializers 41 | ``` 42 | 43 | --- 44 | #### Char array initialization 45 | ```c++ 46 | char a1[] = {'C', '+', '+'}; // list initialization, no null 47 | char a2[] = {'C', '+', '+', '\0'}; // list initialization, explicit null 48 | char a3[] = "C++"; // null terminator added automatically 49 | const char a4[6] = "Daniel"; // error: no space for the null! 50 | ``` 51 | --- 52 | 53 | #### Arrays and pointers 54 | ```c++ 55 | int arr[5] = {0,2,3,4,5} 56 | cout << arr; // returns the address of the first element 57 | int *p = arr; // equivalent to int *ip = &arr[0] 58 | cout << arr[1] << " " << *(arr + 1) 59 | // arr[1] is equivalent to *(arr + 1) 60 | cout << *(arr +1) << endl; // prints 2 61 | cout << (*arr +1) << endl; // prints 1 62 | ``` 63 | 64 | Just remember 65 | ``` 66 | arr[i] is equivalent to *(arr + i) 67 | ``` 68 | --- 69 | #### Multidimensional Arrays 70 | ##### Initialization 71 | ```c++ 72 | // definition without initialization 73 | int arr[2][3]; 74 | // explicit initialization 75 | int arr2[2][3] = {{1, 2, 3}, {4, 5, 6}}; 76 | int arr3[][3] = {{1, 2, 3}, {4, 5, 6}};// also valid 77 | int arr4[2][] = {{1, 2, 3}, {4, 5, 6}};// not valid 78 | int arr5[2][3] = {{1,2},{3,4,5}} // valid 79 | // stores 80 | //1 2 0 81 | //3 4 5 82 | int arr6[2][3] = {1,2,3,4} // valid 83 | // stores 84 | //1 2 3 85 | //4 0 0 86 | ``` 87 | --- 88 | ##### Pointer Arithmetic 89 | ```c++ 90 | int arr[2][3] = {{1, 2, 3}, {4, 5, 6}}; 91 | // address of first row 92 | cout << "long(arr)" << long(arr) << endl; 93 | // address of 2nd row 94 | cout << "long(arr + 1): "<< long(arr + 1) << endl; 95 | // address of first element 96 | cout << "long(arr[0])"<< long(arr[0]) << endl; 97 | // address of 2nd element of the first row 98 | cout << "long(arr[0] + 1): " << long(arr[0]+1) << endl; 99 | // first element of the first row 100 | cout << arr[0][0] << endl; 101 | ``` 102 | 103 | --- 104 | 105 | | | 1 | 2 | 3 | 4 | 5 | 106 | | ---- | ------------- | ------------- | ------------- | ------------- | ------------- | 107 | | val | $$arr[0][0]$$ | $$arr[0][1]$$ | $$arr[0][2]$$ | $$arr[1][0]$$ | $$arr[1][1]$$ | 108 | | addr | $$arr$$ | | | $$arr+1$$ | | 109 | | addr | $$arr[0]$$ | $$arr[0]+1$$ | $$arr[0]+2$$ | $$arr[1]$$ | $$arr[1]+1$$ | 110 | 111 | --- 112 | #### Vectors 113 | `std::vector` is a class that provides a **dynamic array** functionality. Unlike static arrays, where the size is fixed at compile-time, vectors can **dynamically resize themselves** as elements are added or removed. 114 | 115 | `std::vector` is defined within the `std` namespace and is included via the `` header file. It encapsulates data and methods that allow for the storage, manipulation, and management of a sequence of elements. 116 | 117 | --- 118 | ##### Initialization 119 | ```c++ 120 | // Default constructor, empty vector 121 | std::vector vec; 122 | 123 | // Explicit assignment 124 | std::vector vec1 = {1, 2, 3, 4}; 125 | 126 | // Initializes a vector with 10 elements (default-initialized) 127 | std::vector vec2(10); 128 | 129 | // Initializes a vector with 10 elements, all set to 5 130 | std::vector vec3(10, 5); 131 | ``` 132 | --- 133 | ##### Capacity Functions 134 | Functions like `size()`, `capacity()`, and `empty()` help manage and understand the vector's size and capacity. 135 | 136 | - `size()` returns the number of elements currently in the vector. 137 | - `capacity()` returns the number of elements the vector can hold before needing to allocate more memory. 138 | - `empty()` returns whether the vector is empty. 139 | --- 140 | 141 | ##### Element Access 142 | 143 | Vectors provide several ways to access elements: 144 | - `operator[]`: Provides direct access using the subscript operator. 145 | - `at()`: Provides bounds-checked access. 146 | - `front()` and `back()`: Access the first and last elements. 147 | - `data()`: Returns a pointer to the underlying array. 148 | 149 | --- 150 | ```c++ 151 | std::vector vec = {1, 2, 3, 4}; 152 | int x = vec[2]; // Access using operator[] 153 | int y = vec.at(2); // Access using at(), throws an exception if out of bounds 154 | int first = vec.front(); 155 | int last = vec.back(); 156 | 157 | ``` 158 | --- 159 | ##### Modifiers 160 | Vectors provide functions to modify their contents: 161 | - `push_back()`: Adds an element to the end of the vector. 162 | - `pop_back()`: Removes the last element. 163 | - `insert()`: Inserts elements at a specific position. 164 | - `erase()`: Removes elements from a specific position or range. 165 | - `clear()`: Removes all elements from the vector. 166 | --- 167 | ```c++ 168 | vec.push_back(5); // Adds 5 to the end 169 | vec.pop_back(); // Removes the last element 170 | vec.insert(vec.begin() + 1, 10); // Inserts 10 at the second position 171 | vec.erase(vec.begin() + 2); // Removes the element at the third position 172 | ``` 173 | --- 174 | ##### **Memory Management in Vectors** 175 | 176 | - **Dynamic Allocation:** Vectors dynamically allocate memory on the heap. The vector manages this memory, ensuring that elements are stored contiguously. 177 | - **Capacity and Reallocation:** When a vector's capacity is exceeded (e.g., when `push_back()` is called), the vector allocates more memory (usually doubling its capacity) and copies the existing elements to the new memory location. 178 | - **Memory Efficiency:** While vectors handle memory management automatically, understanding the concept of capacity helps you optimize performance. 179 | 180 | --- 181 | ##### Reservation Vs Allocation 182 | ```c++ 183 | // reservation 184 | std::vector vec; 185 | const int N = std::stoi(argv[1]); 186 | vec.reserve(N); // Reserve capacity for N elements 187 | for (int i = 0; i < N; ++i) { 188 | vec.push_back(i); // No dynamic resizing needed 189 | } 190 | std::cout << "Final vector capacity: " << vec.capacity() << std::endl; 191 | std::cout << "Final vector size: " << vec.size() << std::endl; 192 | return 0; 193 | ``` 194 | 195 | --- 196 | ```c++ 197 | // reallocation 198 | std::vector vec; 199 | std::cout << "Initial vector size: " << vec.size() << std::endl; 200 | const int N = std::stoi(argv[1]); 201 | for (int i = 0; i < N; ++i) { 202 | vec.push_back(i); // Vector resizes dynamically as needed 203 | } 204 | std::cout << "Final vector capacity: " << vec.capacity() << std::endl; 205 | std::cout << "Final vector size: " << vec.size() << std::endl; 206 | return 0; 207 | ``` 208 | 209 | --- 210 | #### Comparison 211 | ```bash 212 | time ./vector_reservation.out 100000000 213 | #Final vector capacity: 100000000 214 | #Final vector size: 100000000 215 | 216 | #real 0m0.888s 217 | #user 0m0.700s 218 | #sys 0m0.188s 219 | ``` 220 | ```bash 221 | time ./vector_relocation.out 100000000 222 | #Final vector capacity: 134217728 223 | #Final vector size: 100000000 224 | 225 | #real 0m1.190s 226 | #user 0m0.789s 227 | #sys 0m0.401s 228 | ``` 229 | 230 | --- 231 | ### Vector class implementation 232 | Refer [vector.cpp](../code/) 233 | 234 | --- 235 | #### References 236 | 1. [3.5. Arrays | C++ Primer, Fifth Edition](https://cpp-primer.pages.dev/book/034-3.5._arrays.html) 237 | 2. [3.6. Multidimensional Arrays | C++ Primer, Fifth Edition](https://cpp-primer.pages.dev/book/035-3.6._multidimensional_arrays.html) 238 | --- 239 | -------------------------------------------------------------------------------- /lectures/Lecture7.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - "[[2025-01-28]]" 4 | - "[[2025-01-31]]" 5 | --- 6 | ## Quick recap 7 | - Arrays 8 | - Multidimensional arrays 9 | - Dynamic arrays 10 | --- 11 | ## Agenda 12 | - Generic programming 13 | 14 | --- 15 | 16 | Which function is more generic? 17 | 18 | ```cpp 19 | // function 1 20 | T max(T a, T b){ 21 | return a-b>0? a:b; 22 | } 23 | 24 | // function 2 25 | T max(T a, T b){ 26 | return a>b? a:b; 27 | } 28 | 29 | 30 | ``` 31 | 32 | 33 | --- 34 | #### Function templates 35 | 36 | refer [lecture 5](../lectures/Lecture5) 37 | 38 | --- 39 | #### Generic programming 40 | > Decide which algorithms you want; 41 | parameterize them so that they work for a variety of suitable types and data structures. 42 | 43 | --- 44 | #### Class templates 45 | Class templates in C++ are a way to implement generic programming. They allow you to define a class that can **work with multiple data types, by replacing the data type with a template parameter**. 46 | 47 | --- 48 | #### Declaration of class template 49 | ```cpp 50 | template 51 | class Box{ 52 | private: 53 | T value; 54 | public: 55 | // constructor 56 | Box(T val){ 57 | value = val; 58 | } 59 | // getter 60 | T getValue() { 61 | return value; 62 | } 63 | }; 64 | ``` 65 | 66 | #### usage: 67 | ```cpp 68 | Box intBox(5); 69 | intBox.getValue()// returns 5 70 | 71 | Box strBox("Hello"); 72 | strBox.getValue(); // returns "Hello" 73 | ``` 74 | 75 | --- 76 | #### Parameter types 77 | ##### Type parameters 78 | ```c++ 79 | template // parameter is of type typename 80 | class Box { 81 | T value; 82 | public: 83 | Box(T val) : value(val) {} 84 | T getValue() { return value; } 85 | }; 86 | 87 | // Usage: 88 | Box intBox(42); // T is int 89 | Box strBox("Hi"); // T is std::string 90 | 91 | ``` 92 | --- 93 | 94 | ##### Non type parameters 95 | These allow us to pass constant values (like integers or pointers) as parameters to a template. They can be useful for fixed-size data structures, like arrays. 96 | ```c++ 97 | template 98 | class Array{ 99 | T arr[Size]; 100 | public: 101 | T& operator[](int index) { 102 | return arr[index]; 103 | } 104 | }; 105 | 106 | // Usage: 107 | Array intArray; // Size is set to 10 108 | Array doubleArray; // Size is set to 5 109 | ``` 110 | --- 111 | #### Class instantiation 112 | 1. For class templates, only those member functions that are called are instantiated. 113 | 114 | ```c++ 115 | #include 116 | // A simple class template 117 | template 118 | class Calculator { 119 | public: 120 | // Method that adds two numbers 121 | T add(T a, T b) { 122 | return a + b; 123 | } 124 | // Method that subtracts two numbers 125 | T subtract(T a, T b) { 126 | return a - b; 127 | } 128 | // Method that multiplies two numbers 129 | T multiply(T a, T b) { 130 | return a * b; 131 | } 132 | // Method that divides two numbers 133 | T divide(T a, T b) { 134 | return a / b; 135 | } 136 | }; 137 | ``` 138 | 139 | ```c++ 140 | int main() { 141 | 142 | Calculator intCalc; 143 | 144 | // Only 'add' and 'subtract' methods are called 145 | 146 | std::cout << "Addition: " << intCalc.add(5, 3) << std::endl; 147 | 148 | std::cout << "Subtraction: " << intCalc.subtract(5, 3) << std::endl; 149 | 150 | return 0; 151 | } 152 | ``` 153 | --- 154 | #### Compiler Behavior: 155 | 156 | - **Code Instantiation:** When compiling, the C++ compiler analyzes which member functions are being used with the given type `int` and **generates machine code only for these functions** (`add` and `subtract` in this example). 157 | 158 | - **Efficiency:** This selective instantiation helps reduce the binary size and improve the efficiency of the compiled program because unused methods are not included. 159 | --- 160 | Convert `vectorInt` class to vector class template 161 | Refer vector_template.cpp 162 | 163 | --- 164 | 165 | #### References 166 | 1. [Chapter 2 - C++ templates - Complete guide](https://github.com/fusying-hwang/books/blob/main/C%2B%2B%20Templates%20The%20Complete%20Guide(2nd).pdf) 167 | 2. [CppCon2021-back_to_basics_templates_part_1__bob_steagall__cppcon_2021.pdf (optional)](https://github.com/CppCon/CppCon2021/blob/main/Presentations/back_to_basics_templates_part_1__bob_steagall__cppcon_2021.pdf) -------------------------------------------------------------------------------- /lectures/Lecture8.md: -------------------------------------------------------------------------------- 1 | --- 2 | delivery date: 3 | - "[[2025-02-14]]" 4 | - "[[2025-02-18]]" 5 | --- 6 | #### Quick recap 7 | - Arrays 8 | - Multidimensional arrays 9 | - Dynamic arrays 10 | --- 11 | #### Agenda 12 | - Project retrospection 13 | - Copy Constructor and Assignment operator 14 | - Deep copying vs Shallow copying 15 | - Linked lists 16 | - Iterators(intro) 17 | --- 18 | ### Project retrospective 19 | - **Practical experience** > *working with smart peer* > learning by tradition(instructor/youtube/seniors) 20 | - **Builder** > *Helper* > attentive student > Bystander/spectator 21 | --- 22 | #### Key ideas 23 | - Basic video rendering 24 | - State management 25 | - Collision detection 26 | - Non blocking I/O 27 | --- 28 | ### What is Object Copying? 29 | 30 | - Copying occurs when an object is assigned to another or passed by value. 31 | - Two key mechanisms: **Copy Constructor** and **Assignment Operator**. 32 | 33 | ```c++ 34 | Vector2d v1(10,20); # default constructor 35 | Vector2d v2(v1); # copy constructor 36 | Vector2D v2 = v1; 37 | ``` 38 | --- 39 | 40 | ### Copy Constructor 41 | A special constructor that initializes a new object as a copy of an existing one. 42 | 43 | ```cpp 44 | class MyClass { 45 | public: 46 | MyClass(const MyClass &other); // Copy Constructor 47 | }; 48 | // usage 49 | Vector2D negative = get_negative(v1) // returned by value 50 | display(v1)// passed by value 51 | Vector2d v2(v1)// explicit copying 52 | ``` 53 | - **When is it called?** 54 | - When an object is passed by value. 55 | - When an object is returned by value. 56 | - When explicitly initialized using an existing object. 57 | 58 | --- 59 | 60 | ### Assignment Operator 61 | - Used to assign an already initialized object the values of another. 62 | 63 | ```cpp 64 | class MyClass { 65 | public: 66 | MyClass& operator=(const MyClass &other); // Assignment Operator 67 | }; 68 | //usage 69 | Vector2D v2 = Vector2D(0,0) 70 | v2 = v1; 71 | ``` 72 | 73 | - **When is it called?** 74 | - When using `=` to assign an object after initialization. 75 | 76 | --- 77 | 78 | #### Difference Between Copy Constructor & Assignment Operator 79 | 80 | | Feature | Copy Constructor | Assignment Operator | 81 | | ------------------ | ------------------------------ | -------------------------------- | 82 | | Called When? | Object is created | Object already exists | 83 | | Memory Allocation? | New memory is allocated | Uses existing memory | 84 | | Default Provided? | Yes | Yes | 85 | | Common Use Case | Pass-by-value, return-by-value | Copying between existing objects | 86 | 87 | --- 88 | 89 | ### Shallow Copy 90 | 91 | - **What is a Shallow Copy?** 92 | - Copies the pointer but **not** the actual data. 93 | - Both objects share the same memory. 94 | - **Problem:** When the copied object is destroyed, the original object’s data is also lost (double deletion issue). 95 | 96 | --- 97 | ### Deep Copy 98 | - **What is a Deep Copy?** 99 | - Allocates new memory and copies actual data. 100 | - **Example:** 101 | - Fixes double deletion and ensures independent copies. 102 | 103 | --- 104 | ### Singly Linked list 105 | Refer [singly_linked_list.cpp](../code/Lecture8/singly_linked_list.cpp) 106 | 107 | --- 108 | #### References 109 | 1. [std::forward_list - cppreference.com](https://en.cppreference.com/w/cpp/container/forward_list) 110 | -------------------------------------------------------------------------------- /resources/talk_back_to_basics_classic_stl__bob_steagall__cppcon_2021_1_compressed.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ankush-Chander/Data-Structure-Lab/22246cda1e8c6540aedaa3f04321aa4650aea629/resources/talk_back_to_basics_classic_stl__bob_steagall__cppcon_2021_1_compressed.pdf --------------------------------------------------------------------------------