├── .gitignore ├── Additional ├── K1-additional │ ├── clean-rooms.cpp │ ├── count-numbers-in-intervals.cpp │ ├── least-positive.cpp │ └── readme.md └── Theory │ └── readme.md ├── Exam-Solutions ├── task01.cpp ├── task02.cpp └── task03.cpp ├── K2-solutions ├── cocktail-shaker-sort.cpp ├── flip.cpp └── is-palindrome.cpp ├── README.md ├── Sem02 └── README.md ├── Seminar-01 ├── README.md └── media │ └── hello-world.png ├── Seminar-02 └── README.md ├── Seminar-03 ├── README.md ├── euclid.cpp ├── homework01.md └── isPrime.cpp ├── Seminar-04 ├── README.md ├── solutions.cpp └── solutions_hw1.cpp ├── Seminar-05 ├── README.md ├── bulls_and_cows.cpp └── min-and-max-digit.cpp ├── Seminar-06 ├── linearSearch.cpp ├── readme.md └── sort-digits.cpp ├── Seminar-07 ├── binary-search.cpp ├── readme.md ├── reverse.cpp └── sieve_of_eratosthenes.cpp ├── Seminar-08 └── numeric-systems.cpp ├── Seminar-09 ├── readme.md └── tic-tac-toe.cpp ├── Seminar-10 ├── example.cpp ├── getUniqueElement.cpp ├── media │ └── seg-fault.png ├── my-strcpy.cpp ├── mystrlen.cpp ├── printSubsets.cpp └── readme.md ├── Seminar-11 ├── dynamic memory examples │ ├── getLowerAndUpper.cpp │ └── merge.cpp ├── readme.md └── string examples │ ├── count-occurances.cpp │ ├── find-in-text.cpp │ ├── myatoi.cpp │ ├── mystrcat.cpp │ └── mystrcmp.cpp ├── Seminar-12 ├── censor-numbers.cpp ├── readme.md └── sort-lower.cpp ├── Seminar-13 ├── bubble_sort.cpp ├── insertion_sort.cpp ├── selection_sort.cpp ├── split.cpp └── words.cpp └── Seminar-14 ├── binary_search.cpp ├── isPalindrome.cpp └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /Additional/K1-additional/clean-rooms.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | constexpr unsigned MAX_LENGTH = 1024 + 1; 6 | 7 | void getRooms(unsigned rooms[], unsigned size) 8 | { 9 | unsigned idx = 0; 10 | 11 | for (size_t i = 0; i < size; i++) 12 | { 13 | cin >> rooms[i]; 14 | } 15 | } 16 | 17 | void clean(const unsigned rooms[], bool visited[], unsigned idx, unsigned& cleaner, unsigned& maxLongestCycle) 18 | { 19 | ++cleaner; 20 | 21 | unsigned cycleLength = 0; 22 | while (!visited[idx]) 23 | { 24 | visited[idx] = true; 25 | idx = rooms[idx]; 26 | ++cycleLength; 27 | } 28 | 29 | if (cycleLength > maxLongestCycle) 30 | { 31 | maxLongestCycle = cycleLength; 32 | } 33 | } 34 | 35 | void cleanBuilding(unsigned n) 36 | { 37 | unsigned rooms[MAX_LENGTH]; 38 | unsigned cleanersNeeded = 0; 39 | unsigned longestCycle = 0; 40 | 41 | getRooms(rooms, n); 42 | 43 | bool visited[MAX_LENGTH] = { false }; 44 | 45 | for (size_t i = 0; i < n; i++) 46 | { 47 | if (!visited[i]) 48 | { 49 | clean(rooms, visited, i, cleanersNeeded, longestCycle); 50 | } 51 | } 52 | 53 | cout << "Cleaners needed: " << cleanersNeeded << endl << "Time needed: " << longestCycle << " hours" << endl; 54 | } 55 | 56 | int main() 57 | { 58 | unsigned n = 0; 59 | cin >> n; 60 | 61 | cleanBuilding(n); 62 | } -------------------------------------------------------------------------------- /Additional/K1-additional/count-numbers-in-intervals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | constexpr unsigned MAX_LENGTH = 1024 + 1; 6 | 7 | bool isValidInput(unsigned first, unsigned second) 8 | { 9 | return first < MAX_LENGTH && second < MAX_LENGTH && (first <= second); 10 | } 11 | 12 | void readInterval(unsigned& first, unsigned& second) 13 | { 14 | bool failedInput = false; 15 | do 16 | { 17 | if (failedInput) 18 | { 19 | cout << "Invalid input" << endl; 20 | } 21 | 22 | cin >> first >> second; 23 | failedInput = true; 24 | } while (!isValidInput(first, second)); 25 | } 26 | 27 | void mark(unsigned numberOccurance[], unsigned first, unsigned second) 28 | { 29 | for (size_t i = first; i <= second; i++) 30 | { 31 | numberOccurance[i]++; 32 | } 33 | } 34 | 35 | void printSolution(unsigned numberOccurance[]) 36 | { 37 | for (size_t i = 0; i < MAX_LENGTH; i++) 38 | { 39 | if (numberOccurance[i] != 0) 40 | { 41 | cout << i << " " << numberOccurance[i] << endl; 42 | } 43 | } 44 | } 45 | 46 | void solve(unsigned intervalsCount) 47 | { 48 | unsigned first = 0; 49 | unsigned second = 0; 50 | 51 | unsigned numberOccurance[MAX_LENGTH] = { 0 }; 52 | 53 | for (size_t i = 0; i < intervalsCount; i++) 54 | { 55 | readInterval(first, second); 56 | mark(numberOccurance, first, second); 57 | } 58 | 59 | printSolution(numberOccurance); 60 | } 61 | 62 | 63 | int main() 64 | { 65 | unsigned n = 0; 66 | cin >> n; 67 | solve(n); 68 | } -------------------------------------------------------------------------------- /Additional/K1-additional/least-positive.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | constexpr unsigned MAX_LENGTH = 1024 + 1; 6 | 7 | void input(int arr[], int size) { 8 | for (int i = 0; i < size; i++) { 9 | cin >> arr[i]; 10 | } 11 | } 12 | 13 | void mySwap(int& num1, int& num2) { 14 | int sw = num1; 15 | num1 = num2; 16 | num2 = sw; 17 | } 18 | 19 | int removeZeros(int arr[], int size) { 20 | for (int i = 0; i < size; i++) { 21 | while (size != 0 && arr[i] == 0) { 22 | mySwap(arr[i], arr[size - 1]); 23 | size--; 24 | } 25 | } 26 | return size; 27 | } 28 | 29 | bool isValidIndex(int arr[],int idx, int size) { 30 | return arr[idx] >= 0 && arr[idx] < size; 31 | } 32 | 33 | void adjustElemnt(int arr[], int idx, int size) { 34 | while (isValidIndex(arr, idx, size) && arr[idx] - 1 != idx && arr[idx] != arr[arr[idx] - 1]) { 35 | mySwap(arr[idx], arr[arr[idx] - 1]); 36 | } 37 | } 38 | 39 | int leastPositiveMissing(int arr[], int size){ 40 | input(arr, size); 41 | size = removeZeros(arr, size); 42 | 43 | for (int i = 0; i < size; i++) { 44 | adjustElemnt(arr, i, size); 45 | } 46 | 47 | for (int i = 0; i < size; i++) { 48 | if (arr[i] != i + 1) { 49 | return i + 1; 50 | } 51 | } 52 | return size + 1; 53 | } 54 | 55 | int main() 56 | { 57 | unsigned n = 0; 58 | cin >> n; 59 | 60 | int arr[MAX_LENGTH] = {0}; 61 | leastPositiveMissing(arr, n); 62 | } -------------------------------------------------------------------------------- /Additional/K1-additional/readme.md: -------------------------------------------------------------------------------- 1 | # Консултация за контролно 1 2 | 3 | # Задача първа 4 | От стандартния вход се прочита цяло число `n` и `n` интервала от вида $[a_i, b_i]$ където $0 \leq a_i \leq b_i \leq 1024$. 5 | За всяко число в интервала $[0, \dots 1024]$ да се изведе по колко пъти се среща във всеки интервал. Ако числото се среща 0 пъти може да не бъде изведено. 6 | 7 | Пример: 8 | 9 | 10 | 3 11 | 12 | 1 3 13 | 14 | 2 5 15 | 16 | 0 6 17 | 18 | Изход: 19 | 20 | 0 1 21 | 22 | 1 2 23 | 24 | 2 3 25 | 26 | 3 3 27 | 28 | 4 2 29 | 30 | 5 2 31 | 32 | 6 1 33 | 34 | Всяко друго е 0. 35 | 36 | # Задача втора 37 | Даден е масив от цели числа. Да се изведе първото липсващо **положително** число в масива в линейно време и с константна памет. 38 | 39 | Пример: 40 | [-3, 1, 6, 5, -10, 12, 2, 4] 41 | 42 | > 3 43 | 44 | [0, 0, 0] 45 | 46 | > 1 47 | 48 | [-1, 1] 49 | 50 | > 2 51 | 52 | [1, 2, 3, 4, 5, 6] 53 | 54 | > 7 55 | 56 | # Задача трета 57 | Имаме сграда имаща $N \leq 1024$ стаи. Всяка стая е свързана точно с една стая. Няма две стаи, които да са свързани към една и съща стая. 58 | Всяка стая се чисти за 1 час. Броя на стаите и връзките между тях се подават от потребителя. 59 | 60 | * Колко най - малко чистача ще ни трябват да за изчистим цялата сграда. 61 | * За колко време тези чистачи ще изчистят сградата. 62 | 63 | Пример: 64 | 65 | 4 66 | 67 | 0 0 68 | 69 | 1 3 70 | 71 | 2 1 72 | 73 | 3 2 74 | 75 | Answer: 2 3 76 | 77 | Най - малко 2ма чистачи. Сградата ще се изчисти за 3 часа. Обяснение: 78 | Чистач 1 чисти 0 и приключва. Чистач 2 чисти 3 след това отива в 2 и след това отива в 1. От 1 може да се върне в 3, но тя вече е изчистена. 79 | 80 | За улеснение приемаме, че един чистач, приключвайки работата си, не отива да помага на друг а просто си тръгва. -------------------------------------------------------------------------------- /Additional/Theory/readme.md: -------------------------------------------------------------------------------- 1 | # Въпрос 1 2 | Да се напише оператор за цикъл for, който отпечатва същите числа като следния оператор: for (int i = 0; i < 2 * count + 1; i += 2) cout << i;, но в ОБРАТЕН РЕД 3 | 4 | ```cpp 5 | for (int i = 2 * count; i >= 0; i -= 2) 6 | { 7 | std::cout << i; 8 | } 9 | ``` 10 | # Въпрос 2 11 | Какво ще се отпечата при изпълнението на следния код? 12 | ```cpp 13 | dоuble d = 1.25; int i = d + 1.25; i += 0.5; 14 | cоut << i; 15 | ``` 16 | 17 | **Отговор**: 2 18 | 19 | # Въпрос 3 20 | Какво ще отпечата следният код? 21 | ```cpp 22 | for (int i = 1; i <= 4; i++) { 23 | if (i == 2) { 24 | continue; 25 | } 26 | if (i == 4) { 27 | break; 28 | } 29 | for (int j = 1; j <= 3; j++) { 30 | if (j == 2) { 31 | break; 32 | } 33 | cout << i << j; 34 | } 35 | } 36 | ``` 37 | 38 | **Отговор**: 1131 39 | 40 | # Въпрос 4 41 | Какво ще се отпечата при изпълнението на следния код? 42 | ```cpp 43 | int global = 1; 44 | 45 | void f(int a, int *b, int &c) { 46 | a += 4 * global; 47 | *b += 2 * global; 48 | c += global; 49 | } 50 | 51 | int main() { 52 | f(global, &global, global); 53 | cout << global << endl; 54 | return 0; 55 | } 56 | ``` 57 | 58 | **Отговор**: 6 59 | 60 | # Въпрос 5 61 | Какво ще се отпечата при извикване на f(4)? 62 | ```cpp 63 | void f(int x) { 64 | if (x % 2 == 0) { 65 | x /= 2; 66 | f(x); 67 | } 68 | cout << x; 69 | } 70 | ``` 71 | 72 | **Отговор**: 112 73 | 74 | # Въпрос 6 75 | Колко байта заема в паметта следният символен низ? char s[] = "BNN\nB\nB\n"; 76 | 77 | **Отговор**: 9 78 | 79 | # Въпрос 7 80 | Обяснете защо не трябва функция да връща указател към нейна локална променлива 81 | 82 | **Отговор**: Локалните променливи се трият след приключване на функцията. Върнатия указател ще сочи към невалиден адрес. 83 | 84 | # Въпрос 8 85 | Нека оператор 1 е "&&", оператор 2 е "||", оператор 3 е "!", а оператор 4 е двуаргументният "+". Подредете операторите по приоритет в намаляващ ред (в полето за отговор напишете 4-цифреното число, образувано от техните номера, напр. 1234). 86 | 87 | **Отговор**: 3412 88 | 89 | # Въпрос 9 90 | Кой от двата цикъла извежда правилно дължината на символния низ? 91 | ```cpp 92 | int main() { 93 | char text[] = "statistics"; 94 | 95 | int index = 0; 96 | 97 | for (; text[index]; ++index); 98 | cout << index; 99 | 100 | index = 0; 101 | 102 | while (text[index]); 103 | cout << index; 104 | 105 | return 0; 106 | } 107 | ``` 108 | 109 | **Отговор**: for цикъла 110 | 111 | # Въпрос 10 112 | Имаме следната функция: vоid f(int a) { f(а); а--; }. Какво не е наред? 113 | 114 | **Отговор**: Това е безкрайна рекурсия. 115 | 116 | # Въпрос 11 117 | Да разгледаме програмен фрагмент: int variable = 15;. Какво е "15"? 118 | 119 | **Отговор**: целочислен литерал. 120 | 121 | # Въпрос 12 122 | Кое от следните твърдения НЕ е вярно? 123 | 124 | 1. a = (x > 0 ? c : 0); е еквивалентно на a = c; if (x <= 0) a -= a; 125 | 126 | 2. (x == 0 ? b : c) е еквивалентно на (x ? c : b), където x е израз от тип int 127 | 128 | 3. A && B е еквивалентно на A ? true : B 129 | 130 | 4. a = (x > 5 ? c : d); е еквивалентно на a = c; if (x <= 5) a = d; 131 | 132 | **Отговор**: 3 133 | 134 | # Въпрос 13 135 | В кой от следните програмни фрагменти има грешка при работата с масиви? 136 | 137 | 138 | 1. Всички фрагменти са коректни 139 | 140 | 2. 141 | ```cpp 142 | long numbers[] = {4, 3}; 143 | for (int i = 0; i <= 2; i++) numbers[i % 2]++; 144 | ``` 145 | 146 | 3. 147 | ```cpp 148 | long numbers[6] = {4, 9, 0}; 149 | ``` 150 | 151 | 4. 152 | ```cpp 153 | long a,b[10]; 154 | a[4] = 0; 155 | b[7] = 8; 156 | ``` 157 | **Отговор**: 4 158 | 159 | # Въпрос 14 160 | Кое от следните твърдения НЕ е вярно в C++? 161 | 162 | 1. На delete НЕ може да бъде подаден адрес в стековата памет 163 | 164 | 2. Динамична памет може да бъде заделена и освободена по всяко време на изпълнение на програмата 165 | 166 | 3. При извикване на функция се създава нова стекова рамка. При приключването на изпълнението й тази памет се освобождава 167 | 168 | 4. Памет, заделена с оператор new, се освобождава автоматично, когато не е необходима 169 | 170 | **Отговор**: 4 171 | 172 | # Въпрос 15 173 | Кое от следните твърдения НЕ е вярно? 174 | 175 | 1. В следния код: 176 | ```cpp 177 | if (a == 0) X; else if (b == 1 && a != 0) Y; 178 | ``` 179 | има излишна проверка 180 | 181 | 2. if (P) A; е еквивалентно на if (!P) {} else A; 182 | 183 | 3. В тялото на оператор if можем да напишем for цикъл 184 | 185 | 4. if (number++ == 1) something(); else if (number++ == 2) somethingElse(); е еквивалентно на същия код, но без else 186 | 187 | **Отговор**: 4 188 | 189 | # Въпрос 16 190 | Кое от следните твърдения за многомерните масиви е вярно? 191 | 192 | 1. double f[2, 3] е двумерен масив с общо 6 елемента 193 | 194 | 2. Многомерните масиви се представят в паметта като едномерни и можем да декларираме например 10-мерен масив 195 | 196 | 3. Следната декларация е коректна: unsigned int m[][7]; 197 | 198 | 4. При многомерните масиви елементите в различните измерения могат да бъдат от различен тип (напр. int и double) 199 | 200 | **Отговор**: 2 201 | 202 | # Въпрос 17 203 | Кое от следните твърдения за функции в C++ НЕ е вярно? 204 | 205 | 1. Името на една функция не може да започва с цифра 206 | 207 | 2. Ако дефинираме подходящи функции, големите програми могат да бъдат лесно четими 208 | 209 | 3. Една функция може да извиква други функции и дори себе си 210 | 211 | 4. Ако дадена функция няма параметри, при извикването й не е необходимо да поставяме кръгли скоби след името й 212 | 213 | **Отговор**: 4 214 | 215 | # Въпрос 18 216 | Нека array е масив от тип double с 6 елемента, а i e int. Кое от следните твърдения е вярно? 217 | 218 | 1. &array[0] е еквивалентно на !array 219 | 220 | 2. array[i] е еквивалентно на *(i + array) 221 | 222 | 3. array[4] - array[3] има стойност sizeof(double) 223 | 224 | 4. &array[i] е еквивалентно на array[*i] 225 | 226 | **Отговор**: 2 227 | 228 | # Въпрос 19 229 | В кой от следните примери има неявно преобразуване на тип? 230 | 231 | 1. long a, b; cin >> a; b = a; 232 | 233 | 2. double d = 4.7; return (int)d; 234 | 235 | 3. int b = 4 > 5; 236 | 237 | 4. cout << (char)65; 238 | 239 | **Отговор**: 3 240 | 241 | # Въпрос 20 242 | Кое от следните твърдения за структурите в C++ е вярно? 243 | 244 | 1. Полетата на една структура трябва да бъдат от различен тип 245 | 246 | 2. Ако имаме структура Fraction и функция f(Fraction ratio), функцията ще получи копие на подадената й стойност 247 | 248 | 3. Нека имаме структура struct Timе { int hours, minutеs; }; и функция vоid midnight(Time time) { timе.hours = time.minutes = 0; }. Следният код ще отпечата 0: Time t = {9, 25}; midnight(t); cout << t.hours; 249 | 250 | 4. Ако p е указател към запис, а f е поле на този запис, то p->f е еквивалентно на *(p.f) 251 | 252 | **Отговор**: 2 253 | 254 | # Въпрос 21 255 | Кое от следните твърдения е вярно? 256 | 257 | 1. Компонентите на for(инициализация; условие; корекция) тяло се изпълняват в следния ред: инициализация, условие, корекция, тяло, условие, корекция, тяло и т.н. 258 | 259 | 2. Условието в един for цикъл се проверява преди всяка итерация и ако не е вярно, тялото не се изпълнява 260 | 261 | 3. За разлика от do-while, при while тялото се изпълнява най-малко един път 262 | 263 | 4. Тялото на един for цикъл не може да съдържа други оператори for 264 | 265 | **Отговор**: 2 266 | 267 | # Въпрос 22 268 | Кое от следните твърдения е вярно за C++? 269 | 270 | 271 | 1. Глобалните променливи се декларират в глобални функции и са достъпни и извън тях 272 | 273 | 2. Променливи, декларирани в тялото на do-while цикъл, могат да бъдат използвани в условието в while 274 | 275 | 3. int fact; for (int i = 1; i <= 6; i++) fact *= i; пресмята коректно факториел от 6 276 | 277 | 4. В рамките на един блок не можем да имаме променливи с еднакви имена, но в различни блокове - можем, дори и единият блок да е вложен в другия 278 | 279 | **Отговор**: 4 280 | 281 | # Въпрос 23 282 | Кое от следните твърдения е вярно? 283 | 284 | 1. За да отпечатаме стойностите на масив int a[10], използваме cout << a; 285 | 286 | 2. Масив с 8 елемента от тип bool заема един байт памет 287 | 288 | 3. При дефиниране на едномерен масив може да не указваме броя елементи, стига да сме задали начални стойности за елементите на масива 289 | 290 | 4. Елементите на масива int a[10] могат да бъдат от произволен числов тип (double, long и т.н.), стига да съдържат най-много 10 цифри 291 | 292 | **Отговор**: 3 293 | 294 | # Въпрос 24 295 | Кое от следните твърдения е вярно? 296 | 297 | 1. Ако на даден указател не укажем явно стойност, то той автоматично получава стойност NULL 298 | 299 | 2. Следният програмен фрагмент е синтактично коректен: int* a, A; a = &A; A = *a = !14; 300 | 301 | 3. С float* p1, p2; декларираме два указателя от тип float 302 | 303 | 4. int a = 4; int *p = &a; a++; cout << p; отпечатва сочената от p стойност, а именно 5 304 | 305 | **Отговор**: 2 306 | 307 | # Въпрос 25 308 | Кой от следните фрагменти НЕ е еквивалентен на следния оператор: 309 | ```cpp 310 | switch (number) { 311 | case 1: 312 | case 2: сout << 3; 313 | default: cout << 4; 314 | } 315 | ``` 316 | 317 | 1. 318 | ```cpp 319 | switch (number) { 320 | default: cout << 4; break; 321 | casе 1: 322 | casе 2: сout << 3; cout << 4; 323 | } 324 | ``` 325 | 326 | 2. 327 | ```cpp 328 | switсh (number) { 329 | case 1: 330 | case 2: сout << 3; cout << 4; break; 331 | default: cout << 4; brеak; 332 | } 333 | ``` 334 | 3. 335 | ```cpp 336 | switch (number) { 337 | case 1: 338 | case 2: сout << 3; break; 339 | default: cout << 4; break; 340 | } 341 | ``` 342 | 343 | 4. 344 | ```cpp 345 | switch (number) { 346 | case 1: cout << 34; breаk; 347 | case 2: сout << 34; brеak; 348 | default: cout << 4; break; 349 | } 350 | ``` 351 | 352 | **Отговор**: 3 353 | 354 | # Въпрос 26 355 | Възможно ли е `main` да е рекурсивна функция? 356 | 357 | **Отговор**: да 358 | 359 | # Въпрос 27 360 | Какво ще отпечата следния код? 361 | ```cpp 362 | #include 363 | 364 | int mystery(int n) { 365 | if (n == 0) return 0; 366 | if (n % 2 == 1) return 1 + mystery(n - 1); 367 | return 2 * mystery(n / 2); 368 | } 369 | 370 | int main() { 371 | std::cout << mystery(17); 372 | } 373 | ``` 374 | 375 | **Отговор**: 17 -------------------------------------------------------------------------------- /Exam-Solutions/task01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned countDigitOccurences(unsigned n, int digit) 4 | { 5 | if(n == 0 && digit == 0) 6 | return 1; 7 | 8 | unsigned count = 0; 9 | while(n != 0) 10 | { 11 | if(n % 10 == digit) 12 | count++; 13 | n /= 10; 14 | } 15 | return count; 16 | } 17 | bool infixOfPermutation(unsigned N, unsigned K) 18 | { 19 | for(int i = 0; i <= 9; i++) 20 | if(countDigitOccurences(N, i) < countDigitOccurences(K, i)) 21 | return false; 22 | return true; 23 | } 24 | 25 | int main() 26 | { 27 | unsigned first = 12364; 28 | unsigned second = 30; 29 | std::cout << infixOfPermutation(first, second); 30 | } -------------------------------------------------------------------------------- /Exam-Solutions/task02.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool isDigit(char ch) 4 | { 5 | return ch >= '0' && ch <= '9'; 6 | } 7 | 8 | unsigned lognesrNumber(const char* str, unsigned currentNumLen = 0, unsigned maxNumLen = 0) 9 | { 10 | maxNumLen = std::max(maxNumLen, currentNumLen); 11 | if(!*str) 12 | return maxNumLen; 13 | if(isDigit(*str)) 14 | currentNumLen++; 15 | else 16 | currentNumLen = 0; 17 | 18 | return lognesrNumber(str + 1, currentNumLen, maxNumLen); 19 | } 20 | 21 | int main() 22 | { 23 | std::cout << lognesrNumber("abc123edsfds3fsd1"); 24 | } -------------------------------------------------------------------------------- /Exam-Solutions/task03.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned countDigits(unsigned number) 4 | { 5 | if (number == 0) 6 | return 1; 7 | unsigned count = 0; 8 | while (number) 9 | { 10 | ++count; 11 | number /= 10; 12 | } 13 | return count; 14 | } 15 | 16 | char* numberToString(unsigned number) 17 | { 18 | unsigned digitsCount = countDigits(number); 19 | char* toReturn = new char[digitsCount + 1]; 20 | 21 | toReturn[digitsCount] = '\0'; 22 | 23 | while (digitsCount > 0) 24 | { 25 | unsigned lastDigit = number % 10; 26 | number /= 10; 27 | 28 | toReturn[digitsCount - 1] = lastDigit + '0'; 29 | --digitsCount; 30 | } 31 | 32 | return toReturn; 33 | } 34 | 35 | char* readNumberAsString() 36 | { 37 | unsigned currentNumber = 0; 38 | std::cin >> currentNumber; 39 | return numberToString(currentNumber); 40 | } 41 | 42 | int myStrcmp(const char* first, const char* second) 43 | { 44 | if (!first || !second) 45 | return 0; 46 | 47 | while ((*first) && (*second) && ((*first) == (*second))) //acutally the (*second) check can be missed here. 48 | { 49 | first++; 50 | second++; 51 | } 52 | 53 | return (*first - *second); 54 | } 55 | 56 | void swap(char*& fst, char*& snd) 57 | { 58 | char* temp = fst; 59 | fst = snd; 60 | snd = temp; 61 | } 62 | 63 | void sortStrings(char** words, size_t size) 64 | { 65 | for (size_t i = 0; i < size; i++) 66 | { 67 | size_t minIndex = i; 68 | for (size_t j = i + 1; j < size; j++) 69 | { 70 | if (myStrcmp(words[j], words[minIndex]) < 0) 71 | minIndex = j; 72 | } 73 | 74 | if (minIndex != i) 75 | swap(words[i], words[minIndex]); 76 | } 77 | } 78 | 79 | void freeWords(char** words, size_t size) 80 | { 81 | for (size_t i = 0; i < size; i++) 82 | delete[] words[i]; 83 | delete[] words; 84 | } 85 | 86 | int main() 87 | { 88 | size_t numbersCount = 0; 89 | std::cin >> numbersCount; 90 | 91 | char** stringRepresentedNumbers = new char* [numbersCount]; 92 | 93 | for (size_t i = 0; i < numbersCount; i++) { 94 | stringRepresentedNumbers[i] = readNumberAsString(); 95 | } 96 | 97 | sortStrings(stringRepresentedNumbers, numbersCount); 98 | 99 | for (size_t i = 0; i < numbersCount; i++) { 100 | std::cout << stringRepresentedNumbers[i] << std::endl; 101 | } 102 | 103 | freeWords(stringRepresentedNumbers, numbersCount); 104 | } -------------------------------------------------------------------------------- /K2-solutions/cocktail-shaker-sort.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | void swap(int& a, int& b) 5 | { 6 | int temp = a; 7 | a = b; 8 | b = temp; 9 | } 10 | 11 | void cocktailShakerSort(int* arr, size_t length) 12 | { 13 | int lowerSwappedIndex = 0; 14 | int upperSwappedIndex = length - 1; 15 | 16 | unsigned iterationsNeeded = length / 2; 17 | 18 | for (size_t i = 0; i < iterationsNeeded; i++) 19 | { 20 | int tempLower = lowerSwappedIndex; 21 | int tempUpper = upperSwappedIndex; 22 | 23 | for (size_t j = tempLower; j < tempUpper; j++) 24 | { 25 | if (arr[j] > arr[j + 1]) 26 | { 27 | swap(arr[j], arr[j + 1]); 28 | upperSwappedIndex = j; 29 | } 30 | } 31 | 32 | if (tempUpper == upperSwappedIndex) 33 | { 34 | break; 35 | } 36 | 37 | for (int j = tempUpper; j > tempLower; j--) 38 | { 39 | if (arr[j] < arr[j - 1]) 40 | { 41 | swap(arr[j], arr[j - 1]); 42 | lowerSwappedIndex = j; 43 | } 44 | } 45 | 46 | if (tempLower == lowerSwappedIndex) 47 | { 48 | break; 49 | } 50 | } 51 | } 52 | 53 | int main() 54 | { 55 | int v[] = {0, 4, 3, 5, 2, 6, 9, 8, 7, 11}; 56 | 57 | cocktailShakerSort(v, 10); 58 | 59 | for (size_t i = 0; i < 10; i++) 60 | { 61 | std::cout << v[i] << " "; 62 | } 63 | } -------------------------------------------------------------------------------- /K2-solutions/flip.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned flipNumber(unsigned number) 4 | { 5 | return (number ^ 2); 6 | } 7 | 8 | int main() 9 | { 10 | std::cout << flipNumber(0) << std::endl; 11 | std::cout << flipNumber(1) << std::endl; 12 | std::cout << flipNumber(2) << std::endl; 13 | std::cout << flipNumber(3) << std::endl; 14 | std::cout << flipNumber(4) << std::endl; 15 | } -------------------------------------------------------------------------------- /K2-solutions/is-palindrome.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void countSymbols(const char* text, unsigned histogram[26]) 4 | { 5 | while (*text != '\0') 6 | { 7 | histogram[(*text - 'a')]++; 8 | ++text; 9 | } 10 | } 11 | 12 | void buildPalindrome(char* palindrome, char symbol, unsigned occurances, unsigned stringLenght, unsigned& currentPos) 13 | { 14 | while (occurances > 1) 15 | { 16 | palindrome[currentPos] = palindrome[stringLenght - currentPos - 1] = symbol; 17 | occurances -= 2; 18 | currentPos++; 19 | } 20 | 21 | // corner case for odd symbol 22 | if (occurances == 1) 23 | { 24 | palindrome[stringLenght / 2] = symbol; 25 | } 26 | } 27 | 28 | char* isPalindrome(const char* firstString, const char* secondString) 29 | { 30 | unsigned histogram[26] = {}; 31 | countSymbols(firstString, histogram); 32 | countSymbols(secondString, histogram); 33 | 34 | bool seenOdd = false; 35 | unsigned resultStringLength = 0; 36 | 37 | for (size_t i = 0; i < 26; i++) 38 | { 39 | resultStringLength += histogram[i]; 40 | 41 | if (histogram[i] % 2 == 1) 42 | { 43 | if (seenOdd) 44 | { 45 | return nullptr; 46 | } 47 | seenOdd = true; 48 | } 49 | } 50 | 51 | char* palindrome = new char[resultStringLength + 1]; 52 | unsigned currentPos = 0; 53 | 54 | for (size_t i = 0; i < 26; i++) 55 | { 56 | buildPalindrome(palindrome, i + 'a', histogram[i], resultStringLength, currentPos); 57 | } 58 | palindrome[resultStringLength] = '\0'; 59 | return palindrome; 60 | } 61 | 62 | int main() 63 | { 64 | char* result = isPalindrome("hello", "eh"); 65 | 66 | if(result == nullptr) 67 | { 68 | std::cout << "No palindrome can be formed"; 69 | } 70 | else 71 | { 72 | std::cout << result; 73 | delete[] result; 74 | } 75 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Материали за курса "Увод в програмирането" 2023/2024. 2 | 3 | 1. Въведение и представяне на курса. Променливи и примитивни типове. Преобразуване между типовете. Оператори. 4 | 2. Продължение на оператори. Условни конструкции. 5 | 3. Тернарен оператор. while, do-while, for цикли. Примери с задачата за намиране дали число е просто и алгоритъм на Евклид за намиране на най - голям общ делител. 6 | 4. Функции. 7 | 5. Функции - втора част. 8 | 6. Масиви. -------------------------------------------------------------------------------- /Sem02/README.md: -------------------------------------------------------------------------------- 1 | # 1. Категории 2 | Израз наричаме последователност от оператори, променливи и литерални константи (фиксирани стойности като 5, 'a', 14.00 и тн). Примерно: 3 | ```c 4 | int main() 5 | { 6 | int a = 5; 7 | 8 | a; // Това е израз 9 | 5; // Това е израз 10 | a = 7; // Това е израз 11 | a = a + 9 * 2; // Това е израз 12 | } 13 | ``` 14 | Всеки израз има категория. Категориите, за които ще говорим, са две: 15 | * lvalue 16 | * Нарича се така, понеже може да стои от лявата страна на оператор =. 17 | * За нея има заделена физическа памет. 18 | * rvalue 19 | * Нарича се така, понеже стои от дясната страна на оператор =. 20 | * За нея няма заделена физическа памет. 21 | 22 | Примери: 23 | ```c 24 | int main() 25 | { 26 | int a; 27 | // Тук a е lvalue. 28 | // докато стойността 5 е rvalue. 29 | a = 5; 30 | 31 | // Това е невалиден израз 32 | 5 = a; 33 | // Това също 34 | 6 = 7; 35 | } 36 | ``` 37 | 38 | Каква е категорията на израза `a = 5`? Категорията е `rvalue`. Изразът присвоява стойността 5 на променливата `a` и връща стойността 5. Това е причината изрази от този тип да са валидни: 39 | ```c 40 | a = (b = 10); 41 | ``` 42 | а изрази от този тип да не са валидни 43 | ```c 44 | (a = b) = 10; 45 | ``` 46 | # 2. Оператори за присвояване – присвояват стойност на дадена променлива 47 | |Оператор|Описание| 48 | |--|--| 49 | |=|Присвояване| 50 | |+=|Прибавяне и присвояване| 51 | |-=|Изваждане и присвояване| 52 | |*=|Умножение и присвояване| 53 | |/=|Деление и присвояване| 54 | |%=|Деление по модул и присвояване| 55 | 56 | Кой от следните три израза е валиден? Защо? 57 | 58 | ```c 59 | int main() 60 | { 61 | int a = 0; 62 | int b = 0; 63 | 64 | (a += 10) = 15; 65 | a = (10 += 15); 66 | a = (b += 10); 67 | 68 | // Какво ще изведе този сред след като 69 | // валидният израз се изпълни? 70 | printf("%d, %d, %d\n", a, b, (a == b)); 71 | } 72 | ``` 73 | 74 | # 3. Преговор на логически оператори. Мързеливо оценяване. 75 | |Оператор| 76 | |--| 77 | | && (логическо "И") | 78 | | \|\| (логическо "ИЛИ") | 79 | | ! (логическо отрицание) | 80 | 81 | # 4. Условни конструкции - if, if-else, if-else if 82 | Много често е удобно, ако някое условие е изпълнено (нещо се случи), да се изпълни един код, а ако то не е изпълнено, да се изпълни друг. 83 | Примерно, нека разгледаме следната 84 | ### Задача 85 | Потребител въвежда от стандартния вход цяло число. Ако то е положително, се извежда числото, умножено по 10. Ако е отрицателно, се извежда съобщение за грешка. 86 | Вече знаем как да проверим дали едно число е положително или не. Би било удобно да можем да кажем на езика: 87 | ``` 88 | Ако (a < 0), изведи a * 10, в противен случай изведи грешка. 89 | ``` 90 | 91 | Такива конструкции съжествуват във всеки език за програмиране и се наричат условни конструкции. Синтаксисът е: 92 | ```cpp 93 | if(<условие>) 94 | { 95 | <инструкции> 96 | } 97 | ``` 98 | Можем да имаме също какво да се изпълни, ако условието не е истина: 99 | 100 | ```cpp 101 | if(<условие>) // Условието е израз, който може да се преобразува до тип bool 102 | { 103 | // Изпълни нещо 104 | } 105 | else 106 | { 107 | // Изпълни друго нещо 108 | } 109 | ``` 110 | 111 | Нека разгледаме как ще изглежда решението на задачата: 112 | 113 | ```cpp 114 | #include 115 | 116 | int main() 117 | { 118 | int number = 0; 119 | 120 | printf("Enter your number: "); 121 | scanf("%d", &number); 122 | 123 | if(number < 0) 124 | { 125 | printf("The number must be positive!\n"); 126 | } 127 | else 128 | { 129 | printf("%d\n", number * 10); 130 | } 131 | } 132 | ``` 133 | 134 | ### Разглеждане на повече от едно условие 135 | Нека усложним задачата. Нека сега се въвежда число, ако числото е между 2 и 6, да се извежда оценката с думи, която студентът е получил. В противен случай да се изведе съобщение за грешка. Това можем да го постигнем със следната конструкция: 136 | ```cpp 137 | if(<условие>) 138 | { 139 | // направи нещо 140 | } 141 | else if(<условие1>) 142 | { 143 | // направи друго нещо 144 | } 145 | else if(<условие2>) 146 | { 147 | // направи трето нещо 148 | } 149 | ... // Можем да имаме колкото искаме else if блока 150 | ``` 151 | 152 | Решението на задачата би изглеждало по следния начин: 153 | 154 | ```c 155 | #include 156 | 157 | int main() 158 | { 159 | int number = 0; 160 | 161 | scanf("%d", &number); 162 | 163 | if(number == 2) 164 | { 165 | printf("Poor\n"); 166 | } 167 | else if(number == 3) 168 | { 169 | printf("Satisfactory\n"); 170 | } 171 | else if(number == 4) 172 | { 173 | printf("Good\n"); 174 | } 175 | else if(number == 5) 176 | { 177 | printf("Very good\n"); 178 | } 179 | else if(number == 6) 180 | { 181 | printf("Excellent\n"); 182 | } 183 | else 184 | { 185 | printf("Invalid input\n"); 186 | } 187 | } 188 | ``` 189 | ## Кога пишем if-elseif и кога избираме да напишем няколко if конструкции 190 | 191 | **Задача:** Да се прочете число и да се изведе на стандартния изход дали числото е по-голямо, равно или по-малко от 0. 192 | ```c 193 | #include 194 | 195 | int main() 196 | { 197 | int x = 0; 198 | scanf("%d", &x); 199 | // Първи начин - if-elseif 200 | if(x > 0) 201 | { 202 | printf("Entered number is bigger than zero\n"); 203 | } 204 | else if(x == 0) 205 | { 206 | printf("Entered number is zero\n"); 207 | } 208 | else 209 | { 210 | printf("Entered number is less than zero.\n"); 211 | } 212 | 213 | // Втори начин - три if конструкции 214 | if(x > 0) 215 | { 216 | printf("Entered number is bigger than zero\n"); 217 | } 218 | if(x == 0) 219 | { 220 | printf("Entered number is zero\n"); 221 | } 222 | if(x < 0) 223 | { 224 | printf("Entered number is less than zero.\n"); 225 | } 226 | } 227 | ``` 228 | В този случай е по-добре да използваме if-elseif. Ако влезем в първия случай (х > 0), проверката дали х е нула няма да се извърши. Когато използваме три if-a без значение кой от тях е истина, **другите два отново ще се проверят.** Чрез първата конструкция **спестяваме проверки.** 229 | 230 | Първата конструкция се използва, когато имаме **взаимно изключващи се случаи.** Примерно, ако х е по-голямо от нула, то то не е равно на нула и също така не е по-малко от нула. Тези случаи са взаимно изключващи се. 231 | 232 | Ако имаме две условия, които не са взаимно изключващи се, то тогава **може да се наложи да използваме две if конструкции.** 233 | 234 | ```c 235 | #include 236 | 237 | int main() 238 | { 239 | unsigned hours = 0; 240 | unsigned minutes = 0; 241 | 242 | scanf("%d %d", &hours, &ninutes); 243 | 244 | int validHours = (hours < 24); 245 | int validMinutes = (minutes < 60); 246 | 247 | if(!validHours) 248 | { 249 | printf("You entered invalid hours\n"); 250 | } 251 | 252 | if(!validMinutes) 253 | { 254 | printf("You entered invalid minutes\n"); 255 | } 256 | 257 | if(validHours && validMinutes) 258 | { 259 | printf("All good!\n"); 260 | } 261 | } 262 | ``` 263 | В този пример може часовете да са валидни, но минутите да не са. Това не са взаимно изключващи се случаи и трябва да се проверяват с две if конструкции. 264 | # 5. Тернатен оператор 265 | Синтаксис: 266 | ``` <условие> ? <изпълнява се ако е истина> : <изпълнява се ако е лъжа>; ``` 267 | 268 | # 6. Конструкцията switch 269 | Конструкцията ```switch``` приема някаква променлива и в зависимост от стойността ѝ изпълнява някакъв код. Еквивалентно на конструкцията if-elseif, но различен синтаксис. 270 | 271 | ```c 272 | #include 273 | 274 | int main() 275 | { 276 | int x = 0; 277 | 278 | scanf("%d", &x); 279 | 280 | switch (x) // Случаите са по стойността на х 281 | { 282 | case 2: printf("Poor\n"); break; // break се слага, за да спред изпълнението на кода. Какво става 283 | case 3: printf("Satisfactory\n"); break; // aко нямаме break? 284 | case 4: printf("Good\n"); break; 285 | case 5: printf("Very good\n"); break; 286 | case 6: printf("Excellent\n"); break; 287 | default: printf("Invalid input\n"); 288 | } 289 | } 290 | ``` 291 | 292 | В повечето случаи, когато имаме повече условия, switch e по-бърз, поради хитра имплементация на ниско ниво. Важното обаче е случаите, изброени в case, да са известни по време на компилация. 293 | 294 | # 7. Задачи 295 | ## 7.1 296 | От конзолата се въвеждат ден от седмицата под формата на цяло число от 1 до 7 (1- Понеделник, 2 - Вторник , и т.н.). Нека на конзолата се изведе кой ден от седмицата е това число с думи. 297 | 298 | Примерен вход : 3 299 | 300 | Изход : Wednesday 301 | 302 | ## 7.2 303 | От конзолата се въвеждат дължините на трите страни на триъгълник (реални положителни числа). Нека програмата извежда подходящо съобщение на конзолата, ако тези страни са валидни и ако не са. (Входът не е гарантирано коректен. Да се извежда различно съобщение и за него) 304 | 305 | Примерен вход : 4.5 7 5 306 | 307 | Изход : This triangle exists. 308 | 309 | Примерен вход : 1 50 100 310 | 311 | Изход : This triangle does not exist. 312 | 313 | ## 7.3 314 | От конзолата се въвеждат три реално числа. Първото е ляв край на интервал, а второто десният му край. Да се провери дали третото число се съдържа в интервала и ако съвпада с единия му край да се изведе съобщение, че интервалът е затворен. Ако числото е в интервала да се изведе съобщение, че е в интервалът и интервалът е отворен. Ако не се намира в интервала да се изведе подходящо съобщение. 315 | 316 | Примерен вход : 3.90 15.2 3.91 317 | 318 | Изход : The number is within the given interval. The interval is open. 319 | 320 | ## 7.4 321 | Програмата чете число в интервала [0..12]. Ако числото е четно програмата принтира всички четни числа по - големи или равни на числото намиращи се в интервала. Ако е нечетно принтираме всички по - големи или равни нечетни числа. 322 | 323 | Пример: 324 | 325 | * Вход: 1 326 | 327 | * Изход: 1 3 5 7 9 11 328 | 329 | * Вход: 4 330 | 331 | * Изход: 4 6 8 10 12 332 | -------------------------------------------------------------------------------- /Seminar-01/README.md: -------------------------------------------------------------------------------- 1 | # Първи семинар по увод в програмирането - 03.10.2023 2 | 3 | ## Кратък увод. 4 | Компютрите могат да извършват множество от задачи. Разбира се, те не извършва задачи на случаен принцип, а някой трябва да им "обясни" какво да направят. 5 | Готовият софтуер е в някакъв смисъл точно това - множество от инструкции, определящи какво да извърши компютъра. Използвайки готов софтуер, ние знаем каква е нашата цел и някой друг се е погрижил тази цел да се постигне бързо и ефикасно. 6 | 7 | Но как създаваме ние софтуер? Можем ли да пишем наши инструкции към компютъра? Можем, но никой не пише инструкциите директно. Компютърният език е лесен за разбиране от компютъра, но е нечетим за нас хората - точно както човешкия език в чистата си форма е неразбираем за компютъра. 8 | 9 | За да създаваме наши програми, съществуват междинни езици - езици за програмиране. Те са четими от хора. Същевременно съществуват програми, които превръщат езиците за програмиране в разбираеми за машината инструкции - наши собствени програми. 10 | 11 | Integrated development environment (IDE или среда за разработка) е такъв софтуер, който ни помага от език за програмиране да създадем наша програма! Тя идва с нужните инструменти, които да ни помагат при разработката, поддръжката и търсенето на грешки в кода ни. 12 | 13 | **Езикът за програмиране, който ще използваме за целите на курса, е С++** 14 | 15 | **Средата за разработка която ще използваме за курса е Microsoft Visual Studio** 16 | ## Hello world. 17 | Нека напишем първата си програма. 18 | ```cpp 19 | #include 20 | 21 | using namespace std; 22 | 23 | int main() 24 | { 25 | cout << "Hello world!" << endl; 26 | } 27 | ``` 28 | Когато стартираме програмата, виждаме следното: 29 | ![](media/hello-world.png) 30 | Изпълнението започва от кода в тялото на main(). Тя се приема като начална точка на програмата. 31 | 32 | Използвайки cout, изведохме съобщението "Hello world!" на конзолата. 33 | endl (от end line) принтира нов ред на конзолата. 34 | 35 | 36 | ## Променливи и примитивни типове. 37 | В програмите си можем да имаме **променливи**. Променливите се използват, за да пазят някаква информация по време на изпълнението на нашата програма. В езика С++ променливите си имат типове. Променливите декларираме, следвайки синтаксиса: 38 | ``` 39 | <тип> <име> = <стойност>; 40 | ``` 41 | 42 | ### int 43 | Пази цяло число. Пример: 44 | ```cpp 45 | #include 46 | 47 | using namespace std; 48 | 49 | int main() 50 | { 51 | int number = 145; 52 | 53 | cout << "The value of number is" << number << endl; 54 | } 55 | ``` 56 | Програмата ще изведе стойността, записана в number (в случая 145). 57 | Стойността на типа int може да варира между 58 | 59 | ### char 60 | Пази символ. По същия начин както създадохме променлива от тип int, можем да създадем такава от тип char. 61 | 62 | ```cpp 63 | #include 64 | 65 | using namespace std; 66 | 67 | int main() 68 | { 69 | char ch = 'a'; 70 | 71 | cout << "The value of ch is: " << ch << endl; 72 | } 73 | ``` 74 | 75 | ### double 76 | Използват се за числа с плаваща запетая. 77 | ```cpp 78 | #include 79 | 80 | using namespace std; 81 | 82 | int main() 83 | { 84 | double PI = 3.14; 85 | 86 | cout << "The value of variable PI is: " << PI << endl; 87 | } 88 | ``` 89 | 90 | ### unsigned int 91 | Типът int може да съхранява и отрицателни стойности. Ако желаем в променливата да имаме записани само положителни стойности, използваме unsigned int. 92 | 93 | ### bool 94 | Типът bool има точно две стойности - истина (1) или лъжа (0). 95 | 96 | Езикът предлага още типове, които ще разглеждаме в следващи семинари. 97 | 98 | ## Въвеждане на стойности на променливи от конзолата 99 | Можем да въвеждаме стойността на променлива от конзолата по време на изпълнение на програмата: 100 | 101 | ```cpp 102 | #include 103 | 104 | using namespace std; 105 | 106 | int main() 107 | { 108 | int value = 0; 109 | 110 | cin >> value; 111 | 112 | cout << "You entered: " << value << endl; 113 | } 114 | ``` 115 | 116 | ## Преобразуване между типове 117 | Можем да присвояваме стойността на един тип на друг. Това присвояване става имплицитно (скрито от нас). 118 | Преобразуването обаче крие рискове от загуба на информация. Ако на променлива от тип bool дадем стойност променлива от тип int в общия случай имаме загуба на информация. 119 | 120 | ```cpp 121 | int main() 122 | { 123 | int x = 145; 124 | 125 | // Ако стойността на x е 0, стойността на p e false. Във всички други случаи стойността е true. 126 | bool p = x; // Получава се загуба на информация, p има стойност true, но първоначалната стойност на x се губи. 127 | } 128 | ``` 129 | 130 | Друг пример за преобразуване със загуба на информация е, когато на променлива от тип int дадем стойност на променлива от тип double. 131 | 132 | ```cpp 133 | int main() 134 | { 135 | double x = 14.5; 136 | 137 | int y = x; // сега х има стойност 14 138 | } 139 | ``` 140 | Стойността на y сега е 14 и губим информация от променливата x. 141 | 142 | Можем да имаме преобразуване и без загуба на информация. Например ако преобразуваме от bool към int или от int към double, информацията не се губи. 143 | 144 | ## Оператори 145 | Можем да манипулираме информацията, записана в променливите. Това става с помощта на операторите. 146 | 147 | ### Аритметични оператори 148 | | Оператор | Описание | 149 | | ---------| ---------------- | 150 | | + | Събиране | 151 | | - | Изваждане | 152 | | * | Умножение | 153 | | / | Деление | 154 | | % | Деление с остатък| 155 | 156 | Оператора за делене с остатък не се дефинира за double. 157 | ```cpp 158 | int main() 159 | { 160 | int x = 10; 161 | int y = 3; 162 | 163 | int z = x % y; // Стойността на z e остатъка при деление на x с y, а именно 1. 164 | int d = x / y; // Стойността на d e 3. Дробната част от делението се игнорира. 165 | } 166 | ``` 167 | 168 | **Важно** ако в горния пример y беше 0, това би довело до грешка. При целочислени променливи не можем да делим на 0. Типът double го позволява. 169 | 170 | ### Оператори за сравнение 171 | Можем да сравняваме дали две променливи са равни по стойност с оператора **==**. Те или са равни или не са и този оператор връща булев резултат. 172 | ```cpp 173 | int main() 174 | { 175 | int x = 10; 176 | int y = 10; 177 | 178 | bool areVariablesEqual = (x == y); // В случая стойността на декларираната променлива е true. 179 | } 180 | ``` 181 | 182 | **Важно:** Променливи от тип double не се сравняват с оператора ==. Има други методи, които ще бъдат разгледани скоро. 183 | 184 | Езикът ни предоставя пълния набор от оператори за сравнение. 185 | | Оператор | Описание | 186 | | ---------| ---------------------------------------------------------------------------- | 187 | | == | Равни ли са двете променливи | 188 | | != | Различни ли са двете променливи | 189 | | < | Променливата от лявата страна по-голяма ли е от тази от дясната | 190 | | > | Променливата от лявата страна по-голяма ли е от тази от дясната | 191 | 192 | Също така, за улеснение, имаме операторите >= и <=. 193 | 194 | ```cpp 195 | int main() 196 | { 197 | int x = 10; 198 | int y = 20; 199 | 200 | int first = (x > y); // Каква е стойността на first? 201 | int second = x * first; // Каква е стойността на second? 202 | } 203 | ``` 204 | 205 | ## Логически оператори 206 | Операторите && и || представляват конюнкция и дизюнкция. Те приемат две булеви променливи и връщат булев резултат. 207 | 208 | Оператора && връща истина когато и двата аргумента са истина: 209 | 210 | | Стойност | Стойност| Резултат | 211 | | ---------| ------ | --------- | 212 | | true | true | true | 213 | | true | false | false | 214 | | false | true | false | 215 | | false | false | false | 216 | 217 | 218 | Оператора || връща истина когато и поне един от двата аргумента е истина: 219 | 220 | | Стойност | Стойност| Резултат | 221 | | ---------| ------ | --------- | 222 | | true | true | true | 223 | | true | false | true | 224 | | false | true | true | 225 | | false | false | false | 226 | 227 | Оператора ! представлява негация. Той приема една булева променлива и връща булев резултат. Ако стойността на променливата е true, той ще върне false и ако е false, ще върне true. 228 | 229 | ```cpp 230 | int main() 231 | { 232 | bool first = true; 233 | bool second = false; 234 | bool third = true; 235 | 236 | bool x = first && third; // true 237 | bool y = first && second; // false 238 | 239 | bool z = (first && third) || (first && second); // Каква е стойността? 240 | 241 | bool p = !first; // false 242 | 243 | int example = 13; 244 | bool k = !example; // Каква е стойността? 245 | bool t = !!example; // Каква е стойността? 246 | } 247 | ``` 248 | -------------------------------------------------------------------------------- /Seminar-01/media/hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Introduction-to-programming/e9bfa9d3381bbc9444d5679d2303e6de7d551ac3/Seminar-01/media/hello-world.png -------------------------------------------------------------------------------- /Seminar-02/README.md: -------------------------------------------------------------------------------- 1 | # Втори семинар по увод в програмирането - 09.10.2023 2 | 3 | ## Продължение на оператори 4 | 5 | ### Изрази в С++ 6 | Използвайки операторите, променливите и константите в С++, можем да създаваме **изрази**. Примерно: 7 | ```cpp 8 | #include 9 | 10 | using namespace std; 11 | 12 | int main() 13 | { 14 | int x = 0; // Инициализация на променлива, не е израз 15 | int y = 3; 16 | 17 | y = y + 3; // Валиден израз. Участват оператор =, +, променливата y и константата 3. 18 | x = y = 3; // Валиден израз. 19 | 20 | 1 = x; // Синтактично невалиден израз. 21 | } 22 | ``` 23 | 24 | Изразите в С++ се оценяват. Последния семинар видяхме, че изрази от типа (x < y) се оценяват до булева стойност. 25 | 26 | ```cpp 27 | int main() 28 | { 29 | int x = 0; 30 | int y = 0; 31 | 32 | x = (5 + 7); // Пример 1: Изразът (5 + 7) връща стойност 12, което е и новата стойност на х 33 | 34 | x = y = 3; // Пример 2: Стойността на х и y стават 3. 35 | } 36 | ``` 37 | 38 | Темата за това как се оценяват изразите, е голяма, но можем да кажем, че израз може да се оцени до **променлива** или **стойност**. 39 | 40 | * Изразът в пример 1 се оценява до стойност. Тази стойност служи за нова стойност на х. 41 | 42 | * Подизразът (y = 3) в пример 2 ще се оцени до променлива (ще се върне променливата y). Стойността на променливата y ще послужи за нова стойност на х. 43 | 44 | Как можем да видим това нагледно. 45 | 46 | ```cpp 47 | int main() 48 | { 49 | int x = 0; 50 | 51 | (x = 7) = 9; // Синтактично валидно. Стойността на х сега е 9, изразът в скобите връща х. 52 | 53 | (5 + 7) = 9; // Грешка - Няма как да зададем стойност на стойност. 54 | } 55 | ``` 56 | 57 | Какво ще изведе кода: 58 | ```cpp 59 | #include 60 | 61 | using namespace std; 62 | 63 | int main() 64 | { 65 | int x = 10; 66 | 67 | cout << (x = 5) << endl; 68 | } 69 | ``` 70 | 71 | ### Оператори за инкрементиране и декрементиране. 72 | Операторът за инкрементиране (++) е унарен оператор (приема един аргумент, за разлика от бинарните, приемащи два), който увеличава стойността на променлива с единица. Еквивалентно имаме оператор за декрементиране (--), който намаля стойността на променлива с единица. Синтаксисът може да бъде: 73 | 74 | ```cpp 75 | #include 76 | 77 | using namespace std; 78 | 79 | int main() 80 | { 81 | int x = 0; 82 | 83 | ++x; // префиксен x = 1 84 | x++; // постфиксен x = 2 85 | 86 | --x; // префиксен x = 1 87 | x--; // постфиксен x = 0 88 | } 89 | ``` 90 | Каква е разликата между префиксния и постфиксния запис на оператора ++? Разликата е **в стойността, която връщат**. 91 | 92 | ```cpp 93 | #include 94 | 95 | using namespace std; 96 | 97 | int main() 98 | { 99 | int varOne = 5; 100 | int varTwo = 5; 101 | 102 | int varThree = ++varOne; 103 | int varFour = varTwo++; 104 | 105 | cout << varThree << " " << varFour << endl; 106 | } 107 | ``` 108 | * Префиксният оператор инкрементира стойността на променливата и връща **променливата**. 109 | * Постфиксният оператор инкрементира променливата и връща нейната **стара** стойност. 110 | 111 | Тук отново забелязваме, че единият опетарот връща променлива, а другият - стойност. Как можем да проверим това нагледно? 112 | 113 | ```cpp 114 | int main() 115 | { 116 | int i = 10; 117 | 118 | ++i = 5; // Валидна конструкция -- i се увеличава с 1 и се връща променливата i. След това ѝ се дава стойност 5. 119 | 120 | i++ = 5; // Невалидна конструкция -- i се увеличава с 1 и се връща старата стойност. Няма как да дадем стойност на стойност - това е компилационна грешка. 121 | } 122 | 123 | ``` 124 | 125 | ### Приоритет на оператори 126 | Нека разгледаме следния код: 127 | ```cpp 128 | #include 129 | 130 | using namespace std; 131 | 132 | int main() 133 | { 134 | bool x = true; 135 | bool y = true; 136 | bool z = false; 137 | 138 | bool result = x || y && z; 139 | 140 | cout << result << endl; 141 | } 142 | ``` 143 | 144 | Какво ще изведе този код? Ако първо изпълним оператора || и след това &&, ще получим 0. Ако първо изпълним && и след това || получаваме 1. 145 | 146 | Съществува нещо, наречено **приоритет на операторите**. В случая оператора && е с по-висок приоритет от || и ще се извърши първи. 147 | 148 | **При оценка на изрази в С++ операторите с по-висок приоритет се изпълняват преди тези с по-нисък.** 149 | 150 | ### Таблица с операторите в С++ 151 | ![](https://i.stack.imgur.com/u3q2E.png) 152 | 153 | ```cpp 154 | #include 155 | 156 | using namespace std; 157 | 158 | int main() 159 | { 160 | int x = 5; 161 | int y = 5; 162 | 163 | cout << x++ + ++y; // Какво ще изведе този код? Защо? 164 | } 165 | ``` 166 | 167 | ### Асоциативност на оператори 168 | Какво правим, когато имаме оператори с еднакъв приоритет? Примерно: 169 | ```cpp 170 | #include 171 | 172 | using namespace std; 173 | 174 | int main() 175 | { 176 | int x = 120; 177 | int y = 40; 178 | int z = 10; 179 | 180 | cout << (x / y / z) << endl; 181 | } 182 | ``` 183 | Как разбираме какъв би бил резултатът при оценка на израза? 184 | 185 | Можем да сметнем (120 / 40) / 10 = 0, но можем и да сметнем 120 / (40 / 10) = 30. 186 | 187 | **Всеки оператор има асоциативност**. Това ще рече, когато имаме няколко оператора с еднакъв приоритет, действието се извършва или от ляво надясно (ляво асоциативни) или от дясно наляво (дясно асоциативни). 188 | 189 | Оператора за деление е ляво асоциативен. Това означава, че ще се сметне първо x / y и след това резултатът ще се раздели на z. Стойността на result e 0. 190 | 191 | В третата колона на представената таблица е описана асоциативността на операторите. 192 | 193 | Какво ще изведе кода: 194 | ```cpp 195 | #include 196 | 197 | using namespace std; 198 | 199 | int main() 200 | { 201 | cout << 10 - 5 - 3; 202 | } 203 | 204 | ``` 205 | 206 | ### Операторите =, +=, -=, *=, /=, %= 207 | Когато разглеждахме изразите в С++, видяхме, че операторът = връща стойност. 208 | 209 | Съществуват още няколко оператора, които в някакъв смисъл улесняват живота ни. 210 | 211 | Вместо да пишем: 212 | ```cpp 213 | x = x + 10; 214 | ``` 215 | Би било еквивалентно да напишем: 216 | 217 | ```cpp 218 | x += 10; 219 | ``` 220 | 221 | Операторите +=, -=, *=, /= и %= връщат променлива. 222 | Какво ще изведе кода: 223 | 224 | ```cpp 225 | #include 226 | 227 | using namespace std; 228 | 229 | int main() 230 | { 231 | int x = 10; 232 | int y = 20; 233 | 234 | cout << (y -= (x *= 10)); 235 | } 236 | ``` 237 | 238 | ## Условни конструкции 239 | Много често е удобно, ако някое условие е изпълнено, да се изпълни един код, а ако то не е изпълнено, да се изпълни друг. 240 | Примерно, нека разгледаме следната 241 | ### Задача 242 | Потребител въвежда от стандартния вход цяло число. Ако то е положително, се извежда числото, умножено по 10. Ако е отрицателно, се извежда съобщение за грешка. 243 | Вече знаем как да проверим дали едно число е положително или не. Би било удобно да можем да кажем на езика: 244 | ``` 245 | Ако (a < 0), изведи a * 10, в противен случай изведи грешка. 246 | ``` 247 | 248 | Такива конструкции съжествуват във всеки език за програмиране и се наричат условни конструкции. Синтаксисът е: 249 | ```cpp 250 | if(<условие>) 251 | { 252 | // Изпълни нещо 253 | } 254 | ``` 255 | Можем да имаме също какво да се изпълни, ако условието не е истина: 256 | 257 | ```cpp 258 | if(<условие>) // Условието е израз, който може да се преобразува до тип bool 259 | { 260 | // Изпълни нещо 261 | } 262 | else 263 | { 264 | // Изпълни друго нещо 265 | } 266 | ``` 267 | 268 | Нека разгледаме как ще изглежда решението на задачата: 269 | 270 | ```cpp 271 | #include 272 | 273 | using namespace std; 274 | 275 | int main() 276 | { 277 | int number = 0; 278 | 279 | cout << "Enter your number: "; 280 | cin >> number; 281 | 282 | if(number < 0) 283 | { 284 | cout << "The number must be positive!" << endl; 285 | } 286 | else 287 | { 288 | cout << number * 10; 289 | } 290 | } 291 | ``` 292 | 293 | ### Разглеждане на повече от едно условие 294 | Нека усложним задачата. Нека сега се въвежда число, ако числото е между 2 и 6, да се извежда оценката с думи, която студентът е получил. В противен случай да се изведе съобщение за грешка. Това можем да го постигнем със следната конструкция: 295 | ```cpp 296 | if(<условие>) 297 | { 298 | // направи нещо 299 | } 300 | else if(<условие1>) 301 | { 302 | // направи друго нещо 303 | } 304 | else if(<условие2>) 305 | { 306 | // направи трето нещо 307 | } 308 | ... // Можем да имаме колкото искаме else if блока 309 | ``` 310 | 311 | Решението на задачата би изглеждало по следния начин: 312 | 313 | ```cpp 314 | #include 315 | 316 | using namespace std; 317 | 318 | int main() 319 | { 320 | int number = 0; 321 | 322 | cin >> number; 323 | 324 | if(number == 2) 325 | { 326 | cout << "Poor" << endl; 327 | } 328 | else if(number == 3) 329 | { 330 | cout << "Satisfactory" << endl; 331 | } 332 | else if(number == 4) 333 | { 334 | cout << "Good" << endl; 335 | } 336 | else if(number == 5) 337 | { 338 | cout << "Very good" << endl; 339 | } 340 | else if(number == 6) 341 | { 342 | cout << "Excellent" << endl; 343 | } 344 | else 345 | { 346 | cout << "Invalid input" << endl; 347 | } 348 | } 349 | ``` 350 | ## Кога пишем if-elseif и кога избираме да напишем няколко if конструкции 351 | ```cpp 352 | #include 353 | 354 | using namespace std; 355 | 356 | int main() 357 | { 358 | int x = 0; 359 | 360 | cin x; 361 | 362 | // Първи начин - if-elseif 363 | if(x > 0) 364 | { 365 | cout << "Entered number is bigger than zero"; 366 | } 367 | else if(x == 0) 368 | { 369 | cout << "Entered number is zero"; 370 | } 371 | else 372 | { 373 | cout << "Entered number is less than zero."; 374 | } 375 | 376 | // Втори начин - три if конструкции 377 | if(x > 0) 378 | { 379 | cout << "Entered number is bigger than zero"; 380 | } 381 | if(x == 0) 382 | { 383 | cout << "Entered number is zero"; 384 | } 385 | if(x < 0) 386 | { 387 | cout << "Entered number is less than zero."; 388 | } 389 | } 390 | ``` 391 | В този случай е по-добре да използваме if-elseif. Ако влезем в първия случай (х > 0), проверката дали х е нула няма да се извърши. Когато използваме три if-a без значение кой от тях е истина, **другите два отново ще се проверят.** Чрез първата конструкция **спестяваме проверки.** 392 | 393 | Първата конструкция се използва, когато имаме **взаимно изключващи се случаи.** Примерно, ако х е по-голямо от нула, то то не е равно на нула и също така не е по-малко от нула. Тези случаи са взаимно изключващи се. 394 | 395 | Ако имаме две условия, които не са взаимно изключващи се, то тогава **може да се наложи да използваме две if конструкции.** 396 | 397 | ```cpp 398 | #include 399 | 400 | using namespace std; 401 | 402 | int main() 403 | { 404 | unsigned hours = 0; 405 | unsigned minutes = 0; 406 | 407 | cin >> hours >> minutes; 408 | 409 | bool validHours = hours < 24; 410 | bool validMinutes = minutes < 60; 411 | 412 | if(!validHours) 413 | { 414 | cout << "You entered invalid hours"; 415 | } 416 | 417 | if(!validMinutes) 418 | { 419 | cout << "You entered invalid minutes"; 420 | } 421 | 422 | if(validHours && validMinutes) 423 | { 424 | cout << "All good!"; 425 | } 426 | } 427 | ``` 428 | В този пример може часовете да са валидни, но минутите да не са. Това не са взаимно изключващи се случаи и трябва да се проверяват с две if конструкции. 429 | 430 | 431 | # switch 432 | Конструкцията ```switch``` приема някаква променлива и в зависимост от стойността ѝ изпълнява някакъв код. Еквивалентно на конструкцията if-elseif, но различен синтаксис. 433 | 434 | ```cpp 435 | #include 436 | 437 | using namespace std; 438 | 439 | int main() 440 | { 441 | int x = 0; 442 | 443 | cin >> x; 444 | 445 | switch (x) // Случаите са по стойността на х 446 | { 447 | case 2: cout << "Poor" << endl; break; // break се слага, за да спред изпълнението на кода. Какво става 448 | case 3: cout << "Satisfactory" << endl; break; // aко нямаме break? 449 | case 4: cout << "Good" << endl; break; 450 | case 5: cout << "Very good" << endl; break; 451 | case 6: cout << "Excellent" << endl; break; 452 | default: 453 | cout << "Invalid input"; 454 | break; 455 | } 456 | } 457 | ``` 458 | 459 | В повечето случаи, когато имаме повече условия, switch e по-бърз, поради хитра имплементация на ниско ниво. Важното обаче е случаите, изброени в case, да са известни по време на компилация. 460 | ## Допълнителни задачи 461 | https://github.com/stoychoX/UP-FMI/blob/main/tasks-2022/week02/tasks.md 462 | -------------------------------------------------------------------------------- /Seminar-03/README.md: -------------------------------------------------------------------------------- 1 | # Трети семинар по увод в програмирането - 16.10.2023 2 | 3 | ## Тернарен оператор 4 | * Унарни оператори - приемат 1 аргумент. Примери са операторите !, ++, -- и други 5 | * Бинарни оператори - приемат 2 аргумента. Примери са операторите +, -, *, /, <, > и други. 6 | * Тернарен оператор - той е само един. Приема три аргумента. Синтаксис: 7 | ```cpp 8 | <Булев израз> ? <Стойност, която да се върне, ако той е истина> : <Стойност, която да се върне, ако той е лъжа>; 9 | ``` 10 | ```cpp 11 | #include 12 | 13 | using namespace std; 14 | 15 | int main() 16 | { 17 | int x = 0; 18 | cin >> x; 19 | 20 | int abs = (x < 0) ? -x : x; // Ако х е по-малко от 0, се връща -х. В противен случай се връща х. 21 | 22 | cout << x << " " << y; 23 | } 24 | ``` 25 | 26 | Използва се като съкратен запис за if-else конструкция. 27 | 28 | ## Цикли 29 | **Задача 1.** 30 | От стандартния вход се въвежда цяло положително число. Трябва да се изведат всички числа, по-малки или равни на въведеното число. 31 | 32 | Със знанията, които имаме до момента, решението на тази задача изглежда трудно. Езиците за програмиране имат конструкции, които улесняват подобни задачи. Решението не задачата може да се опише по следния начин: 33 | 34 | 1. Създай число х и го прочети от конзолата. 35 | 2. Създай число i и му дай стойност 0. 36 | 3. Докато i e по-малко или равно на х: 37 | 1. печатай i 38 | 2. увеличи i с единица. 39 | 3. Върни се на 3. 40 | 41 | Вече знаем как да създаваме променливи, как да ги четем от конзолата и как да ги увеличаваме с единица. Как ще моделираме `докато` условието? 42 | 43 | ## while цикъл 44 | Можем да използваме цикъла `while`. Синтаксис: 45 | 46 | ```cpp 47 | while(<Булево условие>) 48 | { 49 | // Код, който да се изпълни 50 | } 51 | ``` 52 | 53 | От тук решението на задачата изглежда по следния начин: 54 | 55 | ```cpp 56 | #include 57 | 58 | using namespace std; 59 | 60 | int main() 61 | { 62 | unsigned x = 0; 63 | cin >> x; 64 | unsigned i = 0; 65 | 66 | while (i <= x) 67 | { 68 | cout << i << " "; 69 | ++i; 70 | } 71 | } 72 | ``` 73 | 74 | **Задача: Променяйки този пример, направете така, че програмата да смята x! (факториел).** 75 | 76 | ## Безкрайни цикли. Ключовата дума *break*. 77 | В показаните примери циклите се въртяха, докато не стигнем някакво условие. В контекста на **Задача 1** ние знаем, че рано или късно това условие ще бъде достигнато. Какво би станало обаче, ако имаме условие, което винаги е истина? 78 | ```cpp 79 | while(true) 80 | { 81 | // do something 82 | } 83 | ``` 84 | Въпреки че условието винаги е истина, все пак можем да прекратим цикъла. Можем да използваме ключовата дума `break`. 85 | `break` се използва в два случая: 86 | 1. За да излезем **веднага** от цикъл. 87 | 2. За да излезем от `switch` конструкция. 88 | 89 | Използвайки `break` извън цикъл или `switch` се счита за синтактична грешка. 90 | 91 | ## do-while цикъл 92 | В началото на изпълнението на while се проверява булевото условие. Ако то не е истина, не се продължава към изпълнението на кода. Понякога е удобно кодът в тялото на цикъла да се изпълни веднъж и чак след това да се провери условието на while. 93 | 94 | **Пример:** 95 | От стандартния вход се четат символи. Четенето продължава докато не се въведе символ 'q'. 96 | 97 | Как ще направим това с while: 98 | ```cpp 99 | #include 100 | 101 | using namespace std; 102 | 103 | int main() 104 | { 105 | char symbol; 106 | cout << "Enter a symbol: " << endl; 107 | cin >> q; 108 | 109 | while(symbol != 'q') 110 | { 111 | cout << "Enter a symbol: " << endl; 112 | cin >> q; 113 | } 114 | } 115 | ``` 116 | 117 | Забелязваме, че имаме дублиране на код. Как би изглеждала тази задача, ако използваме do-while коснструкция: 118 | ```cpp 119 | #include 120 | 121 | using namespace std; 122 | 123 | int main() 124 | { 125 | char symbol; 126 | 127 | do 128 | { 129 | // Кодът се изпълнява веднъж 130 | // след това се праверява условието 131 | cout << "Enter a symbol: " << endl; 132 | cin >> symbol; 133 | } while(symbol != 'q'); 134 | } 135 | ``` 136 | 137 | ## for цикъл 138 | Нека разгледаме малко по-добре решението на **задача 1**. По-точно, нека разгледаме по-подробно как конструирахме while цикъла. 139 | Можем да разделим цикъла на четири основни момента: 140 | 141 | ```cpp 142 | #include 143 | 144 | using namespace std; 145 | 146 | int main() 147 | { 148 | unsigned x = 0; 149 | cin >> x; 150 | 151 | // Момент 1: Създаваме променлива, която ни помага да проверяваме условието. 152 | // Това се случва само веднъж. 153 | unsigned i = 0; 154 | 155 | while (i <= x) // Момент 2: Проверяваме условието. Това се случва до първия път, в който то стане false. 156 | { 157 | cout << i << " "; // Момент 3: Изпълняваме кода, който ни върши работата 158 | ++i; // Момент 4: Променяме по някакъв начин променливата и се връщаме в момент 2. 159 | } 160 | } 161 | ``` 162 | Можем да се абстрахираме от детайлите на задачата и да опишем общата конструкция на този while цикъл: 163 | ``` 164 | init-statement (Момент 1) 165 | 166 | while ( condition ) (Момент 2) 167 | { 168 | statement; (Момент 3) 169 | iteration-expression ; (Момент 4) 170 | } 171 | ``` 172 | 173 | Цикълът for отново използва тази конструкция: 174 | ```cpp 175 | for(init-statement; condition; iteration-expression) 176 | { 177 | statement; 178 | } 179 | ``` 180 | 181 | Във for цикъла нещата са подобни. 182 | 1. init-statement се изпълнява точно веднъж 183 | 2. Провери condition. 184 | * Ако condition e false, приключи 185 | * Ако condition e true: 186 | - изпълни statement 187 | - изпълни iteration-expression 188 | - Върни се в 2. 189 | 190 | Би трябвало да е очевидно, че всяко едно нещо, което можем да направим с for, можем да направим и с while и обратното. 191 | Решението на **Задача 1** би изглеждало по следния начин, написано с for цикъл: 192 | 193 | ```cpp 194 | #include 195 | 196 | using namespace std; 197 | 198 | int main() 199 | { 200 | unsigned x = 0; 201 | cin >> x; 202 | 203 | for(int i = 0; i <= x; i++) 204 | { 205 | cout << i << endl; 206 | } 207 | } 208 | ``` 209 | 210 | ## Примери 211 | 212 | ### Задача 2 213 | От стандартния вход се въвеждат две числа. Да се намери най-големия им общ делител. 214 | 215 | ### Задача 3 216 | От стандартния вход се въвежда число. Да се провери дали е просто или не. 217 | -------------------------------------------------------------------------------- /Seminar-03/euclid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | unsigned first = 0; 8 | unsigned second = 0; 9 | 10 | cin >> first >> second; 11 | 12 | if(first < second) 13 | { 14 | unsigned temp = first; 15 | first = second; 16 | second = temp; 17 | } 18 | 19 | while(second != 0) 20 | { 21 | unsigned firstTemp = first; 22 | first = second; 23 | second = firstTemp % second; 24 | } 25 | 26 | cout << first; 27 | } -------------------------------------------------------------------------------- /Seminar-03/homework01.md: -------------------------------------------------------------------------------- 1 | # Първо малко домашно на семинари - 18.10.2023 2 | 3 | ## Задача първа 4 | 1. От стандартния вход се въвеждат две числа, числител и знаменател на дроб. Да се изведат числителя и знаменателя на получената след съкращението дроб. 5 | 6 | ``` 7 | Вход: 12 6 8 | Изход: 2 1 9 | 10 | Вход: 14 5 11 | Изход: 14 5 12 | 13 | Вход: 145 15 14 | Изход: 29 3 15 | ``` 16 | 17 | 2. От стандартния вход се въвеждат четири числа, представящи числител и знаменател на дроб. Да се изведат числителя и знаменателя на сбора им (отново съкратени). 18 | 19 | 20 | ``` 21 | Вход: 1 2 3 4 22 | Изход: 5 4 23 | 24 | Вход: 9 2 8 3 25 | Изход: 43 6 26 | ``` 27 | 28 | ## Задача втора 29 | Военните използват числа, за да посочат даден момент от време. 30 | Примерно 31 | ` 32 | 1430 - Два и половина (обед) 33 | 230 - Два и половина (вечер) 34 | 100 - Един часа (вечер) 35 | 0 - Полунощ 36 | ` 37 | 38 | 39 | Напишете програма, която по дадени две числа проверява дали са валидни моменти от време и ако да, намира разликата им. 40 | 41 | Пример: 42 | 43 | > Вход: 1430 1630 44 | > 45 | > Изход: Valid timestamps, difference: 2 hours and 00 minutes 46 | 47 | > Вход: 1230 220 48 | > 49 | > Изход: Valid timestamps, difference: 10 hours and 10 minutes 50 | 51 | > Вход: 2424 1230 52 | > 53 | > Изход: 2424 is not a valid timestamp. -------------------------------------------------------------------------------- /Seminar-03/isPrime.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | int main() 7 | { 8 | unsigned number = 0; 9 | cin >> number; 10 | 11 | bool isPrime = true; 12 | if(number > 1) 13 | { 14 | double squareOfNumber = sqrt(number); 15 | 16 | for (int i = 2; i <= squareOfNumber; i++) 17 | { 18 | if(number % i == 0) 19 | { 20 | isPrime = false; 21 | break; 22 | } 23 | } 24 | } 25 | else 26 | { 27 | isPrime = false; 28 | } 29 | 30 | if(isPrime) 31 | { 32 | cout << "The number is prime" << endl; 33 | } 34 | else 35 | { 36 | cout << "The number is not prime" << endl; 37 | } 38 | } -------------------------------------------------------------------------------- /Seminar-04/README.md: -------------------------------------------------------------------------------- 1 | # Четвърти семинар по увод в програмирането - 23.10.2023 2 | 3 | ## Функции 4 | 5 | **Задача 1** 6 | От стандартния вход се въвеждат две числа. Ако първото число е просто, се извежда "First number is prime" и изпълнението на програмата приключва. Ако е съставно, проверяваме второто число. Ако то е просто, се извежда "Second number is prime", в противен случай се извежда "None of the numbers are prime". 7 | 8 | Със знанията до момента можем да решим тази задача. Решението би изглеждало по следния начин: 9 | ```cpp 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | int main() 16 | { 17 | unsigned first = 0; 18 | unsigned second = 0; 19 | 20 | cin >> first >> second; 21 | 22 | bool isFirstPrime = true; 23 | if(first > 1) 24 | { 25 | double squareOfNumber = sqrt(first); 26 | 27 | for (int i = 2; i <= squareOfNumber; i++) 28 | { 29 | if(first % i == 0) 30 | { 31 | isFirstPrime = false; 32 | break; 33 | } 34 | } 35 | } 36 | else 37 | { 38 | isFirstPrime = false; 39 | } 40 | 41 | if(isFirstPrime) 42 | { 43 | cout << "First number is prime"; 44 | } 45 | else 46 | { 47 | bool isSecondPrime = true; 48 | if(second > 1) 49 | { 50 | double squareOfSecondNumber = sqrt(second); 51 | 52 | for (int i = 2; i <= squareOfSecondNumber; i++) 53 | { 54 | if(second % i == 0) 55 | { 56 | isSecondPrime = false; 57 | break; 58 | } 59 | } 60 | } 61 | else 62 | { 63 | isSecondPrime = false; 64 | } 65 | 66 | if(isSecondPrime) 67 | { 68 | cout << "The second number is prime"; 69 | } 70 | else 71 | { 72 | cout << "None of the numbers are prime"; 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | Интуитивно трябва да усещаме, че този код не е добър. Да, той работи, при това коректно, но проблемът тук е **повторението на код**. 79 | Логиката зад кода, проверяващ дали едно число е просто или не, е една и съща. Разликата е в числото, което бива проверено. 80 | 81 | С този проблем се справяме използвайки **функции**. 82 | 83 | Синтаксис: 84 | ``` 85 | <тип на връщане> <име на функцията>(<0 или повече параметри, отделени със запетайки>) 86 | { 87 | <Тяло на функцията> 88 | } 89 | ``` 90 | 91 | За функцията може да си мислим като код, който може да бъде извикван чрез зададено от нас име, който може да връща резултат и на който можем да подаваме нула или повече параметри (променливи). 92 | Това първоначално може да звучи объркващо, но нека видим пример: 93 | 94 | ```cpp 95 | #include 96 | 97 | using namespace std; 98 | 99 | // Име на функцията: sum 100 | // Тип на връщане: int 101 | // Параметри, които приемаме: firsrNumber и secondNumber от тип int 102 | // Идея: Функцията приема като параметри две числа и връща техния сбор. 103 | int sum(int firstNumber, int secondNumber) 104 | { 105 | return firstNumber + secondNumber; 106 | } 107 | 108 | int main() 109 | { 110 | int number = sum(3, 4); // Сега, стойността на number е 7. 111 | } 112 | ``` 113 | Можем да имаме функции, които не връщат нищо. На мястото на типа на връщане пишем ключовата дума `void`. 114 | 115 | ```cpp 116 | #include 117 | 118 | using namespace std; 119 | 120 | void printNumberToConsole(int number) 121 | { 122 | cout << "My number is " << number << endl; 123 | } 124 | 125 | int main() 126 | { 127 | printNumberToConsole(1); // Извежда на конзолата My number is 1 128 | printNumberToConsole(4); // Аналогично 129 | printNumberToConsole(5); // Аналогично 130 | } 131 | ``` 132 | Можем да имаме и функции, които не приемат никакви аргументи. 133 | 134 | Как бихме написали **Задача 1**, използвайки функции? 135 | 136 | ```cpp 137 | #include 138 | 139 | using namespace std; 140 | 141 | bool isPrime(unsigned number) 142 | { 143 | if (number < 2) 144 | { 145 | return false; 146 | } 147 | 148 | double squareOfNumber = sqrt(number); 149 | 150 | for (int i = 2; i <= squareOfNumber; i++) 151 | { 152 | if(number % i == 0) 153 | { 154 | return false; 155 | } 156 | } 157 | return true; 158 | } 159 | 160 | int main() 161 | { 162 | unsigned first = 0; 163 | unsigned second = 0; 164 | 165 | if(isPrime(first)) 166 | { 167 | cout << "First number is prime"; 168 | } 169 | else if(isPrime(second)) 170 | { 171 | cout << "Second number is prime"; 172 | } 173 | else 174 | { 175 | cout << "None of the numbers are prime"; 176 | } 177 | } 178 | ``` 179 | Какви са предимствата? 180 | 1. Кодът става в пъти по-четим. 181 | 2. Кодът става по-кратък. 182 | 3. Кодът става в пъти по-лесен за поддръжка. 183 | 184 | ## Функции, които вече сме виждали. 185 | Всъщност вече сме виждали функции, просто не сме говорили за тях. 186 | 187 | Ако се загледаме много внимателно в програмите, които пишем, ще забележим, че `int main() {}` всъщност синтактично отговаря на дефиницията на функция. Това е защото е функция, но е малко по специална функция. 188 | 189 | Тя е единствена за нашата програма. Можем да мислим, че цялото изпълнение на програмата започва от нея. 190 | 191 | А какво е `sqrt()`? Това също е функция. Тя приема число и връща корен квадратен от числото. Тази функция, както и още други, са написани в библиотеки, идващи с езика C++, за наше удобство. 192 | 193 | ## За ключовата дума return 194 | Какво представлява ключовата дума `return`, която видяхме в решението на `Задача 1`? След `return` се дава стойността, която да се върне от функцията. Ключовата дума `return` **прекратява веднага изпълнението на функцията**. След като сме извикали веднъж `return` **функцията веднага връща подадената стойност и не се изпълнява нито ред повече от нея.** 195 | 196 | ## Задача първа 197 | Напишете функция, която приема неотрицателно число и връща числото обърнато. Приемаме, че числото и обърнатото число се събират в типа int. 198 | 199 | ## Задача втора 200 | Напишете функция, която приема число и връща броя на цифрите му. 201 | 202 | ## Задача трета 203 | Напишете функция, която приема неотрицателно число и проверява дали е палиндром. 204 | 205 | ## Задача четвърта 206 | Да се напише функция, която като параметри приема две числа n и m (нека m < n) и връща сумата в интервала [m, n]. 207 | 208 | ## Задача пета 209 | Напишете функция, която извежда на стандартния екран разбиването на положително число на прости делители. 210 | -------------------------------------------------------------------------------- /Seminar-04/solutions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | unsigned reverseNumber(unsigned numberToReverse) 6 | { 7 | unsigned flippedNumber = 0; 8 | 9 | while (numberToReverse != 0) 10 | { 11 | flippedNumber *= 10; 12 | flippedNumber += numberToReverse % 10; 13 | numberToReverse = numberToReverse / 10; 14 | } 15 | return flippedNumber; 16 | } 17 | 18 | unsigned countDigits(unsigned number) 19 | { 20 | if(number == 0) 21 | return 1; 22 | unsigned counter = 0; 23 | 24 | while (number != 0) 25 | { 26 | number /= 10; 27 | counter++; 28 | } 29 | return counter; 30 | } 31 | 32 | bool isPalindrome(unsigned x) 33 | { 34 | unsigned reversed = reverseNumber(x); 35 | return (x == reversed); 36 | } 37 | 38 | unsigned sumInInterval(unsigned m, unsigned n) 39 | { 40 | if (m > n) 41 | { 42 | return 0; 43 | } 44 | 45 | unsigned sum = 0; 46 | 47 | for (unsigned i = m; i <= n; i++) 48 | { 49 | sum += i; 50 | } 51 | 52 | return sum; 53 | } 54 | 55 | // sum([1, ..., x]) = x * (x + 1) / 2 56 | // sum([m, ..., n]) = sum([1, ..., n]) - sum([1, ..., m]) 57 | unsigned sumInIntervalSmart(unsigned m, unsigned n) 58 | { 59 | if(m > n) 60 | { 61 | return 0; 62 | } 63 | 64 | --m; 65 | 66 | return (n * (n + 1) / 2) - (m * (m + 1) / 2); 67 | } 68 | #include 69 | int main() 70 | { 71 | for (size_t i = 0; i < 1000000; i++) 72 | { 73 | int fst = rand(); 74 | int snd = rand(); 75 | assert(sumInInterval(fst, snd) == sumInIntervalSmart(fst, snd)); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Seminar-04/solutions_hw1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | unsigned gcd(unsigned a, unsigned b) 6 | { 7 | if(b > a) 8 | { 9 | unsigned temp = a; 10 | a = b; 11 | b = temp; 12 | } 13 | 14 | while(b != 0) 15 | { 16 | unsigned temp = a; 17 | a = b; 18 | b = temp % a; 19 | } 20 | 21 | return a; 22 | } 23 | 24 | void firstProblem() 25 | { 26 | unsigned numerator = 0; 27 | unsigned denominator = 0; 28 | 29 | cin >> numerator >> denominator; 30 | unsigned greatestCommonDivisor = gcd(numerator, denominator); 31 | 32 | cout << numerator / greatestCommonDivisor << " " << denominator / greatestCommonDivisor << endl; 33 | } 34 | 35 | void secondProblem() 36 | { 37 | unsigned numeratorFirst = 0; 38 | unsigned denominatorFirst = 0; 39 | unsigned numeratorSecond = 0; 40 | unsigned denominatorSecond = 0; 41 | cin >> numeratorFirst >> denominatorFirst >> numeratorSecond >> denominatorSecond; 42 | 43 | // Знаменателя ще е lcm на знаменателите. Него намираме чрез gcd. 44 | unsigned greatestCommonDivisor = gcd(denominatorFirst, denominatorSecond); 45 | 46 | unsigned resultDenominator = (denominatorFirst / greatestCommonDivisor) * denominatorSecond; 47 | unsigned resultNumerator = (numeratorFirst * (resultDenominator / denominatorFirst)) + (numeratorSecond * (resultDenominator / denominatorSecond)); 48 | 49 | // Трябва да съкратим резултата, понеже нямаме гаранция, че винаги 50 | // ще е съкратена дроб. 51 | unsigned gcdResult = gcd(resultNumerator, resultDenominator); 52 | cout << resultNumerator / gcdResult << " " << resultDenominator / gcdResult << endl; 53 | } 54 | 55 | bool validTimestamp(unsigned x) 56 | { 57 | return x < 2400 && x % 100 < 60; 58 | } 59 | 60 | void thirdProblem() 61 | { 62 | unsigned timestampOne = 0; 63 | unsigned timestampTwo = 0; 64 | 65 | cin >> timestampOne >> timestampTwo; 66 | 67 | if(!validTimestamp(timestampOne)) 68 | { 69 | cout << "Invalid timestamp: " << timestampOne << endl; 70 | return; 71 | } 72 | 73 | if(!validTimestamp(timestampTwo)) 74 | { 75 | cout << "Invalid timestamp: " << timestampTwo << endl; 76 | return; 77 | } 78 | if(timestampOne < timestampTwo) 79 | { 80 | unsigned temp = timestampOne; 81 | timestampOne = timestampTwo; 82 | timestampTwo = temp; 83 | } 84 | 85 | int resultHours = (timestampOne / 100) - (timestampTwo / 100); 86 | int resultMins = (timestampOne % 100) - (timestampTwo % 100); 87 | 88 | if(resultMins < 0) 89 | { 90 | resultMins += 60; 91 | --resultHours; 92 | } 93 | 94 | cout <<"Difference: " << resultHours << " hours and " << resultMins << " minutes." << endl; 95 | } 96 | 97 | int main() 98 | { 99 | 100 | } -------------------------------------------------------------------------------- /Seminar-05/README.md: -------------------------------------------------------------------------------- 1 | # Пети семинар по увод в програмирането - 30.10.2023 2 | 3 | ## Подаване на параметри на функции 4 | В началото на курса казахме, че променливите всъщност се пазят в паметта. Всяка променлива си има адрес, в която е записана нейната стойност. Нека разгледаме следния пример: 5 | ```cpp 6 | #include 7 | 8 | using namespace std; 9 | 10 | void increment(int argument) 11 | { 12 | argument++; 13 | } 14 | 15 | int main() 16 | { 17 | int x = 10; 18 | increment(x); 19 | cout << x; 20 | } 21 | ``` 22 | Какво ще изведе този код? Въпросът тук е дали х ще се увеличи с 1 или няма. Отговорът е, че няма да се увеличи с 1. Но защо? 23 | Когато подаваме променливи като аргументи на функции срещаме два основни случая: 24 | 25 | 1. Работим със **стойността на променливата**. Не ни интересува променливата а **това какво е записано в нея**. В този случай на променливата **се прави копие** понеже се интересуваме **единствено от стойността ѝ**. Това е и случая в разгледания пример. На променливата х, която живее в scope-a на main се прави копие, което живее в scope-a на increment. Това копие живее **до края на scope-a на increment.** След като приключи изпълнението на функцията increment стойността на х е непокътната, понеже сме подали копие. 26 | 27 | Казано с по - прости думи, на x се прави копие, когато живее в increment. А възможно ли е да не направим копие? Възможно ли е да подадем самата променлива? 28 | 29 | ## Подаване на референция 30 | За референцията можем да си мислим като променлива, която е псевдоним на друга променлива. Тя дава друго име на променлива, променяйки едната променяме другата. 31 | 32 | Пример: 33 | ```cpp 34 | #include 35 | 36 | using namespace std; 37 | 38 | int main() 39 | { 40 | int x = 0; 41 | int& xRef = x; 42 | 43 | xRef = 10; 44 | cout << x; 45 | } 46 | ``` 47 | Ако искаме за променлива да не се прави копие а да се използва самата тя във функция можем да я подадем като референция. Примерно: 48 | 49 | ```cpp 50 | void increment(int& x) 51 | { 52 | x++; 53 | } 54 | 55 | int main() 56 | { 57 | int x = 10; 58 | increment(x); 59 | // След изпълнението на increment стойността на х вече е 11. 60 | } 61 | ``` 62 | 63 | **Задача:** Да се напише функция, която разменя две променливи (swap). 64 | 65 | ## Какво можем и какво не можем да правим с референции 66 | Едно от големите предимства на референциите е именно използването им като аргументи на функции. Копирането на променливи не е безплатно и използвайки референции ние пестим време от копиране. 67 | 68 | Не е желателно те да се използват навсякъде обаче. Понякога имаме нужда точно от копие, което можем да променяме знаейки, че няма да навредим на външния свят. Когато се чудите дали да подадете аргумент като референция или по копие се запитайте имате ли нужда да правите копие или самата променлива би ви свършила работа. 69 | 70 | Какво не можем да правим с референции? 71 | * Не можем да създадем референция, която не сочи към нищо. 72 | ```cpp 73 | int main() 74 | { 75 | int& ref; // Грешка, не сочи към нищо 76 | } 77 | ``` 78 | 79 | * Не можем да пренасочваме референция. Веднъж сочеща към една променлива тя не може да започне да сочи към друга. 80 | 81 | ```cpp 82 | #include 83 | 84 | using namespace std; 85 | 86 | int main() 87 | { 88 | int x = 0; 89 | int& xRef = x; 90 | 91 | int y = 10; 92 | xRef = y; // xRef не сочи към у. Стойността на х става стойността на у. 93 | 94 | cout << x << endl; 95 | 96 | xRef = 42; // Стойността на у не се променя. Стойността на х става 42. 97 | cout << x << " " << y; 98 | } 99 | ``` 100 | 101 | ## Function overloading 102 | Можем ли да имаме две функции с еднакви имена? Видяхме, че функция се дефинира чрез три атрибута: 103 | 1. Тип на връщане 104 | 2. Име 105 | 3. Аргументи, които приема 106 | 107 | Ако имаме две функции с еднакви имена, тип на връщане и аргументи това е грешка. Ако имаме две функции с еднакви имена и един и същ тип на връщане това отново е грешка. Но ако имаме две функции с еднакви имена, имащи различен брой или тип параметри, това е позволено. Всъщност това е подход наречен function overloading. 108 | 109 | Нека разглидаме пример: 110 | ```cpp 111 | #include 112 | 113 | using namespace std; 114 | 115 | int max(int a, int b) 116 | { 117 | return a > b ? a : b; 118 | } 119 | 120 | int max(int a, int b, int c) 121 | { 122 | return max(max(a, b), c); 123 | } 124 | 125 | int max(int a, int b, int c, int d) 126 | { 127 | return max(max(a, b), max(c, d)); 128 | } 129 | 130 | int main() 131 | { 132 | cout << max(4, 5) << endl; 133 | cout << max(1, 3, 2) << endl; 134 | cout << max(1, 2, 4, 2) << endl; 135 | } 136 | ``` 137 | 138 | Тук няма никаква грешка. В първия пример ще се извика функцията, приемаща 2 аргумента. Във втория се вика тази, приемаща 3, която вика 2 пъти тази приемаща два. Аналогично, в третия пример се вика тази, приемаща 4, която вика тази приемаща 2 параметъра 3 пъти. 139 | 140 | ## lvalue и rvalue 141 | В Семинар 2 засегнахме темата за това как се оценяват изразите. Споменахме, че те могат да се оценят до **променлива** или до **стойност**. 142 | 143 | Разбрахме, че функцията може да връща някакъв **тип**. Това, което връща функцията обаче, в езика С++, се категоризира с още едно свойство а именно дали е **стойност** или **променлива**. За това свойство можем да си мислим като за **категория на стойността**. Ще разгледаме две такива категории **rvalue (стойност)** и **lvalue (променлива)**. 144 | 145 | Наричат се **rvalue** и **lvalue** поради исторически причини - изразите (функциите) оценени до **lvalue** могат да стоят от лявата страна на равното. Аналогично изразите (функциите) оценени до **rvalue** могат да стоят от дясната. 146 | 147 | **lvalue** изразите също могат да стоят от дясната страна на равенството, но **rvalue** не може да стои от лявата - такива примери са разглеждани. 148 | 149 | Нека си припомним следния пример: 150 | 151 | ```cpp 152 | int main() 153 | { 154 | int i = 0; 155 | ++i = 10; // Това е окей понеже връща lvalue (променлива) 156 | i++ = 10; // Това не е окей понеже връща rvalue (стойност) - как даваме стойност на стойност. 157 | } 158 | ``` 159 | 160 | Ако си поиграем малко можем да имплементираме префиксния и постфиксния оператор чрез функции: 161 | ```cpp 162 | int& preffix_increment(int& arg) 163 | { 164 | arg += 1; 165 | return arg; 166 | } 167 | 168 | int postfix_increment(int& arg) 169 | { 170 | arg += 1; 171 | return arg - 1; 172 | } 173 | 174 | int main() 175 | { 176 | int i = 0; 177 | preffix_increment(i) = 10; // Това е окей - връща lvalue (променлива). 178 | postfix_increment(i) = 10; // Грешка 179 | } 180 | ``` 181 | 182 | ## Задача първа - бикове и крави 183 | Да се напише програма, която представлява играта *Бикове и крави*. Правилата на играта: 184 | 1. Въвежда се число от стандартния вход. Въведеното число **няма повтарящи се цифри.** 185 | 2. След това се опитваме да го познаем като последователно пишем числа. Всеки път когато имаме цифра, която се съдържа в тайното число и е на същата позиция имаме бик. Когато имаме цифра, която се съдържа в тайното число, но е на друга позиция имаме крава. 186 | 187 | Пример: 188 | > 1234 // Тайно число 189 | > 190 | > begin the game: 191 | > 192 | > 4257 193 | > 194 | > 1 bull 1 cow 195 | > 196 | > 4237 197 | > 198 | > 2 bulls 1 cow 199 | > 200 | > 4231 201 | > 202 | > 2 bulls 2 cows 203 | > 204 | > 1234 205 | > 206 | > 4 bulls 0 cows 207 | > 208 | > end. -------------------------------------------------------------------------------- /Seminar-05/bulls_and_cows.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | // 1. Въвеждаме тайното число, което потребителя ще трябва да познае. 5 | // 2. Потребителя въвежда число: 6 | // 2.1 Ако има 4 бика край. 7 | // 2.2 Ако няма 4 бика покажи му колко бика и колко крави има и се върни на 2. 8 | 9 | const unsigned DIGITS_COUNT = 4; 10 | 11 | unsigned digitsCount(unsigned number) 12 | { 13 | if(number == 0) { return 1; } 14 | 15 | unsigned counter = 0; 16 | 17 | while(number != 0) 18 | { 19 | ++counter; 20 | number /= 10; 21 | } 22 | 23 | return counter; 24 | } 25 | 26 | bool containsDigit(unsigned number, unsigned digit) 27 | { 28 | if(number == 0) { return (digit == 0); } 29 | 30 | while(number != 0) 31 | { 32 | if(number % 10 == digit) { return true; } 33 | number /= 10; 34 | } 35 | return false; 36 | } 37 | 38 | bool containsDigit(unsigned number, unsigned digit, int& pos) 39 | { 40 | if(number == 0) 41 | { 42 | pos = (digit == 0) ? 0 : -1; 43 | return (digit == 0); 44 | } 45 | int posCounter = 0; 46 | 47 | while(number != 0) 48 | { 49 | if(number % 10 == digit) 50 | { 51 | pos = posCounter; 52 | return true; 53 | } 54 | number /= 10; 55 | ++posCounter; 56 | } 57 | 58 | pos = -1; 59 | return false; 60 | } 61 | 62 | bool hasDuplicates(unsigned number) 63 | { 64 | while(number != 0) 65 | { 66 | unsigned lastDigit = number % 10; 67 | number /= 10; 68 | if(containsDigit(number, lastDigit)) { return true; } 69 | } 70 | 71 | return false; 72 | } 73 | 74 | bool validNumber(unsigned number) 75 | { 76 | return digitsCount(number) == DIGITS_COUNT && !hasDuplicates(number); 77 | } 78 | 79 | unsigned getInput() 80 | { 81 | unsigned input = 0; 82 | bool validNumberEntered; 83 | 84 | do 85 | { 86 | cout << "Enter number: "; 87 | cin >> input; 88 | validNumberEntered = validNumber(input); 89 | 90 | if(!validNumberEntered) 91 | { 92 | cout << "This number is invalid. Please try again" << endl; 93 | } 94 | } while (!validNumberEntered); 95 | 96 | return input; 97 | } 98 | 99 | void countBullsAndCows(unsigned& bulls, unsigned& cows, unsigned secret, unsigned guess) 100 | { 101 | bulls = cows = 0; 102 | 103 | unsigned posCounter = 0; 104 | int pos = 0; 105 | while(guess) 106 | { 107 | unsigned lastDigit = guess % 10; 108 | bool contain = containsDigit(secret, lastDigit, pos); 109 | 110 | if(contain) 111 | { 112 | if(pos == posCounter) 113 | { 114 | ++bulls; 115 | } 116 | else 117 | { 118 | ++cows; 119 | } 120 | } 121 | ++posCounter; 122 | guess /= 10; 123 | } 124 | } 125 | 126 | int main() 127 | { 128 | cout << "Lets make secret number." << endl; 129 | unsigned secret = getInput(); 130 | cout << "Game starts" << endl; 131 | 132 | bool wonTheGame = false; 133 | unsigned bulls = 0; 134 | unsigned cows = 0; 135 | 136 | do 137 | { 138 | unsigned guess = getInput(); 139 | countBullsAndCows(bulls, cows, secret, guess); 140 | wonTheGame = (bulls == 4); 141 | cout << "Bulls: " << bulls << " Cows: " << cows << endl; 142 | } while (!wonTheGame); 143 | 144 | cout << "You won!" << endl; 145 | } -------------------------------------------------------------------------------- /Seminar-05/min-and-max-digit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void getMaxAndMinDigit(int number, int& min, int& max) 4 | { 5 | int minDigit = 9; 6 | int maxDigit = 0; 7 | 8 | while (number != 0) 9 | { 10 | int lastDigit = number % 10; 11 | number /= 10; 12 | 13 | if (lastDigit < minDigit) 14 | { 15 | minDigit = lastDigit; 16 | } 17 | if (lastDigit > maxDigit) 18 | { 19 | maxDigit = lastDigit; 20 | } 21 | } 22 | 23 | min = minDigit; 24 | max = maxDigit; 25 | } 26 | 27 | 28 | int main() 29 | { 30 | int min = 0; 31 | int max = 0; 32 | 33 | getMaxAndMinDigit(12345, min, max); 34 | 35 | std::cout << min << " " << max; 36 | } -------------------------------------------------------------------------------- /Seminar-06/linearSearch.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | void printArray(const int arr[], unsigned length) 5 | { 6 | for (size_t i = 0; i < length; i++) 7 | { 8 | cout << arr[i] << " "; 9 | } 10 | cout << std::endl; 11 | } 12 | 13 | unsigned linearSearch(const int arr[], unsigned length, int element) 14 | { 15 | for (size_t i = 0; i < length; i++) 16 | { 17 | if(arr[i] == element) 18 | { 19 | return i; 20 | } 21 | } 22 | return -1; 23 | } 24 | 25 | int main() 26 | { 27 | constexpr unsigned MAX_LEN = 10; 28 | int arr[MAX_LEN]; 29 | 30 | for (size_t i = 0; i < MAX_LEN; i++) 31 | { 32 | arr[i] = i; 33 | } 34 | 35 | printArray(arr, MAX_LEN); 36 | unsigned target = 0; 37 | 38 | cin >> target; 39 | 40 | int pos = linearSearch(arr, MAX_LEN, target); 41 | 42 | if(pos != -1) 43 | { 44 | cout << "Element found at position: " << pos << endl; 45 | } 46 | else 47 | { 48 | cout << "No such element found." << endl; 49 | } 50 | } -------------------------------------------------------------------------------- /Seminar-06/readme.md: -------------------------------------------------------------------------------- 1 | # Шести семинар по увод в програмирането - 06.11.2023 2 | 3 | ## Задача първа 4 | Да се реализира функция 5 | ```cpp 6 | unsigned sortDigits(unsigned number); 7 | ``` 8 | която приема число и връща число, съдържащо цифрите на входа в сортиран вид. 9 | 10 | Пример: 11 | Вход: 10241025 12 | Изход: 112245 13 | 14 | ## Масиви 15 | Нека си представим, че трябва да реализираме програма която работи с оценки на ученици с номера от 1 до 26 по даден предмет. Нашата програма трябва да поддържа следната функционалност: 16 | * Да променя оценката на ученик. 17 | * Да чете оценката на ученик. 18 | * Да намира средно аритметично. 19 | * Да сортира оценките. 20 | 21 | Можем да решим тази задача със знанията които имаме до момента, но решението би изглеждало неприемливо. 22 | Тук на помощ идват масивите. 23 | 24 | Масивът е **наредена последователност от елементи от един тип.** В случая наредената последователност са оценките на учениците. Подходящ тип, който можем да използваме, е double. 25 | 26 | Синтаксис: 27 | ``` 28 | < тип > < име >[< Размер >]; 29 | ``` 30 | 31 | Синтаксис от типа `T arr[N]` създава (алокира) N (последователно наредени в паметта) обекта от тип Т. Елементите се индексират чрез индекси 0, ..., N - 1. 32 | 33 | N задължително е `constexpr`. Какво е `constexpr`? Това е стойност, която се знае още по време на компилация на програмата. 34 | 35 | ```cpp 36 | int main() 37 | { 38 | constexpr int N = 26; // constexpr 39 | constexpr int M = 2 + 3; // constexpr 40 | 41 | int k = 0; // not constexpr, can change from user input 42 | } 43 | ``` 44 | 45 | Пример: 46 | ```cpp 47 | double grades[26]; // 26 последователни double променливи 48 | ``` 49 | 50 | Как четем от масив? 51 | ```cpp 52 | grades[0]; // Това връща първия елемент от масива. Те са индексирани от 0. 53 | ``` 54 | 55 | Как пишем в масив? 56 | ```cpp 57 | grades[1] = 6.00; // Сега новата стойност на grades[1], втория елемент от масива grades, е 6.00 58 | ``` 59 | 60 | ## Особености на синтаксиса 61 | ```cpp 62 | int main() 63 | { 64 | int arr[10]; // Създава масив от 10 елемента. Стойностите им зависят от компилатора. Всеки елемент е цяло число. 65 | int arr1[3] = {1, 2, 3}; // Създава масив от три елемента. arr1[0] = 1, arr1[1] = 2, arr1[2] = 3 66 | int arr2[]; // Грешка 67 | int arr3[3] = {1, 2, 3, 4}; // Грешка 68 | } 69 | ``` 70 | 71 | Големината на масива трябва да е стойност, която е ясна по време на компилация. Това ще рече, че не можев да направим масив с големина подадена от потребителя (засега). 72 | 73 | ```cpp 74 | #include 75 | int main() 76 | { 77 | int n = 0; 78 | std::cin >> n; 79 | 80 | int arr[n]; // Грешка. 81 | } 82 | ``` 83 | 84 | Някои компилатори го позволяват, но това е поради исторически причини. Тази конструкция не се приема за коректна от стандарта на С++, компилатора на visual studio и екипа по уп. 85 | 86 | ## Подаване на масиви във функции 87 | Можем да подаваме масиви във функции. 88 | 89 | ```cpp 90 | #include 91 | 92 | // Валиден синтаксис 93 | void func(int arr[]) 94 | { 95 | 96 | } 97 | 98 | // Валиден синтаксис 99 | void funcs(int arr[10]) 100 | { 101 | 102 | } 103 | ``` 104 | 105 | ## Примери 106 | * Отпечатване на масив 107 | * Линейно търсене -------------------------------------------------------------------------------- /Seminar-06/sort-digits.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned countOccurances(unsigned number, unsigned digit) 4 | { 5 | unsigned counter = 0; 6 | 7 | while (number != 0) 8 | { 9 | unsigned lastDigit = number % 10; 10 | number /= 10; 11 | if (lastDigit == digit) 12 | { 13 | ++counter; 14 | } 15 | } 16 | 17 | return counter; 18 | } 19 | 20 | unsigned appendBack(unsigned number, unsigned digit) 21 | { 22 | number *= 10; 23 | number += digit; 24 | return number; 25 | } 26 | 27 | unsigned appendDigits(unsigned number, unsigned digit, unsigned times) 28 | { 29 | for (size_t i = 0; i < times; i++) 30 | { 31 | number = appendBack(number, digit); 32 | } 33 | return number; 34 | } 35 | 36 | unsigned sortDigits(unsigned number) 37 | { 38 | unsigned toReturn = 0; 39 | 40 | for (unsigned i = 1; i < 10; i++) 41 | { 42 | unsigned count = countOccurances(number, i); 43 | toReturn = appendDigits(toReturn, i, count); 44 | } 45 | 46 | return toReturn; 47 | } 48 | 49 | int main() 50 | { 51 | unsigned number = 0; 52 | std::cin >> number; 53 | number = sortDigits(number); 54 | std::cout << number; 55 | } -------------------------------------------------------------------------------- /Seminar-07/binary-search.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int binarySearch(const int arr[], unsigned length, int elem) 4 | { 5 | int left = 0; 6 | int right = length - 1; 7 | 8 | while (right - left >= 0) 9 | { 10 | int mid = left + (right - left) / 2; 11 | 12 | if(arr[mid] == elem) 13 | { 14 | return mid; 15 | } 16 | else if (arr[mid] > elem) 17 | { 18 | right = mid - 1; 19 | } 20 | else 21 | { 22 | left = mid + 1; 23 | } 24 | } 25 | 26 | return -1; 27 | } 28 | 29 | int main() 30 | { 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Seminar-07/readme.md: -------------------------------------------------------------------------------- 1 | # Седми семинар по увод в програмирането - 12.11.2023 2 | 3 | ## Ключовата дума const 4 | Нека разгледаме функцията `printArray`: 5 | 6 | ```cpp 7 | #include 8 | 9 | using namespace std; 10 | 11 | void printArray(const int[] arr, unsigned length) 12 | { 13 | for(unsigned i = 0; i < length; i++) 14 | { 15 | cout << arr[i] << " "; 16 | } 17 | cout << endl; 18 | } 19 | ``` 20 | В случая единственото нещо което превим е да четем от масива (той никъде не се променя). Удобно е една част от променливите ни да бъдат константи (от тях единствено се чете и те не се променят). Ключовака дума `const` прави точно това - казва, че една променлива няма да бъде променена. 21 | 22 | В случая, подавайки arr като `const int[]` компилаторът би дал съобщение за грешка ако се опитаме да променим `arr`. 23 | Всяка променлива може да е константа. 24 | 25 | ```cpp 26 | const double PI = 3.141592; // PI никога не се променя 27 | ``` 28 | 29 | Можем да използваме `const` за да определяме големината на масиви. 30 | ```cpp 31 | const int MAX_SIZE = 100; 32 | 33 | int main() 34 | { 35 | int arr[MAX_SIZE]; // това е ок 36 | } 37 | ``` 38 | Каква е разликата между `const` и `constexpr` обаче? В горния пример константата може да се разгледа като `constexpt`. Не всеки `const` служи като големина на масив. `const` казва, че стойността на променлива няма да се промени. Тази стойност може да е станала ясна и в runtime, но тя няма да се промени. `constexpr` казва, че стойността е ясна (може да де изчисли) още по време на компилация. 39 | 40 | ```cpp 41 | #include 42 | 43 | using namespace std; 44 | 45 | const int get() 46 | { 47 | int x = 0; 48 | cin >> x; 49 | 50 | return x; 51 | } 52 | 53 | int main() 54 | { 55 | const int size = get(); // size е const. 56 | size++; // грешка 57 | int arr[size]; // Грешка 58 | } 59 | ``` 60 | 61 | Въпреки, че size е `const` няма как да го използваме за размер, понеже стойността не е ясна по време на компилация. 62 | 63 | ## Обръщане на масив 64 | Да се напише функция ```cpp void reverse(int arr[], unsigned size)```, която обръща масив. 65 | 66 | Пример: 67 | 68 | Вход: [1, 4, 3, 5, 2] 69 | Изход: [2, 5, 3, 4, 1] 70 | 71 | ## Решето на Ератостен 72 | Искаме да създадем проста система, която получава от стандартния вход числа и ни казва дали са прости или не. 73 | 74 | Пример: 75 | > 1 76 | > Not prime 77 | > 2 78 | > Prime 79 | > 1024 80 | > Not prime 81 | > 145 82 | > Not prime 83 | 84 | Системата работи с числа от 1 до 1024. 85 | 86 | ## Двоично търсене 87 | Да се напише функция ```cpp int binarySearch(const int arr[], unsigned length, int elem)```. `arr` e сортиран масив, `length` e дължината му а elem е число което може да е в масива, но може и да не е. Очаква се да върнем индекса на числото в масива ако се среща и -1 ако не се. 88 | 89 | ## Позиционни бройни системи 90 | Бройните системи са метод за представяне на числа чрез краен брой символи наречени **цифри**. Познаваме десетичната бройна система. Защо се нарича десенична? 91 | 92 | $2345 = 2 * 10^3 + 3 * 10^2 + 4 * 10^1 + 5 * 10^0$ 93 | Ако си представим числото като наредена последователност от цифри $a = a_{n-1}, a_{n-2}, \dots, a_0$ то стойността му е $\sum_{i = 0}^{n-1} 10^i*a_{i}$. В случая позицията на цифрата играе роля, от там идва и името **позиционна**. За представяне на числата се използват 10 символа - от там десетична. 94 | 95 | По същия начин можем да дефинираме и двоична бройна система. Отново имаме $a = a_{n-1}, a_{n-2}, \dots, a_0$ само, че този път $a_i \in \{0, 1\}$ (т.е. използваме два символа). 96 | 97 | Намирането на стойността на числото се извършва по същия начин. Примерно $1101 = 1 * 2^3 + 1 * 2^2 + 0 * 2 + 1 * 2^0 = 13$. 98 | 99 | Предимството на двоичната бройна система е, че използва само два символа, което я прави по - лесна за обработка от машина. 100 | 101 | Осмичната бройна система, както и шестнаддесетичната също се използват на някои места. 102 | 103 | В шестнаддесетичната бройна система цифрите са от 0 до 9 както и символите от `a` до `f`. 104 | В езика С++: 105 | ```cpp 106 | #include 107 | using namespace std; 108 | 109 | int main() 110 | { 111 | int octal = 01230; // Когато има 0 пред числото то е записано в осмична бройна система 112 | cout << octal << endl; 113 | 114 | int hexadecimal = 0xC0FFEE; // Когато пред числото има 0x то е разглеждано като число в шестнаддесетична бройна система 115 | cout << hexadecimal << endl; 116 | } 117 | ``` 118 | 119 | Обобщавайки тази идея можем да имаме произволни к-ични позиционни бройни системи. 120 | 121 | ## 1s and 2s complement. Представяне на числа в паметта. signed/unsigned стойности. -------------------------------------------------------------------------------- /Seminar-07/reverse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | void swap(int& first, int& second) 6 | { 7 | int temp = first; 8 | first = second; 9 | second = temp; 10 | } 11 | 12 | void reverse(int arr[], unsigned length) 13 | { 14 | for (size_t i = 0; i < length / 2; i++) 15 | { 16 | swap(arr[i], arr[length - i - 1]); 17 | } 18 | 19 | } 20 | 21 | int main() 22 | { 23 | constexpr unsigned MAX_SIZE = 10; 24 | int arr[MAX_SIZE]; 25 | 26 | for (size_t i = 0; i < MAX_SIZE; i++) 27 | { 28 | cin >> arr[i]; 29 | } 30 | 31 | reverse(arr, MAX_SIZE); 32 | 33 | for (size_t i = 0; i < MAX_SIZE; i++) 34 | { 35 | cout << arr[i] << " "; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /Seminar-07/sieve_of_eratosthenes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | void initSieve(bool sieve[], unsigned length, bool value) 7 | { 8 | for (unsigned i = 0; i < length; i++) 9 | { 10 | sieve[i] = value; 11 | } 12 | } 13 | 14 | void mark(bool sieve[], unsigned length, unsigned from) 15 | { 16 | unsigned step = from; 17 | unsigned cell = 2 * from; 18 | 19 | while(cell <= length) 20 | { 21 | sieve[cell] = false; 22 | cell += from; 23 | } 24 | } 25 | 26 | void preprocessSieve(bool sieve[], unsigned length) 27 | { 28 | sieve[0] = sieve[1] = false; 29 | 30 | unsigned border = sqrt(length) + 1; 31 | 32 | for (unsigned i = 2; i <= border; i++) 33 | { 34 | if(sieve[i]) 35 | { 36 | mark(sieve, length, i); 37 | } 38 | } 39 | } 40 | 41 | void handleQuery(bool sieve[], unsigned length) 42 | { 43 | int input = 0; 44 | 45 | while(input >= 0) 46 | { 47 | cin >> input; 48 | 49 | if(input > length) 50 | { 51 | cout << "Invalid number" << endl; 52 | } 53 | else if(input < 0) 54 | { 55 | cout << "Goodbye" << endl; 56 | } 57 | else if(sieve[input]) 58 | { 59 | cout << "Number is prime" << endl; 60 | } 61 | else 62 | { 63 | cout << "Number is not prime" << endl; 64 | } 65 | } 66 | } 67 | 68 | int main() 69 | { 70 | constexpr unsigned SIEVE_MAX_LEN = 1024 + 1; 71 | bool sieve[SIEVE_MAX_LEN]; 72 | initSieve(sieve, SIEVE_MAX_LEN, true); 73 | preprocessSieve(sieve, SIEVE_MAX_LEN); 74 | handleQuery(sieve, SIEVE_MAX_LEN); 75 | } -------------------------------------------------------------------------------- /Seminar-08/numeric-systems.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | char toUpper(char ch) 4 | { 5 | if (ch >= 'a' && ch <= 'z') 6 | { 7 | return ch - ('a' - 'A'); 8 | } 9 | return ch; 10 | } 11 | 12 | char toLower(char ch) 13 | { 14 | if (ch >= 'A' && ch <= 'Z') 15 | { 16 | return ch + ('a' - 'A'); 17 | } 18 | 19 | return ch; 20 | } 21 | 22 | bool isDigit(char ch) 23 | { 24 | return (ch >= '0' && ch <= '9'); 25 | } 26 | 27 | // Error handling functions: 28 | void displayOutOfBound(unsigned number) 29 | { 30 | std::cout << "The passed number " << number << " exceeds the bounds of the array." << std::endl; 31 | } 32 | 33 | void displayInvalidDigit(char digit) 34 | { 35 | std::cout << "The passed digit " << digit << " is not well defined." << std::endl; 36 | } 37 | 38 | // Helper functions: 39 | char getDigit(unsigned digit) 40 | { 41 | if (digit < 10) 42 | { 43 | return '0' + digit; 44 | } 45 | else if (digit <= 36) 46 | { 47 | return 'A' + (digit - 10); 48 | } 49 | 50 | // '\0' is the char symbol represented as 0. 51 | return '\0'; 52 | } 53 | 54 | // Returns numeric value for digit represented as char. 55 | // Digits aren't only the standard digits [0..9] but we use 56 | // A to represent 10 57 | // B to represent 11 58 | // etc 59 | unsigned getNumber(char ch) 60 | { 61 | if (ch >= '0' && ch <= '9') 62 | { 63 | return ch - '0'; 64 | } 65 | else if (ch >= 'A' && ch <= 'Z') 66 | { 67 | return ch - 'A' + 10; 68 | } 69 | return -1; 70 | } 71 | 72 | // Версиите на решението което не "обръщат" числото в масива 73 | // т.е. когато имаме 6 в масива за двоична бройна система ще е записано {'1', '1', '0'} 74 | 75 | //"from" is in K numeral system. The function converts it to decimal (and the result is stored in an int) 76 | unsigned int fromRandomToDecimal(const char from[], size_t size, unsigned k) 77 | { 78 | int result = 0; 79 | 80 | for (int i = size - 1, mult = 1; i >= 0; i--, mult *= k) //we declare both the array iter (variable i) and mult in the loop 81 | result += getNumber(from[i]) * mult; 82 | return result; 83 | } 84 | 85 | 86 | //converts number from decimal to a number in K numeral system. The result is stored in the array "to" 87 | void fromDecimalToRandom(unsigned n, char to[], size_t size, unsigned k) 88 | { 89 | for (int arrayIter = size - 1; arrayIter >= 0; arrayIter--) //we fill the array backwards! 90 | { 91 | int ind = n % k; 92 | char current = getDigit(ind); 93 | to[arrayIter] = current; 94 | n /= k; 95 | } 96 | } 97 | 98 | // Версията на функциите които "обръщат" числото в масива. 99 | // т.е. когато имаме 6 в масив за двоична бройна система ще е записано {'0', '1', '1'} 100 | 101 | // returns the number of digits of the number 102 | // bufferSize is the size of the array 103 | int fromDecimal(unsigned number, unsigned base, char result[], unsigned bufferSize) 104 | { 105 | int size = 0; 106 | while (number != 0) 107 | { 108 | if (bufferSize <= size) 109 | { 110 | displayOutOfBound(number); 111 | return -1; 112 | } 113 | 114 | unsigned lastDigit = number % base; 115 | result[size] = getDigit(lastDigit); 116 | number = number / base; 117 | size++; 118 | } 119 | 120 | return size; 121 | } 122 | 123 | 124 | // returns the number represented from `number` in base `base` 125 | // in decimal. 126 | unsigned toDecimal(char number[], unsigned size, unsigned base) 127 | { 128 | unsigned result = 0; 129 | unsigned exponent = 1; 130 | 131 | for (unsigned i = 0; i < size; i++) 132 | { 133 | unsigned currentNumber = getNumber(number[i]); 134 | 135 | if (currentNumber == -1) 136 | { 137 | displayInvalidDigit(number[i]); 138 | return -1; 139 | } 140 | 141 | result += currentNumber * exponent; 142 | exponent *= base; 143 | } 144 | 145 | return result; 146 | } 147 | 148 | // Converts number in k-numeric sysytem to number of n-numeric system. 149 | // returns the number of digits of the new number. 150 | unsigned convertNumericSystems(char numberKNumeric[], unsigned sizeKNumeric, unsigned baseKNumeric, char numberNNumeric[], int sizeNNumeric, unsigned baseNNumeric) 151 | { 152 | unsigned decimalNumber = toDecimal(numberKNumeric, sizeKNumeric, baseKNumeric); 153 | unsigned numberOfDigits = fromDecimal(decimalNumber, baseNNumeric, numberNNumeric, sizeNNumeric); 154 | 155 | return numberOfDigits; 156 | } 157 | 158 | void showNumber(char number[], unsigned size, unsigned originalNumber, unsigned base) 159 | { 160 | std::cout << "The number " << originalNumber << " is "; 161 | for (int i = size - 1; i >= 0; i--) { std::cout << number[i]; } 162 | std::cout << " in base " << base << " numeric system" << std::endl; 163 | } 164 | 165 | int main() 166 | { 167 | constexpr unsigned NUMBER_MAX_LEN = 100; 168 | 169 | // Original number: 170 | unsigned number = 12648430; 171 | 172 | 173 | // Convert it to base 16: 174 | char hexadecimalNumber[NUMBER_MAX_LEN] = {}; 175 | unsigned resultLen = fromDecimal(number, 16, hexadecimalNumber, NUMBER_MAX_LEN); 176 | showNumber(hexadecimalNumber, resultLen, number, 16); 177 | 178 | 179 | // Convert the base 16 number to base 2: 180 | char binaryNumber[NUMBER_MAX_LEN] = {}; 181 | unsigned binaryNumberLength = convertNumericSystems(hexadecimalNumber, resultLen, 16, binaryNumber, NUMBER_MAX_LEN, 2); 182 | showNumber(binaryNumber, binaryNumberLength, number, 2); 183 | 184 | 185 | // Convert the base 2 to base 36: 186 | char baseThirtySixNumber[NUMBER_MAX_LEN] = {}; 187 | unsigned baseThirtySixLen = convertNumericSystems(binaryNumber, binaryNumberLength, 2, baseThirtySixNumber, NUMBER_MAX_LEN, 36); 188 | showNumber(baseThirtySixNumber, baseThirtySixLen, number, 36); 189 | 190 | 191 | // Get beck to the original number: 192 | unsigned originalNumber = toDecimal(binaryNumber, binaryNumberLength, 2); 193 | std::cout << "Started from " << number << " and got to " << originalNumber; 194 | } -------------------------------------------------------------------------------- /Seminar-09/readme.md: -------------------------------------------------------------------------------- 1 | # Девети семинар по увод в програмирането - 27.11.2023 2 | 3 | ## Побитови операции 4 | В последния семинар разгледахме как се представят целочислените типове и типът char в паметта. 5 | Стандартно типът `int` е 4 байта (1 байт е 8 бита - един бит приема стойности 0 и 1). Давайки различни стойности на 32-та байта с които разполагаме, ние кодираме различни числа в двоична бройна система. 6 | 7 | ```cpp 8 | int a = 0; // 00000000 00000000 00000000 00000000 9 | int b = 3; // 00000000 00000000 00000000 00000011 10 | int c = 5; // 00000000 00000000 00000000 00000101 11 | ``` 12 | 13 | Побитовите операции ни дават възможност да работим на ниво битове. Предимството на побитовите операции е, че са бързи и доста често можем компактно да пазим информация "скривайки" я в битовете на числото. 14 | 15 | ## Bitwise AND (&) - побитово `и` 16 | Ако `A` и `B` са битове, то: 17 | | A | B | A & B | 18 | |---|---|-------| 19 | | 0 | 0 | 0 | 20 | | 0 | 1 | 0 | 21 | | 1 | 0 | 0 | 22 | | 1 | 1 | 1 | 23 | 24 | А какво правим когато не работим само с един бит ами с число? Преизползваме логиката за всеки бит на числото! 25 | 26 | ``` 27 | 3 & 5 = 28 | 00000000 00000000 00000000 00000011 29 | & 30 | 00000000 00000000 00000000 00000101 31 | ----------------------------------- 32 | 00000000 00000000 00000000 00000001 33 | ``` 34 | Най - десния бит е единица понеже най - десния бит на първото число е 1 както и най - десния на второто. Нямаме други единици понеже нямаме други две позиции на които имаме единици. 35 | 36 | ## Bitwise OR (|) - побитово `или` 37 | Ако `A` и `B` са битове, то: 38 | | A | B | A \| B | 39 | |---|---|--------| 40 | | 0 | 0 | 0 | 41 | | 0 | 1 | 1 | 42 | | 1 | 0 | 1 | 43 | | 1 | 1 | 1 | 44 | 45 | Тук логиката е същата като побитово и. За всяка позиция прилагаме операцията. 46 | 47 | ``` 48 | 3 | 5 = 49 | 00000000 00000000 00000000 00000011 50 | | 51 | 00000000 00000000 00000000 00000101 52 | ----------------------------------- 53 | 00000000 00000000 00000000 00000111 54 | ``` 55 | Най - десните три бита са единици понеже на съответстващите им позиции, прилагайки операцията побитово и, получаваме 1. 56 | 57 | ## Bitwise XOR (^) 58 | Ако `A` и `B` са битове, то: 59 | | A | B | A ^ B | 60 | |---|---|-------| 61 | | 0 | 0 | 0 | 62 | | 0 | 1 | 1 | 63 | | 1 | 0 | 1 | 64 | | 1 | 1 | 0 | 65 | 66 | Отново - аналогична идея: 67 | ``` 68 | 3 ^ 5 = 69 | 00000000 00000000 00000000 00000011 70 | ^ 71 | 00000000 00000000 00000000 00000101 72 | ----------------------------------- 73 | 00000000 00000000 00000000 00000110 74 | ``` 75 | Тук най - десния бит е 0 понеже 1 ^ 1 = 0 в таблицата. 76 | 77 | Какво се случва когато направим a ^ a? 78 | ## Bitwise NOT (~) 79 | За разлика от предните оператори - този е унарен. Приема едно число и просто обръща битовете му: 80 | Ако `A` е бит, то: 81 | | A | ~A | 82 | |---|----| 83 | | 0 | 1 | 84 | | 1 | 0 | 85 | 86 | Пример: 87 | ``` 88 | ~5 = ~00000000 00000000 00000000 00000101 89 | = 11111111 11111111 11111111 11111010 90 | = -6 91 | ``` 92 | 93 | Представяйки числата в 2s complement, ако `a` е число можем да получим `-a` по следния начин: 94 | ```cpp 95 | int a = 5; 96 | int b = ~a + 1; // Обърни битовете на а и добави 1 97 | ``` 98 | 99 | ## Bitwise shifting (<<, >>) - побитово отместване. 100 | Нека разгледаме дясно побитово отместване >>. То приема число и брой битове които ще "отместим". 101 | 102 | Интуитивно отместването е все едно "придърпваме" битовете на числото надясно (или наляво). 103 | 104 | Получава се нещо такова за дясно отместване: 105 | 106 | ![](https://cdn.getmidnight.com/84f7b02a8128f5f5775611244c24b941/2021/02/ezgif.com-gif-maker--2--1.gif) 107 | 108 | И нещо такова за ляво: 109 | 110 | ![](https://cdn.getmidnight.com/84f7b02a8128f5f5775611244c24b941/2021/02/ezgif.com-gif-maker--1-.gif) 111 | 112 | Разбира се, битовете не са усмихнати, нито цветни. Нека разгледаме по - подробно примера: 113 | 114 | ```cpp 115 | // В началото числото е 0100 1101. Последователно падат 4 бита от дясната страна. 116 | #include 117 | 118 | int main() 119 | { 120 | int a = 77; // 01001101 121 | a = a >> 1; // Пада 1 бит 122 | a = a >> 1; // Пада 1 бит 123 | a = a >> 1; // Пада 1 бит 124 | a = a >> 1; // Пада 1 бит 125 | // Паднаха 4 бита 126 | std::cout << a << std::endl; // Това трябва да е точно числото 0000 0100 което е 4. 127 | 128 | // За да не шифтваме 4 пъти (моля ви не го правете) можем просто да напишем 129 | int b = 77; 130 | b = b >> 4; // отмества 4 бита 131 | std::cout << b; // Отново 4 132 | } 133 | ``` 134 | Лявото отместване е аналогично - този път обаче битовете "падат" от другата страна. 135 | ```cpp 136 | #include 137 | 138 | int main() 139 | { 140 | int a = 16; // 0001 0000 141 | a = a << 1; // 0010 0000 142 | a++; // 0010 0001 143 | a = a << ; // 0100 0010 144 | } 145 | ``` 146 | 147 | Какво ще изведе следния код: 148 | 149 | ```cpp 150 | #include 151 | 152 | int main() 153 | { 154 | std::cout << (-1 >> 1); 155 | } 156 | ``` 157 | Извежда -1. Както видяхме: 158 | ``` 159 | -1 = 11111111 11111111 11111111 11111111 // обърнете битовете и добавете 1. Колко получавате? 160 | ``` 161 | След като отместим един път надясно продължаваме да имаме същата стойност. Това е понеже дясното отместване в С++ запазва бита за знака. Има вариация на дясното отместване, която не го запазва. 162 | 163 | ## Примери 164 | * Проверка за четност на число. 165 | * Бърза степен на двойката. 166 | * Проверка дали число е степен на двойката. 167 | 168 | ## Статични двумерни масиви. Матрици. 169 | Когато разглеждахме масивите видяхме, че можем да имаме масиви от различни типове. Можем да имаме масиви от тип `int` а можем и да имаме масиви от тип `char`. А възможно ли е да имаме масиви от масиви? 170 | Да и точно това представляват двумерните масиви. 171 | ```cpp 172 | #include 173 | 174 | int main() 175 | { 176 | int matrix[3][3]; // Масив от три масива 177 | 178 | int initializedMatrix[3][3] = { 179 | {1, 0, 0}, 180 | {0, 1, 0}, 181 | {0, 0, 1} 182 | }; // Това е валиден синтаксис 183 | 184 | int arr[][]; // това е невалиден синтаксис. Трябва да подадем измеренията на масива. 185 | } 186 | ``` 187 | Можем ли да имаме масив от масиви от масиви? Разбирасе, че можем. Можем да продължаваме логиката колкото искаме (или колкото памет имаме), но ще се ограничим до двумерни масиви (още наричани матрици). 188 | 189 | ## Задачи за самостоятелна подготовка 190 | Да се реализира функция ```void printAllSubsets(int arr[], size_t size)``` която принтира всички подмножества на множество представено чрез масив. 191 | 192 | Пример: 193 | 194 | arr = {1, 2} 195 | 196 | size = 2 197 | 198 | Изход: 199 | 200 | {} 201 | {1} 202 | {2} 203 | {1, 2} 204 | 205 | Hint: Всяко подмножество може да се представи чрез битвектор, 1 означава, че елементът се включва а 0, че не се. Обхождайки всички битвектори, представящи подмножества (тези с дължина size) всеки от тях ще Ви представлява точно едно подмножество. 206 | 207 | Елементите на масива не са повече от 32. 208 | -------------------------------------------------------------------------------- /Seminar-09/tic-tac-toe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | constexpr size_t BOARD_SIZE = 3; 4 | constexpr size_t PLAYERS_COUNT = 2; 5 | const size_t WINNING_LINE_LENGTH = 3; 6 | const int EMPTY_CELL = -1; 7 | 8 | void initPlayerSymbols(char playerSymbols[PLAYERS_COUNT]) 9 | { 10 | for (size_t i = 0; i < PLAYERS_COUNT; i++) 11 | { 12 | std::cout << "Enter symbol for player " << i << ": "; 13 | std::cin >> playerSymbols[i]; 14 | } 15 | } 16 | 17 | void clearBoard(int board[BOARD_SIZE][BOARD_SIZE], int defaultValue) 18 | { 19 | for (size_t i = 0; i < BOARD_SIZE; i++) 20 | { 21 | for (size_t j = 0; j < BOARD_SIZE; j++) 22 | { 23 | board[i][j] = defaultValue; 24 | } 25 | } 26 | } 27 | 28 | bool validIndex(unsigned x, unsigned y) 29 | { 30 | return x < BOARD_SIZE && y < BOARD_SIZE; 31 | } 32 | 33 | bool isValidMove(const int board[BOARD_SIZE][BOARD_SIZE], unsigned x, unsigned y) 34 | { 35 | return validIndex(x, y) && (board[x][y] == EMPTY_CELL); 36 | } 37 | 38 | void getCell(const int board[BOARD_SIZE][BOARD_SIZE], unsigned& x, unsigned& y, unsigned player) 39 | { 40 | bool failed = false; 41 | do 42 | { 43 | if(failed) 44 | { 45 | std::cout << "Invalid coordinates!" << std::endl; 46 | } 47 | 48 | std::cout << "Enter " << player << " move: "; 49 | std::cin >> x >> y; 50 | 51 | failed = true; 52 | } while (!isValidMove(board, x, y)); 53 | } 54 | 55 | void makeMove(int board[BOARD_SIZE][BOARD_SIZE], char symbol, unsigned player, unsigned& xCoord, unsigned& yCoord) 56 | { 57 | getCell(board, xCoord, yCoord, player); 58 | board[xCoord][yCoord] = player; 59 | } 60 | 61 | void printBoard(const int board[BOARD_SIZE][BOARD_SIZE], const char playerSymbols[PLAYERS_COUNT]) 62 | { 63 | system("clear"); 64 | std::cout << " "; 65 | 66 | for (size_t i = 0; i < BOARD_SIZE; i++) 67 | { 68 | std::cout << " " << i; 69 | } 70 | 71 | std::cout << std::endl; 72 | 73 | for (size_t i = 0; i < BOARD_SIZE; i++) 74 | { 75 | std::cout << i << " "; 76 | for (size_t j = 0; j < BOARD_SIZE; j++) 77 | { 78 | if(board[i][j] == EMPTY_CELL) 79 | { 80 | std::cout << "[#]"; 81 | } 82 | else 83 | { 84 | std::cout << "[" << playerSymbols[board[i][j]] << "]"; 85 | } 86 | } 87 | std::cout << std::endl; 88 | } 89 | } 90 | 91 | unsigned getConsecutiveCount(const int board[BOARD_SIZE][BOARD_SIZE], unsigned x, unsigned y, unsigned stepX, unsigned stepY) 92 | { 93 | unsigned result = 0; 94 | int currentSymbol = board[x][y]; 95 | 96 | unsigned tempX = x; 97 | unsigned tempY = y; 98 | 99 | // Those while loops can (and should) be inside another function. 100 | // Keeping them inside this one, hoping to make the idea of the function easier to understand. 101 | while (validIndex(tempX, tempY) && board[tempX][tempY] == currentSymbol) 102 | { 103 | ++result; 104 | tempX += stepX; 105 | tempY += stepY; 106 | } 107 | 108 | tempX = x - stepX; 109 | tempY = y - stepY; 110 | 111 | while (validIndex(tempX, tempY) && board[tempX][tempY] == currentSymbol) 112 | { 113 | ++result; 114 | tempX -= stepX; 115 | tempY -= stepY; 116 | } 117 | 118 | return result; 119 | } 120 | 121 | bool isWinningMove(const int board[BOARD_SIZE][BOARD_SIZE], unsigned x, unsigned y) 122 | { 123 | return (getConsecutiveCount(board, x, y, 1, 0) >= WINNING_LINE_LENGTH) || 124 | (getConsecutiveCount(board, x, y, 0, 1) >= WINNING_LINE_LENGTH) || 125 | (getConsecutiveCount(board, x, y, -1, 1) >= WINNING_LINE_LENGTH) || 126 | (getConsecutiveCount(board, x, y, 1, 1) >= WINNING_LINE_LENGTH); 127 | } 128 | 129 | void play(int board[BOARD_SIZE][BOARD_SIZE], const char playerSymbols[PLAYERS_COUNT]) 130 | { 131 | unsigned playerPlaying = 0; 132 | unsigned x = 0; 133 | unsigned y = 0; 134 | unsigned movesCount = 0; 135 | 136 | int winningPlayer = -1; 137 | 138 | while(true) 139 | { 140 | printBoard(board, playerSymbols); 141 | makeMove(board, playerSymbols[playerPlaying], playerPlaying, x, y); 142 | ++movesCount; 143 | 144 | bool cellsLeft = !(movesCount == (BOARD_SIZE * BOARD_SIZE)); 145 | 146 | if(isWinningMove(board, x, y) || !cellsLeft) 147 | { 148 | winningPlayer = cellsLeft ? playerPlaying : -1; 149 | break; 150 | } 151 | 152 | ++playerPlaying; 153 | 154 | playerPlaying %= PLAYERS_COUNT; 155 | } 156 | printBoard(board, playerSymbols); 157 | 158 | if(winningPlayer == -1) 159 | { 160 | std::cout << "Draw" << std::endl; 161 | } 162 | else 163 | { 164 | std::cout << "Winner is player " << winningPlayer << "!!!" << std::endl; 165 | } 166 | } 167 | 168 | int main() 169 | { 170 | char playerSymbols[PLAYERS_COUNT]; 171 | int board[BOARD_SIZE][BOARD_SIZE]; 172 | 173 | initPlayerSymbols(playerSymbols); 174 | clearBoard(board, EMPTY_CELL); 175 | play(board, playerSymbols); 176 | } -------------------------------------------------------------------------------- /Seminar-10/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int variable = 14; 6 | std::cout << "The address of variable is: " << &variable << std::endl; 7 | int *variablePtr = &variable; 8 | 9 | std::cout << "Variable pointer address is: " << variablePtr << std::endl; 10 | std::cout << "Variable value: " << variable << " variable ptr value: " << *variablePtr << std::endl; 11 | std::cout << "Changing the variable ptr value to 10..." << std::endl; 12 | *variablePtr = 10; 13 | std::cout << "Variable value: " << variable << " variable ptr value: " << *variablePtr << std::endl; 14 | } -------------------------------------------------------------------------------- /Seminar-10/getUniqueElement.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Какви биха били различните идеи за решение? 4 | // 1. Започваме да броим по колко пъти се среща всеки елемент - този който се среща веднъж бива върнат. 5 | // 2. Сортираме масива и намираме елемента, който си няма другарче. 6 | 7 | // Може би най - елегантния начин това да бъде решено е следния 8 | int getUniqueElement(const int arr[], unsigned size) 9 | { 10 | int buff = 0; 11 | for (size_t i = 0; i < size; i++) 12 | { 13 | buff = buff ^ arr[i]; 14 | } 15 | return buff; 16 | } 17 | // Защо това работи? 18 | // 1. a ^ a = 0 винаги имайки XOR на два еднакви бита той дава 0. 19 | // 2. a ^ b = b ^ a това следва гледайки таблицата на XOR. 20 | // 3. a ^ b ^ a = a ^ a ^ b = 0 ^ b = b Първото равенство го получихме използвайки 2. Второто използвайки 1. 21 | // Използвайки тази интуиция еднаквите числа ще се развият до нули и ще остане само това което няма другарче. 22 | 23 | int main() 24 | { 25 | constexpr unsigned EXAMPLE_SIZE = 9; 26 | int example[EXAMPLE_SIZE] = {1, 10, -3, 1, 4, 145, -3, 10, 4}; 27 | std::cout << getUniqueElement(example, EXAMPLE_SIZE); 28 | } -------------------------------------------------------------------------------- /Seminar-10/media/seg-fault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Introduction-to-programming/e9bfa9d3381bbc9444d5679d2303e6de7d551ac3/Seminar-10/media/seg-fault.png -------------------------------------------------------------------------------- /Seminar-10/my-strcpy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Копира source в destination. 4 | // След изпълнение на тази функция destination и source 5 | // имат една и съща стойност! 6 | char* myStrcpy(char* destination, const char* source) 7 | { 8 | char* toReturn = destination; 9 | 10 | while(*source != '\0') 11 | { 12 | *destination = *source; 13 | ++source; 14 | ++destination; 15 | } 16 | 17 | *destination = '\0'; 18 | return toReturn; 19 | } 20 | 21 | int main() 22 | { 23 | char destination[] = "This is some text."; 24 | const char* src = "New text value"; 25 | 26 | char* result = myStrcpy(destination, src); 27 | 28 | // Можем да използваме result или destination и двете сочат към едно и също място. 29 | 30 | std::cout << "After we copy source to destination the new value of destination is " << destination << " or we can use the pointer " 31 | << "returned by the function: " << result; 32 | } -------------------------------------------------------------------------------- /Seminar-10/mystrlen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | size_t myStrlen(const char* str) 4 | { 5 | size_t counter = 0; 6 | while (*str != '\0') 7 | { 8 | ++counter; 9 | ++str; 10 | } 11 | return counter; 12 | } 13 | 14 | int main() 15 | { 16 | const char* str = "Hello string functionallity"; 17 | std::cout << "The length of the text \"" << str << "\" is " << myStrlen(str); 18 | } -------------------------------------------------------------------------------- /Seminar-10/printSubsets.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Провери дали битът на позиция elementPos e вдигнат в currentSubset 4 | bool elementContainedInCurrentSubset(unsigned currentSubset, unsigned elementPos) 5 | { 6 | return ((currentSubset & (1 << elementPos)) != 0); 7 | } 8 | 9 | void printSubset(const int set[], unsigned size, unsigned currentSubset) 10 | { 11 | std::cout << "{ "; 12 | for (size_t i = 0; i < size; i++) 13 | { 14 | if(elementContainedInCurrentSubset(currentSubset, i)) 15 | { 16 | std::cout << set[i] << " "; 17 | } 18 | } 19 | std::cout << "}" << std::endl; 20 | } 21 | 22 | // Защо това работи? 23 | // Броя на всички подмножества на множество с n eлемента е 2^n. 24 | 25 | // Бележка: В множествата елементите не са наредени. Все пак в случая представяме множеството като масив, 26 | // и за да улесним разбирането на задачата можем да говорим за "елемент на позиция i". 27 | 28 | // Подмножество можем да представим като последователност от нули и единици - ако имаме единица на позиция i 29 | // то елементът на позиция i се среща в подмножеството. В противен случай не се среща. 30 | 31 | // Примерно: 32 | // {1, 2, 3, 4, 5} 33 | // 0 1 1 0 1 34 | // това представлява подмножеството {2, 3, 5}. 35 | 36 | // Обхождайки всяка такава последователност от нули и единици ни води до всички подмножества. 37 | // Това обхождане правим като обхождаме числата от 0 до 2^size. 38 | // За всяко число - ако битът на позиция i e вдигнат изведи елемента в противен случай недей. 39 | void printSubsets(const int set[], unsigned size) 40 | { 41 | size_t subsetsCount = (1 << size); // Бързо 2^size. 42 | for (size_t currentSubset = 0; currentSubset < subsetsCount; currentSubset++) 43 | { 44 | printSubset(set, size, currentSubset); 45 | } 46 | } 47 | 48 | int main() 49 | { 50 | constexpr unsigned SET_SIZE = 5; 51 | int set[SET_SIZE] = {1, 2, 3, 4, 5}; 52 | printSubsets(set, SET_SIZE); 53 | } -------------------------------------------------------------------------------- /Seminar-10/readme.md: -------------------------------------------------------------------------------- 1 | # Десети семинар по увод в програмирането - 04.12.2023 2 | 3 | ## Задача първа - побитови операции. 4 | Да се напише функция ```int getUniqueElement(const int arr[], unsigned size);``` която приема масив, в който всеки елемент се среща точно два пъти освен един. Функцията връща този елемент. 5 | 6 | ## Задача втора - побитови операции. 7 | Да се реализира функция ```void printAllSubsets(int arr[], size_t size)``` която принтира всички подмножества на множество представено чрез масив. 8 | 9 | Пример: 10 | При 11 | 12 | arr = {1, 2} 13 | 14 | size = 2 15 | 16 | имаме изход 17 | 18 | { } 19 | { 1 } 20 | { 2 } 21 | { 1 2 } 22 | 23 | ## Указатели. 24 | Когато говорихме за променливи, споменахме, че всяка променлива е записана в паметта. За да достъпим стойността ѝ, четем от даден адрес. За да видим адреса на променлива можем, да използваме оператора `&`. 25 | 26 | ```cpp 27 | #include 28 | 29 | int main() 30 | { 31 | int variable = 14; 32 | std::cout << "The address of variable is: " << &variable; 33 | } 34 | ``` 35 | 36 | Това би извело 37 | ``` 38 | The address of variable is: 0xed1cbffa5c 39 | ``` 40 | 41 | Адресът, за наше удобство, се представя в шестнаддесетична бройна система. В езикът С++ има променливи, които пазят в себе си адреси. 42 | 43 | ``` 44 | Указател - Променлива, която пази адрес. 45 | ``` 46 | 47 | Синтаксисът е следния: 48 | ```cpp 49 | int* ptr; // Валидно, създава указател, който сочи към адреса на променлива от тип int 50 | int *ptr; // Това също е валиден синтаксис. 51 | ``` 52 | 53 | ## Особености на указателите. 54 | Има няколко особености на указателите, които ги правят гъвкави. 55 | 1. Указателят може да сочи към "нищото" (за разлика от референцията). Синтаксис: 56 | ```cpp 57 | int* ptr = nullptr; 58 | ``` 59 | 2. Указателят може да се пренасочва (за разлика от референцията). 60 | 61 | ```cpp 62 | int variable = 0; 63 | int secondVariable = 1; 64 | int* ptr = nullptr; 65 | ptr = &variable; // Указателят вече сочи към variable. 66 | ptr = &secondVariable; // Указателят вече сочи към secondVariable. 67 | ``` 68 | 3. Можем да четем и променяме паметта, към която указателите сочат (иначе защо да ги имаме?). 69 | ```cpp 70 | int variable = 0; 71 | int* ptr = &variable; 72 | *ptr = 10; // Чрез оператора * достъпваме стойността записана в адреса на указателя. 73 | cout << variable << " " << *ptr; // Това сега ще изведе 10 и 10. 74 | ``` 75 | 76 | Повтаряме: 77 | ``` 78 | Чрез оператора * достъпваме стойността, записана в адреса на указателя. 79 | ptr - адрес. 80 | *ptr - стойността, записана в този адрес. 81 | ``` 82 | 83 | ## Какво може да се обърка? 84 | Програмата, която пишем, си има памет. В тази памет тя държи променливите (и още други неща, нужни за изпълнението ѝ). Какво става обаче, когато се опитаме да четем в памет, която не принадлежи на процеса? 85 | 86 | Неправилната работа с указателите води до грешки. Най-често срещаната е следната: 87 | ```cpp 88 | int* max(int* a, int* b) 89 | { 90 | return *a > *b ? a : b; 91 | } 92 | 93 | int main() 94 | { 95 | int* a = nullptr; 96 | int* b = nullptr; 97 | // Тук се дават някакви стойности на а и b. За този код съм на 100% сигурен, че дава стойности. 98 | // Толкова съм сигурен, че дори няма да проверя дали случайно а или b са останали без стойност. Какво може да се обърка? 99 | int* bigger = max(a, b); 100 | } 101 | ``` 102 | ![](media/seg-fault.png) 103 | 104 | Какво се случи тук и защо гръмна? 105 | Да се опиташ да прочетеш стойност от `nullptr` се счита за грешка. В коментарите пише, че сме сигурни, че не са, но грешки винаги стават. 106 | 107 | ## Връзката между указателите и масивите. Указателна аритметика. 108 | ```cpp 109 | #include 110 | 111 | int main() 112 | { 113 | int arr[3]; 114 | for(int i = 0; i < 3; i++) 115 | { 116 | std::cout << &arr[i] << " "; 117 | } 118 | } 119 | ``` 120 | 121 | Това извежда: 122 | ``` 123 | 0x4c56dffdd0 0x4c56dffdd4 0x4c56dffdd8 124 | ``` 125 | Заглеждайки се в адресите (представени като числа в шестнаддесетична бройна система), те са последователни. Не само това, но и разликата между тях е 4. Това не трябва да ни притеснява, дори трябва да е очаквано. Паметта при масивите е **последователна**. 126 | 127 | За наше удобство arr може да се преобразува към указател. 128 | ```cpp 129 | int* arrPtr = arr; 130 | ``` 131 | 132 | Разбира се, **масивът не е указател** (не можем да пренасочим масив е най-лесната аргументация). Но все пак масивът може да се преобразува към указател и това е с причина. 133 | Указателната аритметика ни позволява да се **движим по последователна памет, имайки указател към елемент.** 134 | 135 | ```cpp 136 | #include 137 | 138 | int main() 139 | { 140 | int arr[4] = {1, 2, 3, 4}; 141 | int* arrPtr = arr; 142 | 143 | std::cout << *arrPtr; // Извежда първия елемент от масива - 1 144 | int* ptrSecond = arrPtr + 1; // Указател към втория елемент от масива 145 | 146 | std::cout << *ptrSecond; // Извежда втория елемент от масива - 2. 147 | 148 | std::cout << *(arrPtr + 3); // Извежда четвъртия елемент от масива - 4 149 | 150 | // Тоест ptr + i - премества ptr към i-тия елемент от масива. 151 | } 152 | ``` 153 | Аналогично, имайки указател към третия елемент можем да се върнем назад изваждайки 1 (примерно). 154 | 155 | Всъщност: 156 | 157 | ```cpp 158 | int arr[10]; 159 | int* arrPtr = arr; 160 | 161 | arr[2] е същото като *(arr + 2) е същото като *(arrPtr + 2) e същото като arrPtr[2] 162 | ``` 163 | Когато индексираме, компилатора прави точно ```cpp arr[i] = *(arr + i)```. 164 | 165 | ## Въведение в символните низове 166 | Работата със текст е неизбежна. Типът, представляващ един символ, е `char`. Поглеждайки реалния свят (примерно този документ) хората рядко използват само един символ. Те използват множество от символи, а за това имаме ли тип? 167 | Разбира се, използваме масив от символи и го наричаме **символен низ** (или още string). 168 | 169 | ```cpp 170 | char str[] = {'t', 'e', 's', 't', '\0'}; 171 | char str2[] = "test"; // еквивалентно на str, просто по - лесно за запис. 172 | char str3[7] = "test"; 173 | ``` 174 | Това е сравнително ясно, защо обаче ни е тази нула в края на низа? Ами нулата ни казва къде свършва текста. Нека си представим, че искаме да прочетем този текст. Четенето става като просто четем, докато не стигнем до **терминиращата нула**. Това са така наречените **null terminated strings**. 175 | 176 | ![enter image description here](https://i.ibb.co/ZmRwt6R/Untitled-Diagram-drawio-5.png) 177 | 178 | ## Примери 179 | * unsigned strlen(const char*); - дължина на низ 180 | * char* strcpy(char* destination, char* source); - Копира source в destination 181 | * char* strcat(char* destination, char* source); - Конкатенира два низа. 182 | * int strcmp(const char* str1, const char* str2); - Сравнява два низа лексикографски. 183 | * Търсене на дума в стринг. -------------------------------------------------------------------------------- /Seminar-11/dynamic memory examples/getLowerAndUpper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool isLower(char ch) 4 | { 5 | return ch >= 'a' && ch <= 'z'; 6 | } 7 | 8 | bool isUpper(char ch) 9 | { 10 | return ch >= 'A' && ch <= 'Z'; 11 | } 12 | 13 | unsigned countLower(const char* text) 14 | { 15 | unsigned counter = 0; 16 | 17 | while (*text != '\0') 18 | { 19 | if (isLower(*text)) 20 | { 21 | ++counter; 22 | } 23 | ++text; 24 | } 25 | return counter; 26 | } 27 | 28 | unsigned countUpper(const char* text) 29 | { 30 | unsigned counter = 0; 31 | 32 | while (*text != '\0') 33 | { 34 | if (isUpper(*text)) 35 | { 36 | ++counter; 37 | } 38 | ++text; 39 | } 40 | return counter; 41 | } 42 | 43 | 44 | void getLowerAndUpperLetters(const char* text, char*& lower, char*& upper) 45 | { 46 | if (text == nullptr) 47 | return; 48 | unsigned lowerSymbols = countLower(text) + 1; 49 | unsigned upperSymbols = countUpper(text) + 1; 50 | 51 | lower = new char[lowerSymbols]; // terminating zero 52 | upper = new char[upperSymbols]; 53 | 54 | unsigned lowerIter = 0; 55 | unsigned upperIter = 0; 56 | 57 | while (*text != '\0') 58 | { 59 | if (isLower(*text)) 60 | { 61 | lower[lowerIter++] = *text; 62 | } 63 | else if (isUpper(*text)) 64 | { 65 | upper[upperIter++] = *text; 66 | } 67 | ++text; 68 | } 69 | 70 | lower[lowerIter] = '\0'; 71 | upper[upperIter] = '\0'; 72 | } 73 | 74 | int main() 75 | { 76 | const char* ch = "145textTEXTtext"; 77 | char* lower, * upper; 78 | getLowerAndUpperLetters(ch, lower, upper); 79 | std::cout << lower << " " << upper; 80 | 81 | delete[] lower; 82 | delete[] upper; // !!! 83 | } -------------------------------------------------------------------------------- /Seminar-11/dynamic memory examples/merge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void print(const int* arr, unsigned size) 4 | { 5 | for (size_t i = 0; i < size; i++) 6 | { 7 | std::cout << arr[i] << " "; 8 | } 9 | std::cout << std::endl; 10 | } 11 | 12 | int* merge(const int* left, unsigned leftSize, const int* right, unsigned rightSize) 13 | { 14 | int* toReturn = new int[leftSize + rightSize]; 15 | 16 | unsigned leftIter = 0; 17 | unsigned rightIter = 0; 18 | unsigned resultIter = 0; 19 | 20 | while(leftIter < leftSize && rightIter < rightSize) 21 | { 22 | if(left[leftIter] < right[rightIter]) 23 | { 24 | toReturn[resultIter++] = left[leftIter++]; 25 | } 26 | else 27 | { 28 | toReturn[resultIter++] = right[rightIter++]; 29 | } 30 | } 31 | 32 | while(leftIter < leftSize) 33 | { 34 | toReturn[resultIter++] = left[leftIter++]; 35 | } 36 | 37 | while(rightIter < rightSize) 38 | { 39 | toReturn[resultIter++] = right[rightIter++]; 40 | } 41 | 42 | return toReturn; 43 | } 44 | 45 | int main() 46 | { 47 | int first[7] = {1, 2, 4, 8, 16, 32, 64}; 48 | int second[9] = {1, 3, 7, 10, 11, 29, 69, 71, 100}; 49 | 50 | int* merged = merge(first, 7, second, 9); 51 | 52 | print(merged, 16); 53 | 54 | // !!!! 55 | delete[] merged; 56 | } -------------------------------------------------------------------------------- /Seminar-11/readme.md: -------------------------------------------------------------------------------- 1 | # Единадесети семинар по увод в програмирането - 11.12.2023 2 | 3 | 4 | ## Показани примери за стрингове: 5 | * Броене на символ в текст 6 | * Намиране на текст в текст. 7 | * Конкатениране на два низа 8 | * Сравняване на два низа 9 | * От низ в число. 10 | 11 | ## Разгледани теми: 12 | * Адресно пространство на процес (stack и heap). 13 | * Какво се пази на стека. 14 | * Какво се пази на heap-a. 15 | * Защо имаме нужда от динамична памет. Кога я използваме и кога не. Предимства и недостатъци. 16 | * Синтаксис: ``` operator new``` и ```operator delete```. 17 | * Как се случват отечките: забравен ```delete``` не трием памет върната от функция, некоректно използван ```delete``` 18 | 19 | ## Показани примери за динамична памет 20 | * Да се напише функция която приема стринг и връща два стринга, единия състоящ се само от малките му букви а другия само от главните му. 21 | 22 | -------------------------------------------------------------------------------- /Seminar-11/string examples/count-occurances.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned countSymbolOccurances(const char* text, char symbol) 4 | { 5 | unsigned counter = 0; 6 | while(*text != '\0') 7 | { 8 | if(*text == symbol) 9 | { 10 | ++counter; 11 | } 12 | ++text; 13 | } 14 | return counter; 15 | } 16 | 17 | int main() 18 | { 19 | const char* text = "this is my sample text"; 20 | std::cout << countSymbolOccurances(text, 't'); 21 | } -------------------------------------------------------------------------------- /Seminar-11/string examples/find-in-text.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool isPreffix(const char* str1, const char* str2) 4 | { 5 | while(*str2 != '\0' && *str1 == *str2) 6 | { 7 | ++str1; ++str2; 8 | } 9 | 10 | return (*str2 == '\0'); 11 | } 12 | 13 | const char* findInText(const char* text, const char* pattern) 14 | { 15 | while(*text != '\0') 16 | { 17 | if(isPreffix(text, pattern)) 18 | { 19 | return text; 20 | } 21 | ++text; 22 | } 23 | 24 | return nullptr; 25 | } 26 | 27 | int main() 28 | { 29 | const char* text = "Hello world"; 30 | const char* pattern = "world"; 31 | 32 | std::cout << findInText(text, pattern); 33 | } -------------------------------------------------------------------------------- /Seminar-11/string examples/myatoi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool isDigit(char ch) 5 | { 6 | return (ch >= '0' && ch <= '9'); 7 | } 8 | 9 | int myAtoi(const char* string) 10 | { 11 | if(string == nullptr) 12 | { 13 | return 0; 14 | } 15 | 16 | bool isNegative = (*string == '-'); 17 | if(isNegative) ++string; 18 | 19 | if(*string == '+') ++string; 20 | 21 | int number = 0; 22 | while(*string != '\0') 23 | { 24 | if(!isDigit(*string)) return 0; 25 | 26 | number *= 10; 27 | number += *string - '0'; 28 | ++string; 29 | } 30 | 31 | return isNegative ? -number : number; 32 | } 33 | 34 | int main() 35 | { 36 | } -------------------------------------------------------------------------------- /Seminar-11/string examples/mystrcat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | char* myStrcat(char* destination, const char* source) 4 | { 5 | if(destination == nullptr || source == nullptr) 6 | { 7 | return nullptr; 8 | } 9 | 10 | char* toReturn = destination; 11 | while(*destination != '\0') 12 | { 13 | ++destination; 14 | } 15 | 16 | while (*source != '\0') 17 | { 18 | *destination = *source; 19 | ++destination; 20 | ++source; 21 | } 22 | *destination = '\0'; 23 | 24 | return toReturn; 25 | } 26 | 27 | int main() 28 | { 29 | char buff[100] = "Hello, my name is "; 30 | const char* name = "Stoycho"; 31 | char* result = myStrcat(buff, name); 32 | std::cout << buff; // еквивалентно на std::cout << result 33 | } -------------------------------------------------------------------------------- /Seminar-11/string examples/mystrcmp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // сравнява два стринга лексикографски 4 | // -1 означава, че str1 < str2 5 | // 0 означава, че са равни 6 | // 1 означава, че str1 > str2 7 | int myStrcmp(const char* str1, const char* str2) 8 | { 9 | if(str1 == nullptr || str2 == nullptr) 10 | { 11 | return 0; // some error value 12 | } 13 | 14 | while (*str1 != '\0' && *str2 != '\0' && *str1 == *str2) 15 | { 16 | ++str1; ++str2; 17 | } 18 | // Тук можем да спестим тези if конструкции 19 | // return (*str1 > *str2) - (*str1 < *str2); защо това работи??? 20 | 21 | if(*str1 > *str2) 22 | { 23 | return 1; 24 | } 25 | 26 | if(*str1 < *str2) 27 | { 28 | return -1; 29 | } 30 | 31 | return 0; 32 | } 33 | 34 | int main() 35 | { 36 | const char* firstString = "Hello, my name is Stoycho"; 37 | const char* secondString = "Hello, my name is Yavor"; 38 | 39 | std::cout << myStrcmp(firstString, secondString); 40 | } -------------------------------------------------------------------------------- /Seminar-12/censor-numbers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned myStrlen(const char* text) 4 | { 5 | if (text == nullptr) { return 0; } 6 | 7 | unsigned count = 0; 8 | while (*text != '\0') 9 | { 10 | ++count; 11 | ++text; 12 | } 13 | return count; 14 | } 15 | 16 | bool isDigit(char ch) 17 | { 18 | return ch >= '0' && ch <= '9'; 19 | } 20 | 21 | unsigned countDigits(const char* text) 22 | { 23 | if(text == nullptr) { return 0; } 24 | unsigned count = 0; 25 | while (*text != '\0') 26 | { 27 | if (isDigit(*text)) 28 | { 29 | ++count; 30 | } 31 | ++text; 32 | } 33 | return count; 34 | } 35 | 36 | unsigned countNumbers(const char* text) 37 | { 38 | if(text == nullptr) { return 0; } 39 | 40 | unsigned counter = 0; 41 | while (*text != '\0') 42 | { 43 | if (isDigit(*text) && !isDigit(*(text + 1))) 44 | { 45 | ++counter; 46 | } 47 | ++text; 48 | } 49 | return counter; 50 | } 51 | 52 | char* censorNumbers(const char* text) 53 | { 54 | if(text == nullptr) { return nullptr; } 55 | 56 | unsigned len = myStrlen(text); 57 | unsigned digitsCount = countDigits(text); 58 | unsigned numbersCount = countNumbers(text); 59 | 60 | unsigned resultLenght = len - digitsCount + numbersCount; 61 | 62 | char* toReturn = new char[resultLenght + 1]; 63 | unsigned pos = 0; 64 | 65 | while (*text != '\0') 66 | { 67 | if (!isDigit(*text)) 68 | { 69 | toReturn[pos++] = *text; 70 | } 71 | else if (isDigit(*text) && !isDigit(*(text + 1))) 72 | { 73 | toReturn[pos++] = '*'; 74 | } 75 | ++text; 76 | } 77 | toReturn[pos] = '\0'; 78 | return toReturn; 79 | } 80 | 81 | int main() 82 | { 83 | const char* example = "12This is 145 example 1 text 145"; 84 | 85 | char* censored = censorNumbers(example); 86 | std::cout << censored; 87 | delete[] censored; 88 | } -------------------------------------------------------------------------------- /Seminar-12/readme.md: -------------------------------------------------------------------------------- 1 | # Дванадесети семинар по увод в програмирането - 18.12.2023 2 | 3 | ## Двумерни динамични масиви. 4 | Когато разглеждахме статичните масиви споменахме, че можем да правим масиви от масиви. Синтаксисът беше: 5 | ```cpp 6 | int arr[N][M]; 7 | ``` 8 | където N и М са `constexpr`. В последствие научихме какво са указатели и как можем да използваме ```new``` и ```delete``` за да си поискаме повече памет. Видяхме как да правим масиви, чиито размери не са известни по време на компилация. Възможно ли е да правим двумерни масиви, чиито размери не са известни по време на компилация? Възможно е. 9 | 10 | ```cpp 11 | #include 12 | 13 | int main() 14 | { 15 | int matrixSize = 0; 16 | std::cout << "Enter matrix size: "; 17 | std::cin >> matrixSize; 18 | 19 | int** matrix = new int*[matrixSize]; 20 | 21 | for(size_t i = 0; i < matrixSize; i++) 22 | { 23 | matrix[i] = new int[matrixSize]; 24 | } 25 | 26 | // Вече имаме квадратна матрица с големина matrixSize 27 | // индексацията става по добре познатия начин. 28 | 29 | // След като си свършим работата с нея я трием 30 | 31 | for(size_t i = 0; i < matrixSize; i++) 32 | { 33 | delete[] matrix[i]; 34 | } 35 | 36 | delete[] matrix; 37 | } 38 | ``` 39 | 40 | За какво можем да използваме двумерните масиви освен за матрици? За каквото решим. Приложенията на тази техника са много и с течение на времето ще ги разглеждаме. 41 | 42 | Едно приложение (използвано в задача 6) е представяне на масив от думи. 43 | 44 | ## Задача първа 45 | Да се напише функция ```merge(const int* arrOne, unsigned sizeOne, const int* arrTwo, unsigned sizeTwo)``` която приема два сортирани масива и връща нов сортиран масив, състоящ се от елементите на входните два. Новия масив е сортиран. 46 | 47 | ## Задача втора 48 | Да се напише функция, която приема стринг и връща нов стринг, състоящ се само от малките символи на първоначалния, но в сортиран вид. 49 | 50 | ## Задача трета 51 | Да се напише функция, която приема стринг и връща нов стринг в който всички числа в първоначалния са цензурирани. 52 | 53 | Пример: 54 | 55 | Вход: abc1234dgg4fsdg5 56 | 57 | Изход: abc*dgg*fsdg* 58 | 59 | ## Задача четрърта 60 | Да се напише функция ```char* trimLeft(char* str)``` която връща указател към първия елемент различен от ' '. За реализирането на задачата не е нужно да правим нов низ. 61 | 62 | Пример: 63 | 64 | Вход: " abc " 65 | 66 | изход: "abc " 67 | 68 | ## Задача пета 69 | Да се напише функция ```char* removeWhitespaces(char* text);``` която премахва излишните интервали в подаден текст. 70 | 71 | Пример: 72 | 73 | Вход: " This is example text " 74 | 75 | Изход: "This is example text" -------------------------------------------------------------------------------- /Seminar-12/sort-lower.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool isLower(char ch) 4 | { 5 | return ch >= 'a' && ch <= 'z'; 6 | } 7 | 8 | unsigned getLowerCount(const char* text) 9 | { 10 | unsigned counter = 0; 11 | while (*text != '\0') 12 | { 13 | if (isLower(*text)) 14 | { 15 | ++counter; 16 | } 17 | ++text; 18 | } 19 | return counter; 20 | } 21 | 22 | void fillSymbols(char* temp, unsigned times, unsigned& pos, char symbol) 23 | { 24 | for (size_t i = 0; i < times; i++) 25 | { 26 | temp[pos++] = symbol; 27 | } 28 | } 29 | 30 | char* getLowerSorted(const char* text) 31 | { 32 | unsigned lowerCount = getLowerCount(text); 33 | char* toReturn = new char[lowerCount + 1]; 34 | int hist[26] = {}; 35 | 36 | while (*text != '\0') 37 | { 38 | if (isLower(*text)) 39 | { 40 | unsigned index = *text - 'a'; 41 | ++hist[index]; 42 | } 43 | ++text; 44 | } 45 | unsigned pos = 0; 46 | for (size_t i = 0; i < 26; i++) 47 | { 48 | fillSymbols(toReturn, hist[i], pos, 'a' + i); 49 | } 50 | toReturn[pos] = '\0'; 51 | return toReturn; 52 | } 53 | 54 | int main() 55 | { 56 | const char* example = "this is An example Text"; 57 | char* sortedLower = getLowerSorted(example); 58 | 59 | std::cout << sortedLower; 60 | 61 | delete[] sortedLower; 62 | } -------------------------------------------------------------------------------- /Seminar-13/bubble_sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void swap(int& a, int& b) 4 | { 5 | int temp = a; 6 | a = b; 7 | b = temp; 8 | } 9 | 10 | void bubbleSort(int* arr, unsigned length) 11 | { 12 | size_t lastSwappedIndex = length - 1; 13 | 14 | for (size_t i = 0; i < length; i++) 15 | { 16 | unsigned lastSwappedIndexTemp = lastSwappedIndex; 17 | for (size_t j = 0; j < lastSwappedIndex; j++) 18 | { 19 | if(arr[j] > arr[j + 1]) 20 | { 21 | swap(arr[j], arr[j + 1]); 22 | lastSwappedIndexTemp = j; 23 | } 24 | } 25 | 26 | if(lastSwappedIndex == lastSwappedIndexTemp) { break; } 27 | lastSwappedIndex = lastSwappedIndexTemp; 28 | } 29 | } 30 | 31 | int main() 32 | { 33 | int arr[] = {5, 2, 3, 1, 4}; 34 | bubbleSort(arr, 5); 35 | } -------------------------------------------------------------------------------- /Seminar-13/insertion_sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void insertionSort(int* arr, unsigned length) 4 | { 5 | for (size_t i = 1; i < length; i++) 6 | { 7 | int elementToInsert = arr[i]; 8 | int j = i - 1; 9 | while (j >= 0 && arr[j] > elementToInsert) 10 | { 11 | arr[j + 1] = arr[j]; 12 | --j; 13 | } 14 | arr[j + 1] = elementToInsert; 15 | } 16 | } 17 | 18 | int main() 19 | { 20 | int arr[] = {4, 3, 1, 5, 2, 6}; 21 | insertionSort(arr, 6); 22 | } -------------------------------------------------------------------------------- /Seminar-13/selection_sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void swap(int& a, int& b) 4 | { 5 | int temp = a; 6 | a = b; 7 | b = temp; 8 | } 9 | 10 | void selectionSort(int* arr, unsigned lenght) 11 | { 12 | for (size_t i = 0; i < lenght; i++) 13 | { 14 | size_t minElementIndex = i; 15 | for (size_t j = i + 1; j < lenght; j++) 16 | { 17 | if(arr[j] < arr[minElementIndex]) 18 | { 19 | minElementIndex = j; 20 | } 21 | } 22 | 23 | if(i != minElementIndex) 24 | { 25 | swap(arr[i], arr[minElementIndex]); 26 | } 27 | } 28 | } 29 | 30 | int main() 31 | { 32 | int arr[] = {1, 5, 4, 2, 3, 6}; 33 | selectionSort(arr, 6); 34 | } -------------------------------------------------------------------------------- /Seminar-13/split.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned countOccurances(const char* text, unsigned symbol) 4 | { 5 | unsigned count = 0; 6 | while (*text) 7 | { 8 | if(*text == symbol) 9 | { 10 | ++count; 11 | } 12 | ++text; 13 | } 14 | return count; 15 | } 16 | 17 | char* myStrcpy(char* dest, const char* src, unsigned len) 18 | { 19 | for (size_t i = 0; i < len; i++) 20 | { 21 | dest[i] = src[i]; 22 | } 23 | dest[len] = '\0'; 24 | return dest; 25 | } 26 | 27 | char* getNextWord(const char* text, unsigned wordSize) 28 | { 29 | char* word = new char[wordSize + 1]; 30 | const char* wordBeginning = text - wordSize; 31 | myStrcpy(word, wordBeginning, wordSize); 32 | 33 | return word; 34 | } 35 | 36 | char** split(const char* text, char symbol, unsigned& size) 37 | { 38 | size = countOccurances(text, symbol) + 1; 39 | char** dict = new char*[size]; 40 | unsigned currentWordSize = 0; 41 | unsigned pos = 0; 42 | 43 | while(*text) 44 | { 45 | if(*text != symbol) 46 | { 47 | ++currentWordSize; 48 | } 49 | else 50 | { 51 | dict[pos++] = getNextWord(text, currentWordSize); 52 | currentWordSize = 0; 53 | } 54 | ++text; 55 | } 56 | 57 | // Handle last word 58 | dict[pos] = getNextWord(text, currentWordSize); 59 | 60 | return dict; 61 | } 62 | 63 | void free(char** dict, unsigned size) 64 | { 65 | for (size_t i = 0; i < size; i++) 66 | { 67 | delete[] dict[i]; 68 | } 69 | delete[] dict; 70 | } 71 | 72 | void print(char** dict, unsigned size) 73 | { 74 | for (size_t i = 0; i < size; i++) 75 | { 76 | std::cout << dict[i] << std::endl; 77 | } 78 | } 79 | 80 | int main() 81 | { 82 | const char* example = "Hello, this is an example text!"; 83 | unsigned size = 0; 84 | char** dict = split(example, ' ', size); 85 | 86 | print(dict, size); 87 | free(dict, size); 88 | } -------------------------------------------------------------------------------- /Seminar-13/words.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool isWordSymbol(char ch) 4 | { 5 | return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); 6 | } 7 | 8 | // брой думи = брой символи от зададената азбука, които се следват от символ извън нея. 9 | unsigned wordsCount(const char* text) 10 | { 11 | unsigned count = 0; 12 | while (*text) 13 | { 14 | if(isWordSymbol(*text) && !isWordSymbol(*(text + 1))) 15 | { 16 | ++count; 17 | } 18 | ++text; 19 | } 20 | return count; 21 | } 22 | 23 | void skipSymbols(const char*& text) 24 | { 25 | while (*text && !isWordSymbol(*text)) 26 | { 27 | ++text; 28 | } 29 | } 30 | 31 | char* myStrcpy(char* dest, const char* src, unsigned len) 32 | { 33 | for (size_t i = 0; i < len; i++) 34 | { 35 | dest[i] = src[i]; 36 | } 37 | dest[len] = '\0'; 38 | return dest; 39 | } 40 | 41 | char* getNextWord(const char*& text) 42 | { 43 | unsigned wordSize = 0; 44 | while (isWordSymbol(*text)) 45 | { 46 | ++text; 47 | ++wordSize; 48 | } 49 | char* word = new char[wordSize]; 50 | const char* wordBeginning = text - wordSize; 51 | myStrcpy(word, wordBeginning, wordSize); 52 | return word; 53 | } 54 | 55 | // Дума: последователност от малки и главни латински букви. 56 | // Идея: докато имаме символи в поддадения низ: 57 | // 1. махни всички символи, които не са от азбуката 58 | // 2. махни всички символи от азбуката и ги добави като нова дума 59 | char** splitWords(const char* text, unsigned& size) 60 | { 61 | size = wordsCount(text); 62 | char** dict = new char*[size]; 63 | unsigned pos = 0; 64 | 65 | while(*text) 66 | { 67 | skipSymbols(text); 68 | dict[pos++] = getNextWord(text); 69 | } 70 | return dict; 71 | } 72 | 73 | void free(char** dict, unsigned size) 74 | { 75 | for (size_t i = 0; i < size; i++) 76 | { 77 | delete[] dict[i]; 78 | } 79 | delete[] dict; 80 | } 81 | 82 | void print(char** dict, unsigned size) 83 | { 84 | for (size_t i = 0; i < size; i++) 85 | { 86 | std::cout << dict[i] << std::endl; 87 | } 88 | } 89 | 90 | int main() 91 | { 92 | const char* example = " !!! this Is ! ! ! example ))0 an example ... a"; 93 | unsigned size = 0; 94 | char** dict = splitWords(example, size); 95 | print(dict, size); 96 | free(dict, size); 97 | } -------------------------------------------------------------------------------- /Seminar-14/binary_search.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool binarySearchRec(int* arr, int left, int right, int needle) 4 | { 5 | if(right < left) 6 | { 7 | return false; 8 | } 9 | 10 | int mid = left + (right - left) / 2; 11 | 12 | if(arr[mid] == needle) 13 | { 14 | return true; 15 | } 16 | else if(arr[mid] > needle) 17 | { 18 | return binarySearchRec(arr, left, mid - 1, needle); 19 | } 20 | return binarySearchRec(arr, mid + 1, right, needle); 21 | } 22 | 23 | bool binarySearch(int* arr, size_t size, int needle) 24 | { 25 | return binarySearchRec(arr, 0, size - 1, needle); 26 | } 27 | 28 | int main() 29 | { 30 | int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; 31 | 32 | for (size_t i = 0; i < 11; i++) 33 | { 34 | std::cout << binarySearch(arr, 9, i) << std::endl; 35 | } 36 | } -------------------------------------------------------------------------------- /Seminar-14/isPalindrome.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool isPalindromeRec(const char* text, size_t len) 5 | { 6 | if(len < 2) 7 | { 8 | return true; 9 | } 10 | if(text[0] != text[len - 1]) 11 | { 12 | return false; 13 | } 14 | 15 | return isPalindromeRec(text + 1, len - 2); 16 | } 17 | 18 | bool isPalindrome(const char* text) 19 | { 20 | return isPalindromeRec(text, strlen(text)); 21 | } 22 | 23 | int main() 24 | { 25 | std::cout << isPalindrome("abc???cba") << std::endl; 26 | std::cout << isPalindrome("abc??1>?cba"); 27 | } -------------------------------------------------------------------------------- /Seminar-14/readme.md: -------------------------------------------------------------------------------- 1 | # Четиринадесети семинар по увод в програмирането - 11.01.2024 2 | 3 | ## Рекурсия 4 | До момента, решавайки задачи, използвахме главно цикли. Разбира се, разделяхме кода на по-малки логически части, с помощта на функции. 5 | Задачите решавахме с една или повече итерации върху някаква променлива - точно заради това наричаме този подход `итеративен`. 6 | 7 | Всъщност има и други методи чрез които подхождаме към проблемите. Но защо са ни други подходи? Този не работи ли добре? 8 | 9 | Практически погледнато подходът на решение на една задача се избира спрямо задачата. Знаейки повече подходи и стратегии за решаване на една задача можем да подходим правилно в повече случаи. 10 | 11 | Задачите, които могат да се решат с рекурсия, могат да се решат и с итерация. Понякога обаче информацията се описва по-добре рекурсивно (чрез декомпозиция). 12 | 13 | Та какво е рекурсия? Рекурсивна функция наричаме функция, която извиква себе си. 14 | 15 | ```cpp 16 | int main() 17 | { 18 | main(); 19 | } 20 | ``` 21 | 22 | Това очевидно е отвратителен пример. Програмата ще завърши с грешка. 23 | Ако една функция извиква себе си няма ли да зациклим до безкрайност? Ако сме невнимателни ще зациклим, да (не до безкрайност а докато ни свърши стека). 24 | 25 | За да може да са ефективни рекурсивните алгоритми имат и **дъно**. Достигайки дъното не извикваме отново функцията а връщаме някакъв отговор, който често е лесен да се пресметне. 26 | 27 | (За момента) можем да разглеждаме рекурсивните функции като функции, съставени от две части: 28 | 1. Дъно (или базов случай): Изчисляваме резултата на база на информацията която имаме. Това изчисление обикновено се случва бързо - в дъното резултата често е очевиден. 29 | 2. Рекурсивна стъпка: Изчисляваме резултата с помощта на едно или повече **рекурсивни извиквания**. 30 | 31 | Нека разгледаме пример. Как бихме реализирали задачата за намиране на сума на масив итеративно: 32 | ```cpp 33 | int sum(const int* arr, unsigned size) 34 | { 35 | int toReturn = 0; 36 | for(size_t i = 0; i < size; i++) 37 | { 38 | toReturn += arr[i]; 39 | } 40 | return toReturn; 41 | } 42 | ``` 43 | Как бихме решили задачата рекурсивно? 44 | ```cpp 45 | int sumRec(const int* arr, unsigned size) 46 | { 47 | if(size == 0) 48 | { 49 | // Базов случай. На празен масив сумата е 0. 50 | return 0; 51 | } 52 | 53 | // В противен случай вземи сумата на първия елемент и я събери 54 | // със сумата на arr[1...n]. 55 | return arr[0] + sumRec(arr + 1, size - 1); 56 | } 57 | ``` 58 | 59 | (не особено забавен, но подобен) пример би бил следния: 60 | Колко завивания са нужни да завиеш крушка 61 | * Ако е завита - нула. 62 | * Ако не е завита завърти веднъж, задай ми същия въпрос и добави единица към отговора. 63 | 64 | Какви са предимствата на рекурсията? 65 | * По-кратък и ясен код. 66 | * Част от проблемите биха се решили доста по-сложно използвайки итерация. Причината е, рекурсивния характер на информацията с която работят. 67 | 68 | Недостатъци: 69 | * Често (но не винаги) са по-бавни от итерацията. 70 | * Заемат повече памет заради рекурсивни извиквания. 71 | 72 | ## Примери 73 | ### Как ще напишем факториел 74 | Първия начин, който ще разгледаме е подобен на примера със сумата. 75 | 76 | ```cpp 77 | unsigned factorial(unsigned n) 78 | { 79 | if(n == 0) 80 | { 81 | // Дъно: 0! = 1 по дефиниция. 82 | return 1; 83 | } 84 | 85 | // Рекурсивна стъпка n! = n * (n-1)! 86 | return n * factorial(n - 1); 87 | } 88 | ``` 89 | 90 | За пълнота нека разгледаме още една версия 91 | ```cpp 92 | unsigned factorialAccumulate(unsigned n, unsigned accumulate) 93 | { 94 | if(n == 0) 95 | { 96 | return accumulate; 97 | } 98 | 99 | return factorialAccumulate(n - 1, accumulate * n); 100 | } 101 | 102 | unsigned factorial(unsigned n) 103 | { 104 | return factorialAccumulate(n, 1); 105 | } 106 | ``` 107 | Двете функции са еквивалентни. Просто едната не изчаква да се стигне до дъното за да започне да умножава. 108 | 109 | ### Как ще напишем Фибоначи 110 | Нека сега разгледаме пример как да напишем функция, която намира n-тото число на Фибоначи. 111 | 112 | ```cpp 113 | unsigned fibb(unsigned n) 114 | { 115 | if(n == 0 || n == 1) 116 | { 117 | return 1; 118 | } 119 | 120 | return fibb(n - 1) + fibb(n - 2); 121 | } 122 | ``` 123 | Това е един от най-стандартните примери за рекурсия. Примерът директно прилага дефиницията на числата на Фибоначи. 124 | Какво може да се обърка? 125 | 126 | Функцията прави твърде много изчисления. Нека разгледаме какво ще се случи ако я извикаме. 127 | 128 | fibb(44) = fibb(43) + fibb(42) = fibb(42) + fibb(41) + fibb(41) + fibb(40) = ... 129 | 130 | Забелязваме, че функцията прави твърде много преизчисления на една и съща стойност. Примерно fibb(5) ще се извика много повече от 1 път. 131 | 132 | ### Как да напишем power и fast power. 133 | ### Как да напишем binary search. 134 | ### Как да напишем проверка дали низ е палиндром. 135 | ### Търсене на пътища в лабиринти. 136 | 137 | Лабиринт дефинираме като двумерна матрица съдържаща числата 0, 1 и 2. Представяме си, че сме човече, което се разхожда из тази матрица. 138 | Целта ни е да достигнем до 2. Тръгваме от някоя клетка, която е 0 и можем да преминаваме само през нули. Можем да се движим нагоре, надолу, наляво и надясно. 139 | 140 | Напишете функция ```cpp bool hasPath(int** board, unsigned x, unsigned y);``` която връща дали е възможно да стигнем от координати ```(x, y)``` до някоя двойка. --------------------------------------------------------------------------------