Skip to content

Commit 41e819e

Browse files
committedOct 28, 2024·
Added example solutions for lab01
1 parent 1064322 commit 41e819e

File tree

1 file changed

+636
-0
lines changed

1 file changed

+636
-0
lines changed
 

‎labs/lab01/lab01_solutions.ipynb

+636
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,636 @@
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

Comments
 (0)
Please sign in to comment.