Домашни > Функцията, която казва "Ni!"


Функцията, която казва "Ni!"
Краен срок: 22.10.2024 18:00
Точки: 10

### Контекст Тъй като все още не сте добили достатъчно познания за света на Монти Пайтън, искаме да ви обогатим със следния откъс от техен филм: [![The Knights Who Say "Ni!" - Monty Python and the Holy Grail](/media/resources/homework2.png)](https://www.youtube.com/watch?v=zIV4poUZAQo) ### Въведение Целта на домашното е да напишете функция, която симулира рицарите, които казват "Ni!". Както става ясно от видеото, рицарите винаги отговарят с "Ni!", освен ако не им бъде предоставено "shrubbery" (храсталак) и то само при условие, че храсталакът е "one that looks nice" (такъв, който изглежда хубаво) и "not too expensive" (не прекалено скъп). Е, не винаги, но ние ще го опростим. Целта ви е да напишете функция, която оценява получения си вход и определя дали е получила храсталак, който изглежда хубаво и не е прекалено скъп. ### Задачата Напишете функцията `function_that_says_ni`, която приема произволен брой позиционни и/или именувани параметри. Стойностите на тези параметри могат да бъдат от произволен тип, но тези, които касаят поведението на функцията, са само аргументите от тип `dict` (речник). Всички останали могат да бъдат пренебрегнати. Ако аргумент на функцията (било то позиционен или именуван) е от тип `dict`, то той е потенциално "храст". Речник, който описва "храст", трябва да отговаря на следното условие: **Речникът има ключ от тип `str` със стойност `name`, зад който ключ има стойност от тип `str` и някоя от следните стойности "храст", "shrub", "bush". Може и в комбинация от малки/главни букви.** Речник, който описва "храст", може да съдържа и друг ключ от тип `str` със стойност `cost`, зад който ключ има стойност от тип `int` или `float`, показващ цената на дадения храст, но такъв ключ може и да не се съдържа в речника. Речникът може да има и други ключове, но всичко извън `name` и `cost` не ни интересува. Например: ``` {"name": "чегарак", "cost": 1.00} # Не е "храст", защото "чегарак" не фигурира в списъка по-горе {"name": "храст", "cost": 2.00} # Това е "храст" {"name": "shrub", "cost": 3.00} # Това е "храст" {"name": "bush"} # Това е "храст". Цената не е задължителна. ``` Целта ви е да оцените всички "храст"-и, които вашата функция приеме като вход, и да прецените дали сте удовлетворени, или не. В случай, че не сте получили храсталак (комбинация от "храсти"-и), който изглежда хубаво и не е прекалено скъп, функцията ви трябва да връща `str` със стойност "Ni!". В противен случай трябва да върнете `str`, който съответства на цената на храсталака, който сте получили, във формата "1.23лв" (винаги закръглен до два символа след десетичната запетая и включвайки "лв" накрая. #### Цената (not too expensive) Цената на храсталак се определя като сума от всички "храст"-и, които функцията ви е получила като вход. Един храсталак е прекалено скъп, ако цената му е повече от 42.00лв. #### Хубостта (one that looks nice) Един храсталак изглежда хубав, когато броят от уникалните букви, които са използвани за изписване на имената на всички именувани аргументи на функцията, които представляват "храст"-и, е кратен на целочислената част от цената му. *Кратно се нарича цяло число, което се дели на друго число без остатък. Например: Ако цената на храсталака, който сте получили, е "2.80лв", то той е по-евтин от 42.00лв, но за да удовлетворите функцията, трябва да се уверите и че храсталакът е хубав. Хубав храсталак в този случай би бил само такъв, че броят от уникалните буквите, използвани в имена на именувани аргументи, които представляват "храст"-и, е кратен на 2 (цялата част от 2.80). ``` function_that_says_ni(ab={"name": "храст", "cost": 1.80}, bc={"name": "храст", "cost": 1}) # Броят уникалните букви, използвани за дефиниране на имената на именувани аргументи, е 3 ("a", "b" и "c"). # 3 не е кратно на 2 (цялата част от сумата), така че това не е хубав храсталак. ``` ### Уговорки * "Храст", който има `cost` от тип `float`, може да има стойност най-много до втора цифра след десетичната запетая. Няма да тестваме с по-висока точност. * "Храст", който няма поле с ключ `cost`, се счита за безплатен, т.е. има цена 0. * "Храст", който има поле с ключ `cost`, може да има само стойности `int` и `float` зад този ключ. Няма да тестваме с невалидни цени зад запазеното име за това. * Не очакваме да има такива недоразумения, но за всеки случай - няма да тестваме с вложени храсти. С други думи, не очаквайте аргумент, подаден на функцията, да е колекция, някоя от чиито стойности да е речник, който изпълнява условията за храст. Храст може да бъде само стойност на аргумент, подаден към функцията, но не и речници, които са част от колекция, подадена като аргумент. * Забележете, че при определяне на хубостта се използва целочислената част от общата цена на храсталака (включително цената на тези "храст"-и, които са влезли като позиционни аргументи), но само имената на именуваните аргументи, които са "храст"-и. * В случай, че целочислената част на храсталака е нула, няма как да дефинираме кратност при търсене на хубост, така че приемаме, че храсталакът не е хубав. * Ключовете на речник, които дефинират специална функция са "name" и "cost" и те винаги ще бъдат подавани с малки букви, точно така, както са написани в това изречение. Стойностите, които дефинират храст ("храст", "shrub", "bush"), могат да бъдат подавани и като комбинация от малки/главни букви (например "SHruB"). * Говорейки за имена на именувани аргументи и техния брой "уникални букви", включваме и случаите, в които измежду тези букви има символа "_". Нека този символ се брои за валидна буква. Като изключим този специален символ, всички останали символи ще са малки букви от латиницата, както се препоръчва в PEP8. Не очаквайте комбинации от малки и главни букви там. * **Генерално правило и занапред - очакваме от функциите да върнат резултат, а не да принтират каквото и да било.** ### Пример ``` function_that_says_ni({"name": "храст", "cost": 120}) > "Ni!" # Независимост от всичко останало, очевидно този храсталак е прекалено скъп function_that_says_ni({"name": "храст", "cost": 1}) > "1.00лв" # Цената е под максимума, а общият брой букви, използвани като именувани аргументи за дефиниране на "храст"-и, е 0, което е кратно на 1. function_that_says_ni(aabcc={"name": "bush", "cost": 3.20}) > "3.20лв" # Цената е под максимума. Общият брой уникални букви, използвани като именувани аргументи за дефиниране на "храст"-и, е 3 ("a", "b", "c"). # Целочислената част на цената също е 3. Кратността е изпълнена, т.е. храсталакът е ок и връщаме цената. ```
 1import unittest
 2
 3import solution
 4
 5
 6class TestSanity(unittest.TestCase):
 7    """Check if the function is present."""
 8
 9    def test_function(self):
10        self.assertIn('function_that_says_ni', dir(solution), 'Убеди се, че функцията "function_that_says_ni" е налична с точно това име.')
11        self.assertTrue(callable(solution.function_that_says_ni), 'Убеди се, че "function_that_says_ni" е функция.')
12
13
14if __name__ == '__main__':
15    unittest.main()
 1import unittest
 2
 3from solution import *
 4
 5
 6class TestNi(unittest.TestCase):
 7    """Test all requirements related to the function that says 'Ni!'."""
 8
 9    NI = 'Ni!'
10
11    def test_empty(self):
12        """Test with empty input."""
13        self.assertEqual(function_that_says_ni(), self.NI)
14
15    def test_single_with_no_named_args(self):
16        """Test with a single shrub that is passed as positional argument."""
17        self.assertEqual(function_that_says_ni({'name': 'shrub', 'cost': 3.12}), '3.12лв')
18
19    def test_all_possible_shrub_strings(self):
20        """Test with all possible strings that define a shrub."""
21        self.assertEqual(function_that_says_ni({'name': 'shrub', 'cost': 1}), '1.00лв')
22        self.assertEqual(function_that_says_ni({'name': 'bush', 'cost': 1}), '1.00лв')
23        self.assertEqual(function_that_says_ni({'name': 'храст', 'cost': 1}), '1.00лв')
24        self.assertEqual(function_that_says_ni({'name': 'SHrub', 'cost': 1}), '1.00лв')
25        self.assertEqual(function_that_says_ni({'name': 'buSH', 'cost': 1}), '1.00лв')
26        self.assertEqual(function_that_says_ni({'name': 'хрАст', 'cost': 1}), '1.00лв')
27
28    def test_with_no_cost(self):
29        """Test with a shrub without defined cost."""
30        self.assertEqual(function_that_says_ni({'name': 'shrub'}), self.NI)
31
32    def test_multiple_shrubs_sumс(self):
33        """Test with a multiple shrubs and cornercase costs."""
34        self.assertEqual(function_that_says_ni({'name': 'shrub', 'cost': 20.00},
35                                               {'name': 'shrub', 'cost': 22}), '42.00лв')
36        self.assertEqual(function_that_says_ni({'name': 'shrub', 'cost': 19.99},
37                                               {'name': 'shrub', 'cost': 22}), '41.99лв')
38        self.assertEqual(function_that_says_ni({'name': 'shrub', 'cost': 20.01},
39                                               {'name': 'shrub', 'cost': 22}), self.NI)
40
41    def test_cost_whole_part_zero(self):
42        """Test with a total cost part equal to zero."""
43        self.assertEqual(function_that_says_ni({'name': 'shrub', 'cost': 0.1},
44                                               {'name': 'храст', 'cost': 0.2}), self.NI)
45
46    def test_named_arguments(self):
47        """Test with named arguments."""
48        # Case with whole-part cost exactly
49        # devided by number of unique chars = 7
50        self.assertEqual(function_that_says_ni(_abcde={'name': 'shrub', 'cost': 2.82},
51                                               _abcf={'name': 'bush', 'cost': 4.51}), '7.33лв')
52        # Case with whole-part cost exactly
53        # devided by number of ALL chars (3),
54        # but not by number of unique chars (2)
55        self.assertEqual(function_that_says_ni(a={'name': 'shrub', 'cost': 1},
56                                               ab={'name': 'bush', 'cost': 2}), self.NI)
57
58    def test_combination_of_arguments(self):
59        """Test with combination of named and positional arguments."""
60        self.assertEqual(function_that_says_ni({'name': 'bush', 'cost': 1},
61                                               {'name': 'храст', 'cost': 1},
62                                               a={'name': 'shrub', 'cost': 0},
63                                               ab={'name': 'bush', 'cost': 0}), '2.00лв')
64
65    def test_invalid_strings(self):
66        """Test with invalid strings that might be misinterpreted."""
67        self.assertEqual(function_that_says_ni({'no_name': 'bush', 'cost': 1}), self.NI)
68        self.assertEqual(function_that_says_ni({'name': 'not a shrub', 'cost': 1}), self.NI)
69        self.assertEqual(function_that_says_ni({'name': 'shrub', ' cost': 1}), self.NI) # Space before cost
70
71
72    def test_other_than_dicts(self):
73        """Test with inputs other than dicts."""
74        self.assertEqual(function_that_says_ni(1, 3.14, ['some_list'], some_arg={1, 2, 3}), self.NI)
75
76
77if __name__ == '__main__':
78    unittest.main()
Дискусия
Георги Кунчев
16.10.2024 11:42

Тъй като сме написали само букви и "_", няма да тестваме с цифри, но едва ли наличието на цифри би повлияло на имплементацията ти.
Николай Стоянов
16.10.2024 11:22

Здравейте. Имената на именувани аргументи могат ли да съдържат цифри?