|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "metadata": {}, |
| 6 | + "source": [ |
| 7 | + "# **Примерни решения** на задачите от упражнение 1" |
| 8 | + ] |
| 9 | + }, |
| 10 | + { |
| 11 | + "cell_type": "markdown", |
| 12 | + "metadata": {}, |
| 13 | + "source": [ |
| 14 | + "## Задача 1 (0.75т.)" |
| 15 | + ] |
| 16 | + }, |
| 17 | + { |
| 18 | + "cell_type": "markdown", |
| 19 | + "metadata": {}, |
| 20 | + "source": [ |
| 21 | + "Напишете функция `gather_weather_forecast`, която приема:\n", |
| 22 | + "* `location`: низ (`str`) - име на местоположение\n", |
| 23 | + "* `hours_from_now`: `list` от цели числа (`int`) - часове от текущия момент, за които имаме прогноза\n", |
| 24 | + "* `temperatures`: `list` от цели числа (`int`) - температури за съответните часове (в градуси Целзий)\n", |
| 25 | + "* `rain_probabilities`: `list` от цели числа (`int`) - вероятности за дъжд за съответните часове (между `0` и `100`)\n", |
| 26 | + "* `pressures`: `list` от цели числа (`int`) - налягания за съответните часове (в хектопаскали)\n", |
| 27 | + "\n", |
| 28 | + "Приемете, че дължините на списъците `hours_from_now`, `temperatures`, `rain_probabilities` и `pressures` ще съвпадат винаги.\n", |
| 29 | + "\n", |
| 30 | + "Функцията да върне резултат с тип речник, съдържащ следните ключове и стойности:\n", |
| 31 | + "* `location`: стойността на аргумента `location`\n", |
| 32 | + "* `forecast`: списък от речници всяка прогноза. Всеки от тях съдържа следните ключове и стойности:\n", |
| 33 | + " * `hour`: стойността на съответния елемент от `hours_from_now`\n", |
| 34 | + " * `temperature`: стойността на съответния елемент от `temperatures`\n", |
| 35 | + " * `rain probability`: стойността на съответния елемент от `rain_probabilities`\n", |
| 36 | + " * `pressure`: стойността на съответния елемент от `pressures`\n", |
| 37 | + "\n", |
| 38 | + "*Hint*: в `list` се добавя елемент с метода `append`." |
| 39 | + ] |
| 40 | + }, |
| 41 | + { |
| 42 | + "cell_type": "code", |
| 43 | + "execution_count": null, |
| 44 | + "metadata": {}, |
| 45 | + "outputs": [], |
| 46 | + "source": [ |
| 47 | + "def gather_weather_forecast(location, hours_from_now, temperatures, rain_probabilities, pressures):\n", |
| 48 | + " result = {}\n", |
| 49 | + " result[\"location\"] = location\n", |
| 50 | + " result[\"forecast\"] = []\n", |
| 51 | + " for i, h in enumerate(hours_from_now):\n", |
| 52 | + " result[\"forecast\"].append({\n", |
| 53 | + " \"hour\": h,\n", |
| 54 | + " \"temperature\": temperatures[i],\n", |
| 55 | + " \"rain probability\": rain_probabilities[i],\n", |
| 56 | + " \"pressure\": pressures[i],\n", |
| 57 | + " })\n", |
| 58 | + " return result" |
| 59 | + ] |
| 60 | + }, |
| 61 | + { |
| 62 | + "cell_type": "code", |
| 63 | + "execution_count": null, |
| 64 | + "metadata": {}, |
| 65 | + "outputs": [], |
| 66 | + "source": [ |
| 67 | + "assert gather_weather_forecast(\"Test Island\", [1], [22], [12], [1000]) == {\n", |
| 68 | + " \"location\": \"Test Island\",\n", |
| 69 | + " \"forecast\": [\n", |
| 70 | + " {\"hour\": 1, \"temperature\": 22, \"rain probability\": 12, \"pressure\": 1000},\n", |
| 71 | + " ]\n", |
| 72 | + "}\n", |
| 73 | + "\n", |
| 74 | + "assert gather_weather_forecast(\"Studentski Grad\", [24, 48, 72], [20, 18, 15], [0, 50, 88], [1000, 990, 980]) == {\n", |
| 75 | + " \"location\": \"Studentski Grad\",\n", |
| 76 | + " \"forecast\": [\n", |
| 77 | + " {\"hour\": 24, \"temperature\": 20, \"rain probability\": 0, \"pressure\": 1000},\n", |
| 78 | + " {\"hour\": 48, \"temperature\": 18, \"rain probability\": 50, \"pressure\": 990},\n", |
| 79 | + " {\"hour\": 72, \"temperature\": 15, \"rain probability\": 88, \"pressure\": 980},\n", |
| 80 | + " ]\n", |
| 81 | + "}\n", |
| 82 | + "\n", |
| 83 | + "\"✅ All OK! +0.75 points\"" |
| 84 | + ] |
| 85 | + }, |
| 86 | + { |
| 87 | + "cell_type": "markdown", |
| 88 | + "metadata": {}, |
| 89 | + "source": [ |
| 90 | + "## Задача 2 (1т.)" |
| 91 | + ] |
| 92 | + }, |
| 93 | + { |
| 94 | + "cell_type": "markdown", |
| 95 | + "metadata": {}, |
| 96 | + "source": [ |
| 97 | + "В академичните публикации е прието при наличие на повече няколко автори с имейли с еднакви домейни, техните имена да се обединяват.\n", |
| 98 | + "\n", |
| 99 | + "Т.е. за краткост `name1@domain`, `name2@domain` се изписват вместо това като `{name1,name2}@domain`.\n", |
| 100 | + "\n", |
| 101 | + "Напишете функция `emails_shortener`, която приема `emails` (списък от низове) и връща множество (`set`) от низове, където всички имейли с еднакви домейни са обединени по гореописания начин.\n", |
| 102 | + "\n", |
| 103 | + "*Hint 1*: за да разделите низ `s` по даден символ `c`, изполвайте [`s.split(c)`](https://docs.python.org/3/library/stdtypes.html#str.split). Връща лист с всяка разбита част (низ).\n", |
| 104 | + "\n", |
| 105 | + "*Hint 2*: низове се слепват с метода [`join`](https://docs.python.org/3/library/stdtypes.html#str.join). Например `\":\".join([\"john\", \"doe\", \"jr\"])` ще върне `\"john:doe:jr\"`.\n", |
| 106 | + "\n", |
| 107 | + "*Hint 3*: в `set` се добавя елемент с метода `add`." |
| 108 | + ] |
| 109 | + }, |
| 110 | + { |
| 111 | + "cell_type": "code", |
| 112 | + "execution_count": 1, |
| 113 | + "metadata": {}, |
| 114 | + "outputs": [], |
| 115 | + "source": [ |
| 116 | + "def emails_shortener(emails):\n", |
| 117 | + " domains = {}\n", |
| 118 | + "\n", |
| 119 | + " for email in emails:\n", |
| 120 | + " name, domain = email.split(\"@\")\n", |
| 121 | + " if domain not in domains:\n", |
| 122 | + " domains[domain] = []\n", |
| 123 | + " domains[domain].append(name)\n", |
| 124 | + "\n", |
| 125 | + " result = set()\n", |
| 126 | + " for domain, names in domains.items():\n", |
| 127 | + " if len(names) == 1:\n", |
| 128 | + " email = f\"{names[0]}@{domain}\"\n", |
| 129 | + " else:\n", |
| 130 | + " email = \"{\" + \",\".join(names) + \"}@\" + domain\n", |
| 131 | + " result.add(email)\n", |
| 132 | + " \n", |
| 133 | + " return result\n", |
| 134 | + " " |
| 135 | + ] |
| 136 | + }, |
| 137 | + { |
| 138 | + "cell_type": "code", |
| 139 | + "execution_count": null, |
| 140 | + "metadata": {}, |
| 141 | + "outputs": [], |
| 142 | + "source": [ |
| 143 | + "assert emails_shortener([\n", |
| 144 | + " \"pesho@abv.bg\", \n", |
| 145 | + " \"gosho@abv.bg\",\n", |
| 146 | + " \"sasho@abv.bg\",\n", |
| 147 | + "]) == {\n", |
| 148 | + " \"{pesho,gosho,sasho}@abv.bg\"\n", |
| 149 | + "}\n", |
| 150 | + "\n", |
| 151 | + "assert emails_shortener([\n", |
| 152 | + " \"tinko@fmi.uni-sofia.bg\", \n", |
| 153 | + " \"minko@fmi.uni-sofia.bg\", \n", |
| 154 | + " \"pesho@pesho.org\",\n", |
| 155 | + "]) == {\n", |
| 156 | + " \"{tinko,minko}@fmi.uni-sofia.bg\", \n", |
| 157 | + " \"pesho@pesho.org\",\n", |
| 158 | + "}\n", |
| 159 | + "\n", |
| 160 | + "assert emails_shortener([\n", |
| 161 | + " \"toi_e@pesho.org\",\n", |
| 162 | + " \"golemiq@cyb.org\",\n", |
| 163 | + "]) == {\n", |
| 164 | + " \"toi_e@pesho.org\",\n", |
| 165 | + " \"golemiq@cyb.org\",\n", |
| 166 | + "}\n", |
| 167 | + "\n", |
| 168 | + "\"✅ All OK! +1 points\"" |
| 169 | + ] |
| 170 | + }, |
| 171 | + { |
| 172 | + "cell_type": "markdown", |
| 173 | + "metadata": {}, |
| 174 | + "source": [ |
| 175 | + "## Задача 3 (1.25т.)" |
| 176 | + ] |
| 177 | + }, |
| 178 | + { |
| 179 | + "cell_type": "markdown", |
| 180 | + "metadata": {}, |
| 181 | + "source": [ |
| 182 | + "Напишете функция `is_valid`, приемаща `license_plate` - низ, съдържащ български регистрационен номер на превозно средство. Тя трябва да провери дали номерът е валиден и да върне или областта на регистрация, или съобщение за грешка.\n", |
| 183 | + "\n", |
| 184 | + "\n", |
| 185 | + "Приемаме, че всички валидни номера са във формат `AA0000AA` или `A0000AA`, където `A` означава буква, а `0` - цифра.\n", |
| 186 | + "В `license_plate` може буквите да са малки, трябва да се погрижите да ги преобразувате към главни.\n", |
| 187 | + "Позволените букви от кирилицата може да намерите в даденото от нас множество `ALLOWED_LETTERS`. Номерът е валиден, ако всичките му букви са позволени. \n", |
| 188 | + "(Ако са подадени на латиница, за простота нека считаме номерът за невалиден. Иначе ако имплементираме такава система в реалния свят ще е хубаво да се обръщат от латиница в кирилица :) )\n", |
| 189 | + "Буквата/буквите преди цифрите трябва да съответстват на реигонален код на област в България, което може да проверите чрез речника `REGION_CODES`.\n", |
| 190 | + "\n", |
| 191 | + "Функцията да връща две стойности (в tuple), спрямо валидността на номера:\n", |
| 192 | + "* Случай 1: номерът е валиден: връща `True` и областта, в която е регистрирано превозното средство\n", |
| 193 | + "* Случай 2: номерът не е валиден: връща `False` и текст за грешка, който е един от следните:\n", |
| 194 | + " * `\"Невалиден формат\"` - когато форматът не съответства на гореописаните два\n", |
| 195 | + " * `\"Невалидни букви\"` - когато бъдат подадени букви, които не са позволени\n", |
| 196 | + " * `\"Невалиден регионален код\"` - когато буквите преди цифрите не съответстват на реигонален код на област в България\n", |
| 197 | + "\n", |
| 198 | + "(Забележка: няма да бъдат подавани специални номера, като такива на автомобили от Българската армия (ВА000000), електрически (ЕА...), дипломатически и др.)\n", |
| 199 | + "\n", |
| 200 | + "За проверка дали даден `str` съдържа само цифри, използвайте `str.isdigit()`.\n", |
| 201 | + "\n", |
| 202 | + "За проверка дали даден `str` съдържа само букви, използвайте `str.isalpha()`.\n", |
| 203 | + "\n", |
| 204 | + "*Hint*: За по-лесно (ако не ви се въртят цикли), може да използвате някои от операторите `|`, `&`, `<=` , които изпълнени върху две множества са съотв. операциите обединение, сечение и подмножество." |
| 205 | + ] |
| 206 | + }, |
| 207 | + { |
| 208 | + "cell_type": "code", |
| 209 | + "execution_count": null, |
| 210 | + "metadata": {}, |
| 211 | + "outputs": [], |
| 212 | + "source": [ |
| 213 | + "INVALID_FORMAT_MSG = \"Невалиден формат\"\n", |
| 214 | + "INVALID_LETTERS_MSG = \"Невалидни букви\"\n", |
| 215 | + "INVALID_CODE_MSG = \"Невалиден регионален код\"\n", |
| 216 | + "\n", |
| 217 | + "ALLOWED_LETTERS = set(\"АВЕКМНОРСТУХ\")\n", |
| 218 | + "\n", |
| 219 | + "REGION_CODES = {\n", |
| 220 | + " \"Е\": \"Благоевград\",\n", |
| 221 | + " \"А\": \"Бургас\",\n", |
| 222 | + " \"В\": \"Варна\",\n", |
| 223 | + " \"ВТ\": \"Велико Търново\",\n", |
| 224 | + " \"ВН\": \"Видин\",\n", |
| 225 | + " \"ВР\": \"Враца\",\n", |
| 226 | + " \"ЕВ\": \"Габрово\",\n", |
| 227 | + " \"ТХ\": \"Добрич\",\n", |
| 228 | + " \"К\": \"Кърджали\",\n", |
| 229 | + " \"КН\": \"Кюстендил\",\n", |
| 230 | + " \"ОВ\": \"Ловеч\",\n", |
| 231 | + " \"М\": \"Монтана\",\n", |
| 232 | + " \"РА\": \"Пазарджик\",\n", |
| 233 | + " \"РК\": \"Перник\",\n", |
| 234 | + " \"ЕН\": \"Плевен\",\n", |
| 235 | + " \"РВ\": \"Пловдив\",\n", |
| 236 | + " \"РР\": \"Разград\",\n", |
| 237 | + " \"Р\": \"Русе\",\n", |
| 238 | + " \"СС\": \"Силистра\",\n", |
| 239 | + " \"СН\": \"Сливен\",\n", |
| 240 | + " \"СМ\": \"Смолян\",\n", |
| 241 | + " \"СО\": \"София (област)\",\n", |
| 242 | + " \"С\": \"София (столица)\",\n", |
| 243 | + " \"СА\": \"София (столица)\",\n", |
| 244 | + " \"СВ\": \"София (столица)\",\n", |
| 245 | + " \"СТ\": \"Стара Загора\",\n", |
| 246 | + " \"Т\": \"Търговище\",\n", |
| 247 | + " \"Х\": \"Хасково\",\n", |
| 248 | + " \"Н\": \"Шумен\",\n", |
| 249 | + " \"У\": \"Ямбол\",\n", |
| 250 | + "}" |
| 251 | + ] |
| 252 | + }, |
| 253 | + { |
| 254 | + "cell_type": "code", |
| 255 | + "execution_count": null, |
| 256 | + "metadata": {}, |
| 257 | + "outputs": [], |
| 258 | + "source": [ |
| 259 | + "def is_valid(license_plate):\n", |
| 260 | + " if not isinstance(license_plate, str):\n", |
| 261 | + " return False, INVALID_FORMAT_MSG\n", |
| 262 | + " \n", |
| 263 | + " if len(license_plate) not in (7, 8):\n", |
| 264 | + " return False, INVALID_FORMAT_MSG\n", |
| 265 | + " \n", |
| 266 | + " license_plate = license_plate.upper()\n", |
| 267 | + " regional_chars = license_plate[:-6]\n", |
| 268 | + " digits = license_plate[-6:-2]\n", |
| 269 | + " last_chars = license_plate[-2:]\n", |
| 270 | + "\n", |
| 271 | + " if not digits.isdigit() or not last_chars.isalpha() or not regional_chars.isalpha():\n", |
| 272 | + " return False, INVALID_FORMAT_MSG\n", |
| 273 | + "\n", |
| 274 | + " chars_used = set(regional_chars) | set(last_chars)\n", |
| 275 | + " if not chars_used <= ALLOWED_LETTERS:\n", |
| 276 | + " return False, INVALID_LETTERS_MSG\n", |
| 277 | + " \n", |
| 278 | + " if regional_chars not in REGION_CODES:\n", |
| 279 | + " return False, INVALID_CODE_MSG\n", |
| 280 | + " \n", |
| 281 | + " return True, REGION_CODES[regional_chars]\n" |
| 282 | + ] |
| 283 | + }, |
| 284 | + { |
| 285 | + "cell_type": "code", |
| 286 | + "execution_count": null, |
| 287 | + "metadata": {}, |
| 288 | + "outputs": [], |
| 289 | + "source": [ |
| 290 | + "assert is_valid(\"СА1234АВ\") == (True, \"София (столица)\")\n", |
| 291 | + "assert is_valid(\"С1234АВ\") == (True, \"София (столица)\")\n", |
| 292 | + "assert is_valid(\"ТХ0000ТХ\") == (True, \"Добрич\")\n", |
| 293 | + "assert is_valid(\"ТХ000ТХ\") == (False, INVALID_FORMAT_MSG)\n", |
| 294 | + "assert is_valid(\"ТХ0000Т\") == (False, INVALID_FORMAT_MSG)\n", |
| 295 | + "assert is_valid(\"ТХ0000ТХХ\") == (False, INVALID_FORMAT_MSG)\n", |
| 296 | + "assert is_valid(\"У8888СТ\") == (True, \"Ямбол\")\n", |
| 297 | + "assert is_valid(\"Y8888CT\") == (False, INVALID_LETTERS_MSG)\n", |
| 298 | + "assert is_valid(\"ПЛ7777АА\") == (False, INVALID_LETTERS_MSG)\n", |
| 299 | + "assert is_valid(\"РВ7777БВ\") == (False, INVALID_LETTERS_MSG)\n", |
| 300 | + "assert is_valid(\"ВВ6666КН\") == (False, INVALID_CODE_MSG)\n", |
| 301 | + "\n", |
| 302 | + "\"✅ All OK! +1.25 points\"" |
| 303 | + ] |
| 304 | + }, |
| 305 | + { |
| 306 | + "cell_type": "markdown", |
| 307 | + "metadata": {}, |
| 308 | + "source": [ |
| 309 | + "## Задача 4 (1.25т.)" |
| 310 | + ] |
| 311 | + }, |
| 312 | + { |
| 313 | + "cell_type": "markdown", |
| 314 | + "metadata": {}, |
| 315 | + "source": [ |
| 316 | + "Turtle е библиотека в Python, която предоставя опростен начин за рисуване чрез графичен интерфейс. С нея може да се създават различни форми, фигури и анимации, като се използва костенурка - виртуален курсор, който се движи по екрана и оставя следи. Вашата задача днес е вдъхновена именно от нея и е следната:\n", |
| 317 | + "\n", |
| 318 | + "Създайте клас `Turtle`. \n", |
| 319 | + "\n", |
| 320 | + "При създаване на обект от класа Turtle трябва да бъдат приети 2 параметъра - `x` и `y`, координатите на стартовата точка на вашата костенурка по оста x и оста y. Ако такива не бъдат подадени, се приема, че стартовата точка е с координати (0,0).\n", |
| 321 | + "\n", |
| 322 | + "Имплементирайте метод `get_current_position`, който да връща **tuple** с координатите на настоящата позиция на костенурката. \n", |
| 323 | + "\n", |
| 324 | + "Костенурката трябва да може да се движи. За целта трябва да бъде написан метод `move`, който да приема произволен брой аргументи - команди за движение. Ако се подаде невалидна команда (т.е. различна от позволените) да се покаже на конзолата съобщение `Invalid command: {command}`, но да не се прекъсва хода на метода.\n", |
| 325 | + "Вашата костенурка може да изпълнява само следните 4 движения: \n", |
| 326 | + "* `up` - премества се 1 стъпка нагоре по координатната система\n", |
| 327 | + "* `down` - премества се 1 стъпка надолу по координатната система\n", |
| 328 | + "* `left` - премества се 1 стъпка наляво по координатната система\n", |
| 329 | + "* `right` - премества се 1 стъпка надясно по координатната система \n", |
| 330 | + "\n", |
| 331 | + "Костенурката може да има различни конфигурации. Тя може да рисува в определен цвят, да оставя диря с точно определена дебелина и т.н. Имплементирайте метод `configure_turtle`, който да приема произволен брой аргументи - конфигурационните параметри и техните стойности. Да се връща низ от вида `Current configuration: {P}:{V} | {P}:{V} |...`, където **P** е името на конфигурационния параметър, а **V** е неговата стойност. \n", |
| 332 | + "\n", |
| 333 | + "Трябва да можем да проверим дали сме изобразили точно определна рисунка. Под рисунка разбираме конкретна последователност от команди. \n", |
| 334 | + "Вашата задача е да имплементирате метод `check_for_drawing`, който приема последователност от команди (рисунка) и връща bool, който индикира дали такава е била нарисувана.\n", |
| 335 | + "* *Пример за рисунка*: **[up, up, up, right, right, down, left, left]**.\n", |
| 336 | + "\n", |
| 337 | + "Като за финал пренапишете `__str__`, така че да връща следното съобщение: `Turtle is at position ({x}, {y}) and has moved {count} times since start`" |
| 338 | + ] |
| 339 | + }, |
| 340 | + { |
| 341 | + "cell_type": "code", |
| 342 | + "execution_count": 2, |
| 343 | + "metadata": {}, |
| 344 | + "outputs": [], |
| 345 | + "source": [ |
| 346 | + "class Turtle:\n", |
| 347 | + " def __init__(self, x = 0, y=0):\n", |
| 348 | + " self.x = x\n", |
| 349 | + " self.y = y\n", |
| 350 | + " self.moves = []\n", |
| 351 | + "\n", |
| 352 | + " def get_current_position(self):\n", |
| 353 | + " return (self.x, self.y)\n", |
| 354 | + "\n", |
| 355 | + " def move(self, *sequence):\n", |
| 356 | + " for direction in sequence:\n", |
| 357 | + " match direction:\n", |
| 358 | + " case 'up':\n", |
| 359 | + " self.moves.append('up')\n", |
| 360 | + " self.y += 1\n", |
| 361 | + " case 'down':\n", |
| 362 | + " self.moves.append('down')\n", |
| 363 | + " self.y -= 1\n", |
| 364 | + " case 'left':\n", |
| 365 | + " self.moves.append('left')\n", |
| 366 | + " self.x -= 1\n", |
| 367 | + " case 'right':\n", |
| 368 | + " self.moves.append('right')\n", |
| 369 | + " self.x += 1\n", |
| 370 | + " case _:\n", |
| 371 | + " print(\"Invalid command:\", direction)\n", |
| 372 | + "\n", |
| 373 | + " def configure_turtle(self, **configurationParameters):\n", |
| 374 | + " message = \"Current configuration:\"\n", |
| 375 | + " for parameter, value in configurationParameters.items():\n", |
| 376 | + " message +=f\" {parameter}:{value} |\"\n", |
| 377 | + " return message\n", |
| 378 | + " \n", |
| 379 | + " def check_for_drawing(self, drawing):\n", |
| 380 | + " moves_str = ' '.join(self.moves)\n", |
| 381 | + " drawing_str = ' '.join(drawing)\n", |
| 382 | + " if drawing_str in moves_str:\n", |
| 383 | + " return True\n", |
| 384 | + " else:\n", |
| 385 | + " return False\n", |
| 386 | + " \n", |
| 387 | + " def __str__(self):\n", |
| 388 | + " return f\"Turtle is at position ({self.x},{self.y}) and has moved {len(self.moves)} times since start\"\n", |
| 389 | + "\n" |
| 390 | + ] |
| 391 | + }, |
| 392 | + { |
| 393 | + "cell_type": "code", |
| 394 | + "execution_count": null, |
| 395 | + "metadata": {}, |
| 396 | + "outputs": [], |
| 397 | + "source": [ |
| 398 | + "# Test Case 1: Test Turtle Initialization with default coordinates (0, 0)\n", |
| 399 | + "t1 = Turtle()\n", |
| 400 | + "assert t1.x == 0 and t1.y == 0, \"Initial position should be (0,0)\"\n", |
| 401 | + "assert str(t1) == \"Turtle is at position (0,0) and has moved 0 times since start\", \"String representation is incorrect\"\n", |
| 402 | + "\n", |
| 403 | + "# Test Case 2: Test move method with valid moves\n", |
| 404 | + "t1.move('up', 'right', 'down', 'left')\n", |
| 405 | + "assert t1.x == 0 and t1.y == 0, \"Turtle should return to (0,0) after up, right, down, left\"\n", |
| 406 | + "assert len(t1.moves) == 4, \"Turtle should have 4 moves recorded\"\n", |
| 407 | + "assert str(t1) == \"Turtle is at position (0,0) and has moved 4 times since start\", \"String representation after 4 moves is incorrect\"\n", |
| 408 | + "\n", |
| 409 | + "# Test Case 3: Test move method with invalid move\n", |
| 410 | + "t1.move('right', 'testing', 'right', 'left')\n", |
| 411 | + "assert len(t1.moves) == 7, \"Invalid move should not be added to the move list\"\n", |
| 412 | + "assert str(t1) == \"Turtle is at position (1,0) and has moved 7 times since start\", \"Invalid move should not affect the position or count of moves\"\n", |
| 413 | + "\n", |
| 414 | + "# Test Case 4: Test Turtle Initialization with custom coordinates\n", |
| 415 | + "t2 = Turtle(3, 4)\n", |
| 416 | + "assert t2.x == 3 and t2.y == 4, \"Initial position should be (3,4)\"\n", |
| 417 | + "assert str(t2) == \"Turtle is at position (3,4) and has moved 0 times since start\", \"String representation with custom initial coordinates is incorrect\"\n", |
| 418 | + "\n", |
| 419 | + "# Test Case 5: Test move method with different valid moves\n", |
| 420 | + "t2.move('up', 'up', 'right')\n", |
| 421 | + "assert t2.x == 4 and t2.y == 6, \"Turtle should be at (4,6) after moving up twice and right\"\n", |
| 422 | + "assert len(t2.moves) == 3, \"Turtle should have 3 moves recorded\"\n", |
| 423 | + "assert str(t2) == \"Turtle is at position (4,6) and has moved 3 times since start\", \"String representation after custom moves is incorrect\"\n", |
| 424 | + "\n", |
| 425 | + "# Test Case 6: Test configure_turtle method\n", |
| 426 | + "config_message = t2.configure_turtle(color=\"green\", thickness=2, size=10)\n", |
| 427 | + "assert config_message == \"Current configuration: color:green | thickness:2 | size:10 |\", \"Configuration message is incorrect\"\n", |
| 428 | + "\n", |
| 429 | + "# Test Case 7: Test check_for_drawing method with existing drawing\n", |
| 430 | + "t2.move('down', 'down', 'left')\n", |
| 431 | + "assert t2.check_for_drawing(['up', 'right', 'down']) is True, \"Drawing sequence should match recorded moves\"\n", |
| 432 | + "assert t2.check_for_drawing(['up', 'up', 'right', 'left']) is False, \"Invalid drawing sequence should not match recorded moves\"\n", |
| 433 | + "\n", |
| 434 | + "# Test Case 8: Test get_current_position method \n", |
| 435 | + "assert t2.get_current_position() == (3, 4), \"Current position should be (3,4) after initial moves\"\n", |
| 436 | + "\n", |
| 437 | + "\"✅ All OK! +1.25 points\"" |
| 438 | + ] |
| 439 | + }, |
| 440 | + { |
| 441 | + "cell_type": "markdown", |
| 442 | + "metadata": {}, |
| 443 | + "source": [ |
| 444 | + "## Задача 5 (0.75т.)" |
| 445 | + ] |
| 446 | + }, |
| 447 | + { |
| 448 | + "cell_type": "markdown", |
| 449 | + "metadata": {}, |
| 450 | + "source": [ |
| 451 | + "Напишете клас `Person`, който да репрезентира човек:\n", |
| 452 | + "* Да притежава член-данни:\n", |
| 453 | + "\n", |
| 454 | + " * `name`: низ (`str`) - име на човека от семейството\n", |
| 455 | + "\n", |
| 456 | + " * `age`: цяло число (`int`) - възраст на човека\n", |
| 457 | + "\n", |
| 458 | + "* Да реализира следните методи:\n", |
| 459 | + "\n", |
| 460 | + " * `__repr__`: Метода трябва да връща низ (`str`) с подходяща Python репрезентация на обекта\n", |
| 461 | + "\n", |
| 462 | + " * `__str__`: Метода трябва да връща низ (`str`), представляващ име на човека и неговите години във формат `Name (age)`\n", |
| 463 | + "\n", |
| 464 | + " * `__gt__`: Предефинирайте оператора `>` (greater than), който да сравнява два човека `(Person)` спрямо годините им.\n", |
| 465 | + "\n", |
| 466 | + "\n", |
| 467 | + "Напишете клас `FamilyTree`, който да репрезентира родословно дърво:\n", |
| 468 | + "* Да притежава член-данни:\n", |
| 469 | + " * `root`: обект от тип `Person` представляващ главата на едно родословно дърво \n", |
| 470 | + "\n", |
| 471 | + " * `children`: `list` от елементи от същия клас (`Family_Tree`) - Наследници на човека от семейство. Всяко дете си е корен на своето родословно дърво. По подразбиране един човек няма деца. (*hint* be carefull with default empty lists)\n", |
| 472 | + "\n", |
| 473 | + "* Да реализира следните методи:\n", |
| 474 | + "\n", |
| 475 | + " * `__str__`: Метода трябва да връща низ (`str`), представляващ построеното родословно дърво с корен `root`. За всеки `Person` в дървото трябва да са изведени неговото име и години във формат `> Name (age)`, като пред всеки реди поставяме 4 4 празни места, спрямо нивото му в дървото. Ако човекът има деца, подреждаме информацията за тях спрямо реда им на добавяне. (*hint* поздрави от дискретна математика) . \n", |
| 476 | + " * Пример: \n", |
| 477 | + " ```\n", |
| 478 | + " > John (50)\n", |
| 479 | + " > Jake (18)\n", |
| 480 | + " > Emily (30)\n", |
| 481 | + " > Dan (3)\n", |
| 482 | + " > Fiona (7)\n", |
| 483 | + " ```\n", |
| 484 | + "\n", |
| 485 | + "\n", |
| 486 | + " * `count_descendants`: Рекурсивен метод който да връща броя на наследници на корена да дървото (`int`) (деца, внуци, правнуци, праправнуци, ...). Например \"Boyko has 69 descendants\"\n", |
| 487 | + "\n", |
| 488 | + " * `add_child_tree`: Метод който да добавя дете (друг `Family_Tree` обект) към списъка с деца." |
| 489 | + ] |
| 490 | + }, |
| 491 | + { |
| 492 | + "cell_type": "code", |
| 493 | + "execution_count": null, |
| 494 | + "metadata": {}, |
| 495 | + "outputs": [], |
| 496 | + "source": [ |
| 497 | + "class Person:\n", |
| 498 | + " def __init__(self, name: str, age: int) -> None:\n", |
| 499 | + " self.name = name\n", |
| 500 | + " self.age = age\n", |
| 501 | + "\n", |
| 502 | + " def __repr__(self) -> str:\n", |
| 503 | + " return f\"Person({self.name=},{self.age=})\"\n", |
| 504 | + "\n", |
| 505 | + " def __str__(self) -> str:\n", |
| 506 | + " return f\"{self.name} ({self.age})\"\n", |
| 507 | + "\n", |
| 508 | + " def __gt__(self,other):\n", |
| 509 | + " return True if self.age > other.age else False\n", |
| 510 | + " \n", |
| 511 | + "\n", |
| 512 | + "\n", |
| 513 | + "class FamilyTree:\n", |
| 514 | + " def __init__(self,root: Person,children = None) -> None:\n", |
| 515 | + " self.root = root\n", |
| 516 | + " self.children = [] if children is None else children \n", |
| 517 | + " \n", |
| 518 | + " def __str__(self,depth = 0) -> str:\n", |
| 519 | + " spacing_symbol = ' '\n", |
| 520 | + " answer = f\"{spacing_symbol*depth}> {self.root.__str__()}\\n\"\n", |
| 521 | + " for child in self.children:\n", |
| 522 | + " answer += child.__str__(depth= depth + 1)\n", |
| 523 | + " return answer\n", |
| 524 | + "\n", |
| 525 | + " def add_child_tree(self,child_tree):\n", |
| 526 | + " self.children.append(child_tree)\n", |
| 527 | + "\n", |
| 528 | + " def count_descendants(self):\n", |
| 529 | + " if self.children == []:\n", |
| 530 | + " return 0 \n", |
| 531 | + " else:\n", |
| 532 | + " descendants = len(self.children)\n", |
| 533 | + " for child in self.children:\n", |
| 534 | + " descendants += child.count_descendants()\n", |
| 535 | + " return descendants" |
| 536 | + ] |
| 537 | + }, |
| 538 | + { |
| 539 | + "cell_type": "code", |
| 540 | + "execution_count": null, |
| 541 | + "metadata": {}, |
| 542 | + "outputs": [], |
| 543 | + "source": [ |
| 544 | + "# tests\n", |
| 545 | + "\n", |
| 546 | + "# Create dummies of class Person\n", |
| 547 | + "john = Person(\"John\", 50)\n", |
| 548 | + "emily = Person(\"Emily\", 30)\n", |
| 549 | + "jake = Person(\"Jake\", 18)\n", |
| 550 | + "dan = Person(\"Dan\", 3)\n", |
| 551 | + "fiona = Person(\"Fiona\", 7)\n", |
| 552 | + "\n", |
| 553 | + "# Create family trees for each person\n", |
| 554 | + "john_familiy_tree = FamilyTree(john)\n", |
| 555 | + "emily_familiy_tree = FamilyTree(emily)\n", |
| 556 | + "jake_familiy_tree = FamilyTree(jake)\n", |
| 557 | + "dan_familiy_tree = FamilyTree(dan)\n", |
| 558 | + "fiona_familiy_tree = FamilyTree(fiona)\n", |
| 559 | + "\n", |
| 560 | + "# ---- Testing add_child_tree functionality ----\n", |
| 561 | + "\n", |
| 562 | + "# Add children to John\n", |
| 563 | + "john_familiy_tree.add_child_tree(jake_familiy_tree)\n", |
| 564 | + "john_familiy_tree.add_child_tree(emily_familiy_tree)\n", |
| 565 | + "\n", |
| 566 | + "# Add children to Emily\n", |
| 567 | + "emily_familiy_tree.add_child_tree(dan_familiy_tree)\n", |
| 568 | + "emily_familiy_tree.add_child_tree(fiona_familiy_tree)\n", |
| 569 | + "\n", |
| 570 | + "assert john_familiy_tree.children[1] == emily_familiy_tree\n", |
| 571 | + "assert john_familiy_tree.children[0] == jake_familiy_tree\n", |
| 572 | + "assert emily_familiy_tree.children[0] == dan_familiy_tree\n", |
| 573 | + "assert emily_familiy_tree.children[1] == fiona_familiy_tree\n", |
| 574 | + "\n", |
| 575 | + "# ---- Testing __init__ functionality ----\n", |
| 576 | + "\n", |
| 577 | + "assert john.name == \"John\"\n", |
| 578 | + "assert john.age == 50\n", |
| 579 | + "\n", |
| 580 | + "\n", |
| 581 | + "assert jake.name == \"Jake\"\n", |
| 582 | + "assert jake.age == 18\n", |
| 583 | + "\n", |
| 584 | + "\n", |
| 585 | + "assert john_familiy_tree.root == john\n", |
| 586 | + "assert len(john_familiy_tree.children) == 2\n", |
| 587 | + "\n", |
| 588 | + "assert jake_familiy_tree.root == jake\n", |
| 589 | + "assert len(jake_familiy_tree.children) == 0\n", |
| 590 | + "\n", |
| 591 | + "assert emily_familiy_tree.root == emily\n", |
| 592 | + "assert dan_familiy_tree.root == dan\n", |
| 593 | + "assert fiona_familiy_tree.root == fiona\n", |
| 594 | + "\n", |
| 595 | + "\n", |
| 596 | + "# ---- Testing __str__functionality ----\n", |
| 597 | + "expected_repr = \"> John (50)\\n > Jake (18)\\n > Emily (30)\\n > Dan (3)\\n > Fiona (7)\\n\"\n", |
| 598 | + "assert str(john_familiy_tree) == expected_repr\n", |
| 599 | + "\n", |
| 600 | + "# # ---- Testing __gt__functionality ---- \n", |
| 601 | + "assert john > emily\n", |
| 602 | + "assert john > jake\n", |
| 603 | + "assert emily > jake\n", |
| 604 | + "assert jake > dan\n", |
| 605 | + "\n", |
| 606 | + "# # ---- Testing __gt__functionality ---- \n", |
| 607 | + "assert john_familiy_tree.count_descendants() == 4\n", |
| 608 | + "assert jake_familiy_tree.count_descendants() == 0\n", |
| 609 | + "assert emily_familiy_tree.count_descendants() == 2\n", |
| 610 | + "\n", |
| 611 | + "\"✅ All OK! +0.75 points\"" |
| 612 | + ] |
| 613 | + } |
| 614 | + ], |
| 615 | + "metadata": { |
| 616 | + "kernelspec": { |
| 617 | + "display_name": "Python 3", |
| 618 | + "language": "python", |
| 619 | + "name": "python3" |
| 620 | + }, |
| 621 | + "language_info": { |
| 622 | + "codemirror_mode": { |
| 623 | + "name": "ipython", |
| 624 | + "version": 3 |
| 625 | + }, |
| 626 | + "file_extension": ".py", |
| 627 | + "mimetype": "text/x-python", |
| 628 | + "name": "python", |
| 629 | + "nbconvert_exporter": "python", |
| 630 | + "pygments_lexer": "ipython3", |
| 631 | + "version": "3.11.0rc1" |
| 632 | + } |
| 633 | + }, |
| 634 | + "nbformat": 4, |
| 635 | + "nbformat_minor": 2 |
| 636 | +} |
0 commit comments