Домашни > Да върнем левчето обратно!


Да върнем левчето обратно!
Краен срок: 11.03.2026 18:00
Точки: 6

Вчера беше е [трети март, националният празник на България](https://bg.wikipedia.org/wiki/%D0%94%D0%B5%D0%BD_%D0%BD%D0%B0_%D0%9E%D1%81%D0%B2%D0%BE%D0%B1%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%D1%82%D0%BE_%D0%BD%D0%B0_%D0%91%D1%8A%D0%BB%D0%B3%D0%B0%D1%80%D0%B8%D1%8F_%D0%BE%D1%82_%D0%BE%D1%81%D0%BC%D0%B0%D0%BD%D1%81%D0%BA%D0%BE_%D0%B8%D0%B3%D0%BE). И както ви обещахме в [тази новина](https://2024.py-fmi.org/news/19), приготвили сме ви страшно патриотично домашно... В контекста на тазгодишните събития. ![Да видите, че сме патриоти!](/media/resources/homework-1.png) ### Да се подготвим за връщане на левчето Не знаем за вас, но ние сме разстроени, че ни махнаха левчето. Някои от нас защото вдигнаха цените на хубавото българско кисело мляко, други - цените на традиционните български мартеници, трети - цените на кокаина. Освен инфлацията, не знаем какво ще се случи с магазините за едно левче, дали няма да преименуват Левски _(отбора, не човека, не могат да пипнат националните герои)_ и Боро Първи ще запише ли наново песента си [Левче](https://www.youtube.com/watch?v=i1a-3vzA7JU&list=RDi1a-3vzA7JU), пеейки за едно евро, така че да стане евъргрийн. Знаем едно, обаче, няма да оставим нещата така! Отказваме да потвърдим или отхвърлим дали сме предприели, законови или не, мерки за връщане на левчето обратно. Все пак ще сме ви благодарни ако ни помогнете да се подготвим за това, ако случайно каузата ни е успешна. ### Задачата От вас ще искаме да напишете две функции, които да работят с чужда валута. Едната от тях ще конвертира чужда валута в левчета, а другата ще проверява дали сумата, която се получава, е патриотична или не. #### `курс_в_лева` Напишете функцията `курс_в_лева`, която приема речник - набор от трибуквени идентификатори на валути и обменен курс. Вероятно сте забелязали, че в чейндж бюрата курсът обикновено се пише "спрямо" другата единица, а не спрямо лева. С други думи - виждали сте курс EUR / BGN - `1.9558`. Тоест `1.9558` лева за `1` евро. Да, ама ние сме патриоти, и искаме да е обратното, искаме да покажем колко евро са едно левче - `0.5113`. Всеки от обменните курсове, ще бъде във вида "левчета _за една единица от чуждестранна валута_" Функцията трябва да го конвертира във вида "чуждестранна валута **за едно левче**", и да ни върне речник със съответните стойности например: ``` exchange_rates = {"EUR": 1.9558, "USD": 1.6718, "DKK": 0.2616} курс_в_лева(exchange_rates) # {"EUR": 0.5113, "USD": 0.5982, "DKK": 3.8226} ``` ##### Уговорки - Закръгляме всичко до 4-тия символ след десетичната запетая, включително. #### `валута_към_левчета` - Произволен брой позиционни аргументи - двойки във вида `(идентификатор, брой_единици_от_валутата)`. - Произволен брой именувани аргументи, където името е идентификатор, а стойността - обменен курс "брой единици чуждестранна валута **за едно левче**". Функцията трябва да връща колекция от двойки във вида `(идентификатор, брой_единици_в_лева)`, в която са сумирани всички единици от дадената валута и конвертирани в лева. Например: ``` валута_към_левчета( ("EUR", 1.5), ("USD", 10), ("DKK", 10), ("EUR", 2.5), EUR=0.5, USD=0.8, DKK=7, ) # [("USD", 12.5), ("DKK", 1.4286), ("EUR", 8)] ``` Функцията може да срещне и левчета сред позиционните аргументи ги "конвертира" 1:1: ``` валута_към_левчета( ("BGN", 1.5), ("USD", 10), USD=0.8, ) # [("USD", 12.5), ("BGN", 1.5)] ``` ##### Уговорки - Типът на върнатата колекция е без значение. - Подредбата няма значение. - Няма да тестваме функциите с вход, който не съдържа обменни курсове за всяка валута. - Закръгляме всичко до 4-тия символ след десетичната запетая, включително. - Курс за лева няма да бъде подаван на функцията, тъй като би било непатриотично. #### `е_патриотична` Функцията `е_патриотична` приема: - Позиционен аргумент - списък от двойки, първият елемент на които е идентификатор на валута, а вторият - брой единици от въпросната валута. - Втори позиционен аргумент - речник, където ключовете са познатите трибуквени идентификатори на валутите, а стойностите - обменен курс "брой единици чуждестранна валута **за едно левче**". Например: ``` exchange_rates = {"EUR": 0.5113, "USD": 0.5982, "DKK": 3.8226} amount = [("EUR", 1.34), ("USD", 10.23), ("DKK", 0.12), ("EUR", 1.5)] е_патриотична(amount, exchange_rates) ``` Ако сумата на всички пари в списъка, конвертирани в лева, е кръгло число **(с прецизност 2 символа след десетичната запетая, т.е. - стотинки)** - то тя е патриотична и връща стринга `"ПАТРИОТИЧНА!"`, ако не е - връща стринга `"НЕПАТРИОТИЧНА!"`. Пример: ``` exchange_rates = {"EUR": 0.5, "USD": 0.6, "DKK": 3.8} amount = [("EUR", 1), ("USD", 3), ("DKK", 7.6), ("EUR", 3)] е_патриотична(amount, exchange_rates) # ПАТРИОТИЧНА! - 4 / 0.5 + 3 / 0.6 + 7.6 / 3.8 = 15 amount = [("EUR", 1), ("USD", 2), ("DKK", 7.6), ("EUR", 3)] е_патриотична(amount, exchange_rates) # НЕПАТРИОТИЧНА! - 4 / 0.5 + 2 / 0.6 + 7.6 / 3.8 = 13.33, опитват се да ни измамят ``` ##### Уговорки - Няма да тестваме функциите с вход, който не съдържа обменни курсове за всяка валута. - Прецизността е до стотинка, т.е. `7.00003` го третираме като `7.00`, следователно е патриотична стойност.
 1import unittest
 2
 3import solution
 4
 5
 6class TestSanity(unittest.TestCase):
 7    """Check if all data is present."""
 8
 9    def test_requirements(self):
10        names = [
11            "курс_в_лева",
12            "валута_към_левчета",
13            "е_патриотична",
14        ]
15        unimported = [name for name in names if name not in dir(solution)]
16        self.assertEqual(
17            unimported, [], "\n\nЕлементите по-горе липсват (проверете си имената)!"
18        )
19
20
21if __name__ == "__main__":
22    unittest.main()
  1import unittest
  2
  3from solution import *
  4
  5
  6class TestКурсВЛева(unittest.TestCase):
  7    def test_курс_в_лева(self):
  8        """Output should be properly converted to BGN-relative exchange rate."""
  9        exchange_rate = {"NOK": 1, "HKD": 2, "NZD": 0.5}
 10        exchange_rate_in_bgn = {"NOK": 1, "HKD": 0.5, "NZD": 2}
 11        self.assertEqual(exchange_rate_in_bgn, курс_в_лева(exchange_rate))
 12
 13    def test_курс_в_лева_rounding(self):
 14        """Output should be rounded to the 4th digit after the decimal point."""
 15        exchange_rate = {"NOK": 0.1740, "HKD": 0.2147, "NZD": 0.9941, "CHF": 1.1011}
 16        exchange_rate_in_bgn = {
 17            "NOK": 5.7471,
 18            "HKD": 4.6577,
 19            "NZD": 1.0059,
 20            "CHF": 0.9082,
 21        }
 22        self.assertEqual(exchange_rate_in_bgn, курс_в_лева(exchange_rate))
 23
 24
 25class TestВалутаКъмЛевчета(unittest.TestCase):
 26    def assertContainersSomewhatEqual(self, expected, actual):
 27        """Assert if the contents of the containers are equal, regardless of their type."""
 28        if isinstance(expected, dict):
 29            expected = list(expected.items())
 30        if isinstance(actual, dict):
 31            actual = list(actual.items())
 32        not_found = [item for item in expected if item not in actual]
 33        extra = [item for item in actual if item not in expected]
 34        if (not_found, extra) != ([], []):
 35            raise AssertionError(
 36                "Collections mismatch (type-independent):\n"
 37                "Items in the first collection but not the second:\n"
 38                if not_found
 39                else ""
 40                f"{not_found}\n"
 41                "Items in the second collection but not the second:\n"
 42                if extra
 43                else f"{extra}\n"
 44            )
 45
 46    def test_валута_към_левчета_takes_variable_length_args(self):
 47        """Function should take a variable length of args and kwargs."""
 48        валута_към_левчета(
 49            ("EUR", 1.5),
 50            ("USD", 10),
 51            ("DKK", 10),
 52            ("EUR", 2.5),
 53            ("EUR", 1.5),
 54            ("USD", 10),
 55            ("DKK", 10),
 56            ("EUR", 2.5),
 57            EUR=0.5,
 58            USD=0.8,
 59            DKK=7,
 60            NOK=0.5,
 61            NZD=0.8,
 62            JPY=7,
 63        )
 64
 65    def test_валута_към_левчета_with_bgn(self):
 66        """Entries with BGN should be converted 1:1."""
 67        expected_in_leva = {("HUF", 10), ("BGN", 10000.5)}
 68        in_leva = валута_към_левчета(
 69            ("HUF", 10),
 70            ("BGN", 10000.5),
 71            HUF=1,
 72        )
 73        self.assertContainersSomewhatEqual(expected_in_leva, in_leva)
 74
 75    def test_валута_към_левчета_sums(self):
 76        """Entries for the same currency should be summed up."""
 77        expected_in_leva = {("TRY", 6), ("JPY", 1010)}
 78        in_leva = валута_към_левчета(
 79            ("JPY", 10),
 80            ("TRY", 5),
 81            ("TRY", 1),
 82            ("JPY", 1000),
 83            JPY=1,
 84            TRY=1,
 85        )
 86        self.assertContainersSomewhatEqual(expected_in_leva, in_leva)
 87
 88    def test_валута_към_левчета_conversion(self):
 89        """Entries should be converted to BGN."""
 90        expected_in_leva = {("THB", 10), ("ZAR", 50)}
 91        in_leva = валута_към_левчета(
 92            ("THB", 100),
 93            ("ZAR", 5),
 94            THB=10,
 95            ZAR=0.1,
 96        )
 97        self.assertContainersSomewhatEqual(expected_in_leva, in_leva)
 98
 99    def test_валута_към_левчета_rounding(self):
100        """Output should be rounded to the 4th digit after the decimal point."""
101        expected_in_leva = {("SGD", 6.5876), ("PHP", 2.8715)}
102        in_leva = валута_към_левчета(
103            ("SGD", 5),
104            ("PHP", 100),
105            PHP=34.8251,
106            SGD=0.759,
107        )
108        self.assertContainersSomewhatEqual(expected_in_leva, in_leva)
109
110
111class TestЕПатриотична(unittest.TestCase):
112    def test_е_патриотична_цели(self):
113        """The total should be evaluated by means of whether the sum is a round number."""
114        патриотична = е_патриотична(
115            [("ASL", 10), ("PLS", 5)],
116            {"ASL": 10, "PLS": 0.1},
117        )
118        self.assertEqual("ПАТРИОТИЧНА!", патриотична)
119
120        непатриотична = е_патриотична(
121            [("ASL", 9), ("PLS", 5)],
122            {"ASL": 10, "PLS": 0.1},
123        )
124        self.assertEqual("НЕПАТРИОТИЧНА!", непатриотична)
125
126    def test_е_патриотична_стотинки(self):
127        """The total should be evaluated with a precision of 2 digits after the decimal point."""
128        патриотична = е_патриотична(
129            [("KRW", 10000), ("INR", 4856)],
130            {"KRW": 872.4512, "INR": 54.846},
131        )
132        self.assertEqual("ПАТРИОТИЧНА!", патриотична)
133
134        непатриотична = е_патриотична(
135            [("KRW", 10000), ("INR", 4860)],
136            {"KRW": 872.4512, "INR": 54.846},
137        )
138        self.assertEqual("НЕПАТРИОТИЧНА!", непатриотична)
139
140    def test_е_патриотична_no_side_effects(self):
141        """There should be no side effects on the input data."""
142        currency = [("MYR", 20), ("MYR", 25)]
143        rates = {"MYR": 0.4287}
144        е_патриотична(
145            currency,
146            rates,
147        )
148        self.assertEqual(
149            currency, [("MYR", 20), ("MYR", 25)], "Function has side effects"
150        )
151        self.assertEqual(rates, {"MYR": 0.4287}, "Function has side effects")
152
153
154if __name__ == "__main__":
155    unittest.main()
Дискусия
Николина Маджарова
05.03.2026 17:14

В примера за курс-в-лева при EUR = 1.9558 се изчислява 1 / 1.9558 = 0.51129…, което се закръглява на 0.5113, т.е. петият знак се взима предвид. В примера за валута-към-левчета при 10 / 7 = 1.42857… се закръглява на 1.4285, т.е. петият знак не се взима предвид. Кое закръгляне е правилното, понеже в "уговорките" за двете функции пише един и същ начин?
Нишка
Виктор Бечев
05.03.2026 17:45

Закръгля се математически, не се игнорират цифри. Мерси, оправено!
Ариф Мехмедали
05.03.2026 09:10

В описанието на функцията е_патриотична пише, че стойностите в речника са "брой единици чуждестранна валута за едно левче". Например 1BGN = 0.5EUR. Ако имаме 4EUR, не трябва ли да ги конвертираме като 4/0.5, а не като 4 * 0.5, както е в примера?
Нишка
Виктор Бечев
05.03.2026 10:50

Уточнение за изпусналите - това е в примерите на `е_патриотична` и е абсолютно на място. Мерси, оправено _(заедно с няколко други неща)_.