From 46ac9ba1bca6b3ea190a31869ebc13bfc9e75e10 Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:36 +0000 Subject: [PATCH 01/75] [Sync Iteration] python/hello-world/1 --- solutions/python/hello-world/1/hello_world.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 solutions/python/hello-world/1/hello_world.py diff --git a/solutions/python/hello-world/1/hello_world.py b/solutions/python/hello-world/1/hello_world.py new file mode 100644 index 0000000..d695ea1 --- /dev/null +++ b/solutions/python/hello-world/1/hello_world.py @@ -0,0 +1,2 @@ +def hello(): + return 'Hello, World!' From d33d5f1cb50d6f408478bab17798d7964cbccaa1 Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:37 +0000 Subject: [PATCH 02/75] [Sync Iteration] cpp/hello-world/1 --- solutions/cpp/hello-world/1/hello_world.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 solutions/cpp/hello-world/1/hello_world.cpp diff --git a/solutions/cpp/hello-world/1/hello_world.cpp b/solutions/cpp/hello-world/1/hello_world.cpp new file mode 100644 index 0000000..142ad1f --- /dev/null +++ b/solutions/cpp/hello-world/1/hello_world.cpp @@ -0,0 +1,16 @@ +#include "hello_world.h" + +// Use everything from the 'std' namespace. +// This lets us write 'string' instead of 'std::string'. +using namespace std; + +namespace hello_world { + +// Define the function itself. This could have also been written as: +// std::string hello_world::hello() +string hello() { + // Return the string we need. + return "Hello, World!"; +} + +} // namespace hello_world From c078eb76fd3f47fc90c4d602d23f29cfc03e450f Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:37 +0000 Subject: [PATCH 03/75] [Sync Iteration] cpp/leap/1 --- solutions/cpp/leap/1/leap.cpp | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 solutions/cpp/leap/1/leap.cpp diff --git a/solutions/cpp/leap/1/leap.cpp b/solutions/cpp/leap/1/leap.cpp new file mode 100644 index 0000000..9d9d360 --- /dev/null +++ b/solutions/cpp/leap/1/leap.cpp @@ -0,0 +1,5 @@ +#include "leap.h" + +namespace leap { + +} // namespace leap From e3c2b4282dfbb80d7baf82125c6d8b215cf3ff76 Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:37 +0000 Subject: [PATCH 04/75] [Sync Iteration] cpp/leap/2 --- solutions/cpp/leap/2/leap.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 solutions/cpp/leap/2/leap.cpp diff --git a/solutions/cpp/leap/2/leap.cpp b/solutions/cpp/leap/2/leap.cpp new file mode 100644 index 0000000..ed6fdab --- /dev/null +++ b/solutions/cpp/leap/2/leap.cpp @@ -0,0 +1,15 @@ +#include "leap.h" + +namespace leap { + bool is_leap_year(int year) { + + //on every year that is evenly divisible by 4 + if (year % 4 == 0) { + return true; + } + else { + return false; + } + + } +} // namespace leap From 9577dfa6ceb524acc555c45cc986f8717f0882cd Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:37 +0000 Subject: [PATCH 05/75] [Sync Iteration] cpp/leap/3 --- solutions/cpp/leap/3/leap.h | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 solutions/cpp/leap/3/leap.h diff --git a/solutions/cpp/leap/3/leap.h b/solutions/cpp/leap/3/leap.h new file mode 100644 index 0000000..85341a5 --- /dev/null +++ b/solutions/cpp/leap/3/leap.h @@ -0,0 +1,8 @@ +#if !defined(LEAP_H) +#define LEAP_H + +namespace leap { + bool is_leap_year(int year); +} // namespace leap + +#endif // LEAP_H \ No newline at end of file From 0584e79f73015ab6089c3b74a0bc1af68aa9a21c Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:37 +0000 Subject: [PATCH 06/75] [Sync Iteration] cpp/leap/4 --- solutions/cpp/leap/4/leap.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 solutions/cpp/leap/4/leap.cpp diff --git a/solutions/cpp/leap/4/leap.cpp b/solutions/cpp/leap/4/leap.cpp new file mode 100644 index 0000000..ed6fdab --- /dev/null +++ b/solutions/cpp/leap/4/leap.cpp @@ -0,0 +1,15 @@ +#include "leap.h" + +namespace leap { + bool is_leap_year(int year) { + + //on every year that is evenly divisible by 4 + if (year % 4 == 0) { + return true; + } + else { + return false; + } + + } +} // namespace leap From 657297d5e0452490a2c8a52a3b2c70635c39b80c Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:38 +0000 Subject: [PATCH 07/75] [Sync Iteration] cpp/leap/5 --- solutions/cpp/leap/5/leap.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 solutions/cpp/leap/5/leap.cpp diff --git a/solutions/cpp/leap/5/leap.cpp b/solutions/cpp/leap/5/leap.cpp new file mode 100644 index 0000000..c37abd6 --- /dev/null +++ b/solutions/cpp/leap/5/leap.cpp @@ -0,0 +1,28 @@ +#include "leap.h" + +namespace leap { + //The tricky thing here is that a leap year in the Gregorian calendar occurs: + bool is_leap_year(int year) { + + //on every year that is evenly divisible by 4 + if (year % 4 == 0) { + + //except every year that is evenly divisible by 100 + if (year % 100 == 0) { + + //unless the year is also evenly divisible by 400 + if (year % 400 == 0) { + return true; + } + return false; + } + else { + return true; + }; + } + else { + return false; + } + + } +} // namespace leap From e665319cdbb338121388916848a86386e67efef6 Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:38 +0000 Subject: [PATCH 08/75] [Sync Iteration] cpp/leap/6 --- solutions/cpp/leap/6/leap.cpp | 12 ++++++++++++ solutions/cpp/leap/6/leap.h | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 solutions/cpp/leap/6/leap.cpp create mode 100644 solutions/cpp/leap/6/leap.h diff --git a/solutions/cpp/leap/6/leap.cpp b/solutions/cpp/leap/6/leap.cpp new file mode 100644 index 0000000..3a0d4ae --- /dev/null +++ b/solutions/cpp/leap/6/leap.cpp @@ -0,0 +1,12 @@ +#include "leap.h" + +namespace leap { + //The tricky thing here is that a leap year in the Gregorian calendar occurs: + bool is_leap_year(int year) { + + //on every year that is evenly divisible by 4 + //except every year that is evenly divisible by 100 + //unless the year is also evenly divisible by 400 + return (year % 4 == 0 && year % 100 != 0) || (year % 100 == 0 && year % 400 == 0); + } +} // namespace leap diff --git a/solutions/cpp/leap/6/leap.h b/solutions/cpp/leap/6/leap.h new file mode 100644 index 0000000..85341a5 --- /dev/null +++ b/solutions/cpp/leap/6/leap.h @@ -0,0 +1,8 @@ +#if !defined(LEAP_H) +#define LEAP_H + +namespace leap { + bool is_leap_year(int year); +} // namespace leap + +#endif // LEAP_H \ No newline at end of file From f44c1cff53c9345ea29bd95374650a9e65df7b86 Mon Sep 17 00:00:00 2001 From: Exercism's Solution Syncer Bot <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:50:38 +0000 Subject: [PATCH 09/75] [Sync Iteration] cpp/leap/7 --- solutions/cpp/leap/7/leap.cpp | 12 ++++++++++++ solutions/cpp/leap/7/leap.h | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 solutions/cpp/leap/7/leap.cpp create mode 100644 solutions/cpp/leap/7/leap.h diff --git a/solutions/cpp/leap/7/leap.cpp b/solutions/cpp/leap/7/leap.cpp new file mode 100644 index 0000000..92756b7 --- /dev/null +++ b/solutions/cpp/leap/7/leap.cpp @@ -0,0 +1,12 @@ +#include "leap.h" + +namespace leap { + //The tricky thing here is that a leap year in the Gregorian calendar occurs: + bool is_leap_year(int year) { + + //on every year that is evenly divisible by 4 + //except every year that is evenly divisible by 100 + //unless the year is also evenly divisible by 400 + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + } +} // namespace leap diff --git a/solutions/cpp/leap/7/leap.h b/solutions/cpp/leap/7/leap.h new file mode 100644 index 0000000..85341a5 --- /dev/null +++ b/solutions/cpp/leap/7/leap.h @@ -0,0 +1,8 @@ +#if !defined(LEAP_H) +#define LEAP_H + +namespace leap { + bool is_leap_year(int year); +} // namespace leap + +#endif // LEAP_H \ No newline at end of file From 12ce7d4b214b55bde725d994ff6673cd7d9b3ad3 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 19:21:42 +0000 Subject: [PATCH 10/75] [Sync Iteration] python/guidos-gorgeous-lasagna/1 --- .../guidos-gorgeous-lasagna/1/lasagna.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 solutions/python/guidos-gorgeous-lasagna/1/lasagna.py diff --git a/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py b/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py new file mode 100644 index 0000000..595a253 --- /dev/null +++ b/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py @@ -0,0 +1,63 @@ +""" +Functions used in preparing Guido's gorgeous lasagna. + +Learn about Guido, the creator of the Python language: +https://en.wikipedia.org/wiki/Guido_van_Rossum + +This is a module docstring, used to describe the functionality +of a module and its functions and/or classes. +""" + + +EXPECTED_BAKE_TIME: int = 40 +PREPARATION_TIME: int = 2 + + +def bake_time_remaining(elapsed_bake_time: int) -> int: + """ + Calculate the bake time remaining. + + Function that takes the actual minutes the lasagna has been in the oven as + an argument and returns how many minutes the lasagna still needs to bake + based on the `EXPECTED_BAKE_TIME`. + + :param elapsed_bake_time: int - baking time already elapsed. + :return: int - remaining bake time (in minutes) derived from 'EXPECTED_BAKE_TIME'. + """ + return EXPECTED_BAKE_TIME - elapsed_bake_time + + +def preparation_time_in_minutes(number_of_layers: int) -> int: + """ + Calculate preparation time in minutes + + Takes the `number_of_layers` you want to add to the lasagna as an argument and + returns how many minutes you would spend making them. + + Assume each layer takes 2 minutes to prepare. + + :param number_of_layers: Number of layers you want to add to the lasagna. + :type number_of_layers: int + :return: Preparation time in minutes + :rtype: int + """ + return int(PREPARATION_TIME * number_of_layers) + + +def elapsed_time_in_minutes(number_of_layers: int, + elapsed_bake_time: int) -> int: + """ + Calculate elapsed time in minutes. + + Return the total minutes you have been in the kitchen cooking — your preparation time layering + + the time the lasagna has spent baking in the oven. + + :param number_of_layers: The number of layers added to the lasagna. + :type number_of_layers: int + :param elapsed_bake_time: The number of minutes the lasagna has spent baking in the oven already. + :type elapsed_bake_time: int + :return: Elapsed time in minutes. + :rtype: int + """ + return preparation_time_in_minutes(number_of_layers=number_of_layers) + ( + EXPECTED_BAKE_TIME - bake_time_remaining(elapsed_bake_time)) From 48f375a35b3f5b1dc699f57831555951ad6bb806 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:18:29 +0000 Subject: [PATCH 11/75] [Sync Iteration] python/currency-exchange/1 --- .../python/currency-exchange/1/exchange.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 solutions/python/currency-exchange/1/exchange.py diff --git a/solutions/python/currency-exchange/1/exchange.py b/solutions/python/currency-exchange/1/exchange.py new file mode 100644 index 0000000..2632aa2 --- /dev/null +++ b/solutions/python/currency-exchange/1/exchange.py @@ -0,0 +1,93 @@ +""" +Functions for calculating steps in exchanging currency. + +Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex + +Overview of exchanging currency when travelling: https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ +""" + + +def exchange_money(budget: float, + exchange_rate: float) -> float: + """ + Return the value of the exchanged currency. + + Note: If your currency is USD and you want to exchange USD for EUR with + an exchange rate of `1.20`, then `1.20 USD == 1 EUR`. + + :param budget: float - amount of money you are planning to exchange. + :param exchange_rate: float - unit value of the foreign currency. + :return: float - exchanged value of the foreign currency you can receive. + """ + return budget / exchange_rate + + +def get_change(budget: float, + exchanging_value: float) -> float: + """ + Return the amount of money that "is left" from the budget. + + :param budget: float - amount of money you own. + :param exchanging_value: float - amount of your money you want to exchange now. + :return: float - amount left of your starting currency after exchanging. + """ + return budget - exchanging_value + + +def get_value_of_bills(denomination: float, + number_of_bills: float) -> float: + """ + Return only the total value of the bills (excluding fractional amounts) the booth would give back. + + The total you receive must be divisible by the value of one "bill" or unit, + which can leave behind a fraction or remainder. + + :param denomination: int - the value of a bill. + :param number_of_bills: int - total number of bills. + :return: int - calculated value of the bills. + """ + return denomination * number_of_bills + + +def get_number_of_bills(amount: float, + denomination: int) -> int: + """ + Return the _number of currency bills_ that you can receive within the given _amount_. + + :param amount: float - the total starting value. + :param denomination: int - the value of a single bill. + :return: int - number of bills that can be obtained from the amount. + """ + return int(amount // denomination) + + +def get_leftover_of_bills(amount: float, + denomination: int) -> float: + """ + Return the _leftover amount_ that cannot be returned from your starting _amount_ + given the denomination of bills. + + :param amount: float - the total starting value. + :param denomination: int - the value of a single bill. + :return: float - the amount that is "leftover", given the current denomination. + """ + return amount % denomination + + +def exchangeable_value(budget: float, + exchange_rate: float, + spread: int, + denomination: int) -> int: + """ + Return the maximum value of the new currency after calculating the *exchange rate* plus the *spread*. + + :param budget: float - the amount of your money you are planning to exchange. + :param exchange_rate: float - the unit value of the foreign currency. + :param spread: int - percentage that is taken as an exchange fee. + :param denomination: int - the value of a single bill. + :return: int - maximum value you can get. + """ + total_exchange_rate = exchange_rate + (exchange_rate * spread / 100) + foreign_currency = exchange_money(budget, total_exchange_rate) + bills = get_number_of_bills(foreign_currency, denomination) + return int(get_value_of_bills(bills, denomination)) From 314325ecc2ad04e531fab3efc6b90ebfc0b2dd12 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:33:10 +0000 Subject: [PATCH 12/75] [Sync Iteration] python/ghost-gobble-arcade-game/1 --- .../ghost-gobble-arcade-game/1/arcade_game.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 solutions/python/ghost-gobble-arcade-game/1/arcade_game.py diff --git a/solutions/python/ghost-gobble-arcade-game/1/arcade_game.py b/solutions/python/ghost-gobble-arcade-game/1/arcade_game.py new file mode 100644 index 0000000..eb5fe64 --- /dev/null +++ b/solutions/python/ghost-gobble-arcade-game/1/arcade_game.py @@ -0,0 +1,48 @@ +"""Functions for implementing the rules of the classic arcade game Pac-Man.""" + + +def eat_ghost(power_pellet_active: bool, touching_ghost: bool) -> bool: + """ + Verify that Pac-Man can eat a ghost if he is empowered by a power pellet. + + :param power_pellet_active: bool - does the player have an active power pellet? + :param touching_ghost: bool - is the player touching a ghost? + :return: bool - can a ghost be eaten? + """ + return power_pellet_active and touching_ghost + + +def score(touching_power_pellet: bool, touching_dot: bool) -> bool: + """ + Verify that Pac-Man has scored when a power pellet or dot has been eaten. + + :param touching_power_pellet: bool - is the player touching a power pellet? + :param touching_dot: bool - is the player touching a dot? + :return: bool - has the player scored or not? + """ + return touching_power_pellet or touching_dot + + +def lose(power_pellet_active: bool, touching_ghost: bool) -> bool: + """ + Trigger the game loop to end (GAME OVER) when Pac-Man touches a ghost without his power pellet. + + :param power_pellet_active: bool - does the player have an active power pellet? + :param touching_ghost: bool - is the player touching a ghost? + :return: bool - has the player lost the game? + """ + return touching_ghost and not power_pellet_active + + +def win(has_eaten_all_dots: bool, + power_pellet_active: bool, + touching_ghost: bool) -> bool: + """ + Trigger the victory event when all dots have been eaten. + + :param has_eaten_all_dots: bool - has the player "eaten" all the dots? + :param power_pellet_active: bool - does the player have an active power pellet? + :param touching_ghost: bool - is the player touching a ghost? + :return: bool - has the player won the game? + """ + return has_eaten_all_dots and not lose(power_pellet_active, touching_ghost) From 3dbd1c144a236216ffe339cf6f741685950c75e0 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 02:19:27 +0000 Subject: [PATCH 13/75] [Sync Iteration] python/grains/1 --- solutions/python/grains/1/grains.py | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 solutions/python/grains/1/grains.py diff --git a/solutions/python/grains/1/grains.py b/solutions/python/grains/1/grains.py new file mode 100644 index 0000000..f59f376 --- /dev/null +++ b/solutions/python/grains/1/grains.py @@ -0,0 +1,36 @@ +"""Grains.""" + + +def square(number) -> int: + """ + Calculate the number of grains on a given square. + + One grain on the first square of a chessboard, with the number + of grains doubling on each successive square. + + :param number: A chessboard square + :type number: int + :return: number of grains on a given square. + :rtype: int + """ + if number <= 0 or number > 64: + raise ValueError(f"square must be between 1 and 64") + + if number == 1: + return 1 + + return 2 ** (number - 1) + + +def total() -> int: + """ + Calculate the total number of grains on the chessboard. + + A chessboard has 64 squares. Square 1 has one grain, + square 2 has two grains, square 3 has four grains, and so on, + doubling each time. + + :return: the total number of grains on the chessboard. + :rtype: int + """ + return sum(square(sqr) for sqr in range(1, 65)) From 36761b194fb0eb497110ebe7ec0a4ad914eade25 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 02:44:07 +0000 Subject: [PATCH 14/75] [Sync Iteration] python/armstrong-numbers/1 --- .../armstrong-numbers/1/armstrong_numbers.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 solutions/python/armstrong-numbers/1/armstrong_numbers.py diff --git a/solutions/python/armstrong-numbers/1/armstrong_numbers.py b/solutions/python/armstrong-numbers/1/armstrong_numbers.py new file mode 100644 index 0000000..0a14cd6 --- /dev/null +++ b/solutions/python/armstrong-numbers/1/armstrong_numbers.py @@ -0,0 +1,18 @@ +"""Armstrong Numbers.""" + + +def is_armstrong_number(number: int) -> bool: + """ + Test if "Armstrong Number". + + An Armstrong number is a number that is the sum of its own digits + each raised to the power of the number of digits. + + :param number: any integer number + :type number: int + :return: if "Armstrong Number" + :rtype: bool + """ + str_num: str = str(number) + n: int = len(str_num) + return number == sum(int(char) ** n for char in str_num) From 2c282230d4bd330e84c1dadacee362ba06354238 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 03:01:20 +0000 Subject: [PATCH 15/75] [Sync Iteration] python/collatz-conjecture/1 --- .../1/collatz_conjecture.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 solutions/python/collatz-conjecture/1/collatz_conjecture.py diff --git a/solutions/python/collatz-conjecture/1/collatz_conjecture.py b/solutions/python/collatz-conjecture/1/collatz_conjecture.py new file mode 100644 index 0000000..1b7ce6e --- /dev/null +++ b/solutions/python/collatz-conjecture/1/collatz_conjecture.py @@ -0,0 +1,39 @@ +""" +Collatz Conjecture. + +The rules were deceptively simple. Pick any positive integer. + +If it's even, divide it by 2. +If it's odd, multiply it by 3 and add 1. +Then, repeat these steps with the result, continuing indefinitely. + +Curious, you picked number 12 to test and began the journey: + +12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 +""" + + +def steps(number: int) -> int | ValueError: + """ + Return the number of steps it takes to reach 1 according to + the rules of the Collatz Conjecture. + + :param number: any positive integer + :type number: int + :return: number of steps it takes to reach 1 + :rtype: int + """ + if number < 1: + raise ValueError("Only positive integers are allowed") + + n_steps: int = 0 + while number > 1: + # If it's even, divide it by 2 + if number % 2 == 0: + number = number / 2 + # If it's odd, multiply it by 3 and add 1 + else: + number = (number * 3) + 1 + n_steps += 1 + + return n_steps From 88a4462a441d9682c08f7e1e4d48f844414e471f Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 03:07:59 +0000 Subject: [PATCH 16/75] [Sync Iteration] python/collatz-conjecture/2 --- .../2/collatz_conjecture.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 solutions/python/collatz-conjecture/2/collatz_conjecture.py diff --git a/solutions/python/collatz-conjecture/2/collatz_conjecture.py b/solutions/python/collatz-conjecture/2/collatz_conjecture.py new file mode 100644 index 0000000..7755bfc --- /dev/null +++ b/solutions/python/collatz-conjecture/2/collatz_conjecture.py @@ -0,0 +1,41 @@ +""" +Collatz Conjecture. + +The rules were deceptively simple. Pick any positive integer. + +If it's even, divide it by 2. +If it's odd, multiply it by 3 and add 1. +Then, repeat these steps with the result, continuing indefinitely. + +Curious, you picked number 12 to test and began the journey: + +12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 +""" + + +def steps(number: int) -> int: + """ + Return the number of steps it takes to reach 1 according to + the rules of the Collatz Conjecture. + + :param number: any positive integer + :type number: int + :return: number of steps it takes to reach 1 + :rtype: int + """ + if number < 1: + raise ValueError("Only positive integers are allowed") + + n_steps: int = 0 + while number > 1: + # If it's even, divide it by 2 + if number % 2 == 0: + # Switch to integer division + # keeps everything as int and avoids precision issues + number = number // 2 + # If it's odd, multiply it by 3 and add 1 + else: + number = (number * 3) + 1 + n_steps += 1 + + return n_steps From a51287b802eb16e971a8d20044aa3e04627ef262 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 03:16:09 +0000 Subject: [PATCH 17/75] [Sync Iteration] python/armstrong-numbers/2 --- .../armstrong-numbers/2/armstrong_numbers.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 solutions/python/armstrong-numbers/2/armstrong_numbers.py diff --git a/solutions/python/armstrong-numbers/2/armstrong_numbers.py b/solutions/python/armstrong-numbers/2/armstrong_numbers.py new file mode 100644 index 0000000..648d086 --- /dev/null +++ b/solutions/python/armstrong-numbers/2/armstrong_numbers.py @@ -0,0 +1,27 @@ +"""Armstrong Numbers.""" + + +def is_armstrong_number(number: int) -> bool: + """ + Test if "Armstrong Number". + + An Armstrong number is a number that is the sum of its own digits + each raised to the power of the number of digits. + + :param number: any integer number + :type number: int + :return: if "Armstrong Number" + :rtype: bool + + Examples: + >>> is_armstrong_number(153) + True + >>> is_armstrong_number(10) + False + """ + if number < 0: + raise ValueError("Only non-negative integers are allowed") + + str_num: str = str(number) + n: int = len(str_num) + return number == sum(int(char) ** n for char in str_num) From b831901ed498fb710f4e951a696664912ea6b5a6 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 03:24:22 +0000 Subject: [PATCH 18/75] [Sync Iteration] python/grains/2 --- solutions/python/grains/2/grains.py | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 solutions/python/grains/2/grains.py diff --git a/solutions/python/grains/2/grains.py b/solutions/python/grains/2/grains.py new file mode 100644 index 0000000..277079e --- /dev/null +++ b/solutions/python/grains/2/grains.py @@ -0,0 +1,34 @@ +"""Grains.""" + + +def square(number) -> int: + """ + Calculate the number of grains on a given square. + + One grain on the first square of a chessboard, with the number + of grains doubling on each successive square. + + :param number: A chessboard square + :type number: int + :return: number of grains on a given square. + :rtype: int + """ + if number <= 0 or number > 64: + raise ValueError("square must be between 1 and 64") + + return 2 ** (number - 1) + + +def total() -> int: + """ + Calculate the total number of grains on the chessboard. + + A chessboard has 64 squares. Square 1 has one grain, + square 2 has two grains, square 3 has four grains, and so on, + doubling each time. + + :return: the total number of grains on the chessboard. + :rtype: int + """ + # return sum(square(sqr) for sqr in range(1, 65)) + return 2 ** 64 - 1 From 256fdde9e16a2e4bfaa39e12dab0b194cfc2e31e Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Wed, 6 Aug 2025 22:29:19 -0700 Subject: [PATCH 19/75] Meltdown Mitigation --- meltdown-mitigation/.exercism/config.json | 22 +++ meltdown-mitigation/.exercism/metadata.json | 1 + meltdown-mitigation/HELP.md | 130 +++++++++++++++ meltdown-mitigation/HINTS.md | 50 ++++++ meltdown-mitigation/README.md | 172 ++++++++++++++++++++ meltdown-mitigation/conditionals.py | 71 ++++++++ meltdown-mitigation/conditionals_test.py | 82 ++++++++++ 7 files changed, 528 insertions(+) create mode 100644 meltdown-mitigation/.exercism/config.json create mode 100644 meltdown-mitigation/.exercism/metadata.json create mode 100644 meltdown-mitigation/HELP.md create mode 100644 meltdown-mitigation/HINTS.md create mode 100644 meltdown-mitigation/README.md create mode 100644 meltdown-mitigation/conditionals.py create mode 100644 meltdown-mitigation/conditionals_test.py diff --git a/meltdown-mitigation/.exercism/config.json b/meltdown-mitigation/.exercism/config.json new file mode 100644 index 0000000..eb88d8b --- /dev/null +++ b/meltdown-mitigation/.exercism/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "sachsom95", + "BethanyG" + ], + "contributors": [ + "kbuc" + ], + "files": { + "solution": [ + "conditionals.py" + ], + "test": [ + "conditionals_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "circular-buffer", + "blurb": "Learn about conditionals and avoid a meltdown by developing a simple control system for a Nuclear Reactor." +} diff --git a/meltdown-mitigation/.exercism/metadata.json b/meltdown-mitigation/.exercism/metadata.json new file mode 100644 index 0000000..979e3d2 --- /dev/null +++ b/meltdown-mitigation/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"meltdown-mitigation","id":"48c37b3f5b254b098fbf495b6ff1c7aa","url":"https://exercism.org/tracks/python/exercises/meltdown-mitigation","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/meltdown-mitigation/HELP.md b/meltdown-mitigation/HELP.md new file mode 100644 index 0000000..f931e6c --- /dev/null +++ b/meltdown-mitigation/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit conditionals.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/meltdown-mitigation/HINTS.md b/meltdown-mitigation/HINTS.md new file mode 100644 index 0000000..bee2779 --- /dev/null +++ b/meltdown-mitigation/HINTS.md @@ -0,0 +1,50 @@ +# Hints + +## General + +- The Python Docs on [Control Flow Tools][control flow tools] and the Real Python tutorial on [conditionals][real python conditionals] are great places to start. +- The Python Docs on [Boolean Operations][boolean operations] can be a great refresher on `bools`, as can the Real Python tutorial on [booleans][python booleans]. +- The Python Docs on [Comparisons][comparisons] and [comparisons examples][python comparisons examples] can be a great refresher for comparisons. + +## 1. Check for criticality + +- Comparison operators ([comparisons][comparisons review]) and boolean operations ([concept:python/bools]()) can be combined and used with conditionals. +- Conditional expressions must evaluate to `True` or `False`. +- `else` can be used for a code block that will execute when all conditional tests return `False`. + + ```python + >>> item = 'blue' + >>> item_2 = 'green' + + >>> if len(item) >= 3 and len(item_2) < 5: + print('Both pass the test!') + elif len(item) >= 3 or len(item_2) < 5: + print('One passes the test!') + else: + print('None pass the test!') + ... + One passes the test! + ``` + +## 2. Determine the Power output range + +- Comparison operators can be combined and used with conditionals. +- Any number of `elif` statements can be used as decision "branches". +- Each "branch" can have a separate `return`, although it might be considered "bad form" by linting tools. +- If the linter complains, consider assigning the output of a branch to a common variable, and then `return`ing that variable. + +## 3. Fail Safe Mechanism + +- Comparison operators can be combined and used with conditionals. +- Any number of `elif` statements can be used as decision "branches". +- Each "branch" can have a separate `return`, although it might be considered "bad form" by linting tools. +- If the linter complains, consider assigning the output of a branch to a common variable, and then `return`ing that variable. + + +[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not +[comparisons review]: https://www.learnpython.dev/02-introduction-to-python/090-boolean-logic/20-comparisons/ +[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons +[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html +[python booleans]: https://realpython.com/python-boolean/ +[python comparisons examples]: https://www.tutorialspoint.com/python/comparison_operators_example.htm +[real python conditionals]: https://realpython.com/python-conditional-statements/ \ No newline at end of file diff --git a/meltdown-mitigation/README.md b/meltdown-mitigation/README.md new file mode 100644 index 0000000..d16704e --- /dev/null +++ b/meltdown-mitigation/README.md @@ -0,0 +1,172 @@ +# Meltdown Mitigation + +Welcome to Meltdown Mitigation on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +In Python, [`if`][if statement], `elif` (_a contraction of 'else and if'_) and `else` statements are used to [control the flow][control flow tools] of execution and make decisions in a program. +Unlike many other programming languages, Python versions 3.9 and below do not offer a formal case-switch statement, instead using multiple `elif` statements to serve a similar purpose. + +Python 3.10 introduces a variant case-switch statement called `structural pattern matching`, which will be covered separately in another concept. + +Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` type directly, or by evaluating as ["truthy" or "falsy"][truth value testing]. + +```python +x = 10 +y = 5 + +# The comparison '>' returns the bool 'True', +# so the statement is printed. +if x > y: + print("x is greater than y") +... +>>> x is greater than y +``` + +When paired with `if`, an optional `else` code block will execute when the original `if` condition evaluates to `False`: + +```python +x = 5 +y = 10 + +# The comparison '>' here returns the bool 'False', +# so the 'else' block is executed instead of the 'if' block. +if x > y: + print("x is greater than y") +else: + print("y is greater than x") +... +>>> y is greater than x +``` + +`elif` allows for multiple evaluations/branches. + +```python +x = 5 +y = 10 +z = 20 + +# The 'elif' statement allows for the checking of more conditions. +if x > y: + print("x is greater than y and z") +elif y > z: + print("y is greater than x and z") +else: + print("z is greater than x and y") +... +>>> z is greater than x and y +``` + +[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: + +```python +>>> def classic_fizzbuzz(number): + if number % 3 == 0 and number % 5 == 0: + say = 'FizzBuzz!' + elif number % 5 == 0: + say = 'Buzz!' + elif number % 3 == 0: + say = 'Fizz!' + else: + say = str(number) + + return say + +>>> classic_fizzbuzz(15) +'FizzBuzz!' + +>>> classic_fizzbuzz(13) +'13' +``` + +[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not +[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons +[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools +[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing + +## Instructions + +In this exercise, we'll develop a simple control system for a nuclear reactor. + +For a reactor to produce the power it must be in a state of _criticality_. +If the reactor is in a state less than criticality, it can become damaged. +If the reactor state goes beyond criticality, it can overload and result in a meltdown. +We want to mitigate the chances of meltdown and correctly manage reactor state. + +The following three tasks are all related to writing code for maintaining ideal reactor state. + +## 1. Check for criticality + +The first thing a control system has to do is check if the reactor is balanced in criticality. +A reactor is said to be critical if it satisfies the following conditions: + +- The temperature is less than 800 K. +- The number of neutrons emitted per second is greater than 500. +- The product of temperature and neutrons emitted per second is less than 500000. + +Implement the function `is_criticality_balanced()` that takes `temperature` measured in kelvin and `neutrons_emitted` as parameters, and returns `True` if the criticality conditions are met, `False` if not. + +```python +>>> is_criticality_balanced(750, 600) +True +``` + +## 2. Determine the Power output range + +Once the reactor has started producing power its efficiency needs to be determined. +Efficiency can be grouped into 4 bands: + +1. `green` -> efficiency of 80% or more, +2. `orange` -> efficiency of less than 80% but at least 60%, +3. `red` -> efficiency below 60%, but still 30% or more, +4. `black` -> less than 30% efficient. + +The percentage value can be calculated as `(generated_power/theoretical_max_power)*100` +where `generated_power` = `voltage` * `current`. +Note that the percentage value is usually not an integer number, so make sure to consider the +proper use of the `<` and `<=` comparisons. + +Implement the function `reactor_efficiency(, , )`, with three parameters: `voltage`, +`current`, and `theoretical_max_power`. +This function should return the efficiency band of the reactor : 'green', 'orange', 'red', or 'black'. + +```python +>>> reactor_efficiency(200,50,15000) +'orange' +``` + +## 3. Fail Safe Mechanism + +Your final task involves creating a fail-safe mechanism to avoid overload and meltdown. +This mechanism will determine if the reactor is below, at, or above the ideal criticality threshold. +Criticality can then be increased, decreased, or stopped by inserting (or removing) control rods into the reactor. + +Implement the function called `fail_safe()`, which takes 3 parameters: `temperature` measured in kelvin, +`neutrons_produced_per_second`, and `threshold`, and outputs a status code for the reactor. + +- If `temperature * neutrons_produced_per_second` < 90% of `threshold`, output a status code of 'LOW' + indicating that control rods must be removed to produce power. + +- If the value `temperature * neutrons_produced_per_second` is within 10% of the `threshold` (so either 0-10% less than the threshold, at the threshold, or 0-10% greater than the threshold), the reactor is in _criticality_ and the status code of 'NORMAL' should be output, indicating that the reactor is in optimum condition and control rods are in an ideal position. + +- If `temperature * neutrons_produced_per_second` is not in the above-stated ranges, the reactor is + going into meltdown and a status code of 'DANGER' must be passed to immediately shut down the reactor. + +```python +>>> fail_safe(temperature=1000, neutrons_produced_per_second=30, threshold=5000) +'DANGER' +``` + +## Source + +### Created by + +- @sachsom95 +- @BethanyG + +### Contributed to by + +- @kbuc \ No newline at end of file diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py new file mode 100644 index 0000000..87b831b --- /dev/null +++ b/meltdown-mitigation/conditionals.py @@ -0,0 +1,71 @@ +"""Functions to prevent a nuclear meltdown.""" + + +def is_criticality_balanced(temperature, neutrons_emitted) -> bool: + """Verify criticality is balanced. + + :param temperature: int or float - temperature value in kelvin. + :param neutrons_emitted: int or float - number of neutrons emitted per second. + :return: bool - is criticality balanced? + + A reactor is said to be critical if it satisfies the following conditions: + - The temperature is less than 800 K. + - The number of neutrons emitted per second is greater than 500. + - The product of temperature and neutrons emitted per second is less than 500000. + """ + return temperature < 800 and neutrons_emitted > 500 and (temperature * neutrons_emitted) < 500000 + + +def reactor_efficiency(voltage, current, theoretical_max_power) -> str: + """Assess reactor efficiency zone. + + :param voltage: int or float - voltage value. + :param current: int or float - current value. + :param theoretical_max_power: int or float - power that corresponds to a 100% efficiency. + :return: str - one of ('green', 'orange', 'red', or 'black'). + + Efficiency can be grouped into 4 bands: + + 1. green -> efficiency of 80% or more, + 2. orange -> efficiency of less than 80% but at least 60%, + 3. red -> efficiency below 60%, but still 30% or more, + 4. black -> less than 30% efficient. + + The percentage value is calculated as + (generated power/ theoretical max power)*100 + where generated power = voltage * current + """ + generated_power = voltage * current + efficiency = (generated_power/theoretical_max_power)*100 + + if efficiency < 30: + return 'black' + elif 30 <= efficiency < 60: + return 'red' + elif 60 <= efficiency < 80: + return 'orange' + + return 'green' + + +def fail_safe(temperature, neutrons_produced_per_second, threshold) -> str: + """Assess and return status code for the reactor. + + :param temperature: int or float - value of the temperature in kelvin. + :param neutrons_produced_per_second: int or float - neutron flux. + :param threshold: int or float - threshold for category. + :return: str - one of ('LOW', 'NORMAL', 'DANGER'). + + 1. 'LOW' -> `temperature * neutrons per second` < 90% of `threshold` + 2. 'NORMAL' -> `temperature * neutrons per second` +/- 10% of `threshold` + 3. 'DANGER' -> `temperature * neutrons per second` is not in the above-stated ranges + """ + current_state = (temperature * neutrons_produced_per_second) / 100 + thr_percent = threshold / 100 + + if thr_percent - 10 <= current_state <= thr_percent + 10: + return 'NORMAL' + elif current_state < thr_percent - 10: + return 'LOW' + + return 'DANGER' diff --git a/meltdown-mitigation/conditionals_test.py b/meltdown-mitigation/conditionals_test.py new file mode 100644 index 0000000..5e48ca3 --- /dev/null +++ b/meltdown-mitigation/conditionals_test.py @@ -0,0 +1,82 @@ +import unittest +import pytest +from conditionals import (is_criticality_balanced, + reactor_efficiency, + fail_safe) + + +class MeltdownMitigationTest(unittest.TestCase): + """Test cases for Meltdown mitigation exercise. + """ + + @pytest.mark.task(taskno=1) + def test_is_criticality_balanced(self): + """Testing border cases around typical points. + + T, n == (800, 500), (625, 800), (500, 1000), etc. + + """ + + test_data = ((750, 650, True), (799, 501, True), (500, 600, True), + (1000, 800, False), (800, 500, False), (800, 500.01, False), + (799.99, 500, False), (500.01, 999.99, False), (625, 800, False), + (625.99, 800, False), (625.01, 799.99, False), (799.99, 500.01, True), + (624.99, 799.99, True), (500, 1000, False), (500.01, 1000, False), + (499.99, 1000, True)) + + for variant, data in enumerate(test_data, start=1): + temp, neutrons_emitted, expected = data + with self.subTest(f'variation #{variant}', temp=temp, neutrons_emitted=neutrons_emitted, expected=expected): + + # pylint: disable=assignment-from-no-return + actual_result = is_criticality_balanced(temp, neutrons_emitted) + failure_message = (f'Called is_criticality_balanced({temp}, {neutrons_emitted}). ' + f' The function returned {actual_result}, ' + f'but the test expected {expected} as the return value.') + + self.assertEqual(actual_result, expected, failure_message) + + @pytest.mark.task(taskno=2) + def test_reactor_efficiency(self): + voltage = 10 + theoretical_max_power = 10000 + + # The numbers are chosen so that current == 10 x percentage + test_data = ((1000, 'green'), (999, 'green'), (800, 'green'), + (799, 'orange'), (700, 'orange'), (600, 'orange'), + (599, 'red'), (560, 'red'), (400, 'red'), (300, 'red'), + (299, 'black'), (200, 'black'), (0, 'black')) + + for variant, data in enumerate(test_data, start=1): + current, expected = data + with self.subTest(f'variation #{variant}', voltage=voltage, current=current, + theoretical_max_power=theoretical_max_power, expected=expected): + + # pylint: disable=assignment-from-no-return + actual_result = reactor_efficiency(voltage, current, theoretical_max_power) + failure_message =(f'Called reactor_efficiency({voltage}, {current}, {theoretical_max_power}). ' + f'The function returned {actual_result}, ' + f'but the test expected {expected} as the return value.') + + self.assertEqual(actual_result, expected, failure_message) + + @pytest.mark.task(taskno=3) + def test_fail_safe(self): + temp = 10 + threshold = 10000 + test_data = ((399, 'LOW'), (300, 'LOW'), (1, 'LOW'), + (0, 'LOW'), (901, 'NORMAL'), (1000, 'NORMAL'), + (1099, 'NORMAL'), (899, 'LOW'), (700, 'LOW'), + (400, 'LOW'), (1101, 'DANGER'), (1200, 'DANGER')) + + for variant, (neutrons_per_second, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', temp=temp, neutrons_per_second=neutrons_per_second, + threshold=threshold, expected=expected): + + # pylint: disable=assignment-from-no-return + actual_result = fail_safe(temp, neutrons_per_second, threshold) + failure_message = (f'Called fail_safe({temp}, {neutrons_per_second}, {threshold}). ' + f'The function returned {actual_result}, ' + f'but the test expected {expected} as the return value.') + + self.assertEqual(actual_result, expected, failure_message) From df0f9bc483dc1da993bdc1bb9b58679560d52fea Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Wed, 6 Aug 2025 23:00:15 -0700 Subject: [PATCH 20/75] Black Jack --- black-jack/.exercism/config.json | 27 ++ black-jack/.exercism/metadata.json | 1 + black-jack/HELP.md | 130 ++++++++++ black-jack/HINTS.md | 45 ++++ black-jack/README.md | 395 +++++++++++++++++++++++++++++ black-jack/black_jack.py | 110 ++++++++ black-jack/black_jack_test.py | 114 +++++++++ 7 files changed, 822 insertions(+) create mode 100644 black-jack/.exercism/config.json create mode 100644 black-jack/.exercism/metadata.json create mode 100644 black-jack/HELP.md create mode 100644 black-jack/HINTS.md create mode 100644 black-jack/README.md create mode 100644 black-jack/black_jack.py create mode 100644 black-jack/black_jack_test.py diff --git a/black-jack/.exercism/config.json b/black-jack/.exercism/config.json new file mode 100644 index 0000000..a23fa9e --- /dev/null +++ b/black-jack/.exercism/config.json @@ -0,0 +1,27 @@ +{ + "authors": [ + "Ticktakto", + "Yabby1997", + "limm-jk", + "OMEGA-Y", + "wnstj2007", + "pranasziaukas", + "bethanyG" + ], + "contributors": [ + "PaulT89" + ], + "files": { + "solution": [ + "black_jack.py" + ], + "test": [ + "black_jack_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "poker", + "blurb": "Learn about comparisons by implementing some Black Jack judging rules." +} diff --git a/black-jack/.exercism/metadata.json b/black-jack/.exercism/metadata.json new file mode 100644 index 0000000..d3ce77b --- /dev/null +++ b/black-jack/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"black-jack","id":"f566c75c943c4573898840a393c43fca","url":"https://exercism.org/tracks/python/exercises/black-jack","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/black-jack/HELP.md b/black-jack/HELP.md new file mode 100644 index 0000000..970b1fa --- /dev/null +++ b/black-jack/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit black_jack.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/black-jack/HINTS.md b/black-jack/HINTS.md new file mode 100644 index 0000000..aaa0273 --- /dev/null +++ b/black-jack/HINTS.md @@ -0,0 +1,45 @@ +# Hints + +[The Python comparisons tutorial][python comparisons tutorial] and [Python comparisons examples][python comparisons examples] are a great introduction covering the content of this exercise. + +## 1. Calculate the value of a card + +- You can use the equality comparison operator `==` to determine if a card is an ace card: `card == 'A'`. +- You can use the containment operator `in` to determine if a substring is contained inside a string: `'Q' in 'KJQ'`. +- You can use the [`int` constructor][int constructor] to convert a `str` of an `int` to an `int`: `int('13')`. + +## 2. Determine which card has a higher value + +- Once you have defined the `value_of_card` function, you can call it from other functions. +- You can use the value comparison operators `>` and `<` to determine if specific cards are _greater than_ or _less than_ a given value: `3 < 12`. +- You can use the equality comparison operator `==` to determine if two values are equal to one another. + +## 3. Calculate the value of an ace + +- Once you have defined the `value_of_card` function, you can call it from other functions. +- You can use the order comparison operator `>` to decide the appropriate course of action here. + +## 4. Determine Blackjack + +- Remember, you can use the [`if`/`elif`/`else` syntax][if syntax] to handle different combinations of cards. +- You can chain BOTH comparison operators and boolean operators _arbitrarily_: `y < z < x` or `(y or z) and (x or z)` +- You can reuse the already implemented `value_of_card` function. + +## 5. Splitting pairs + +- You can reuse the already implemented `value_of_card` function. +- You can handle the `A` case (when at least one of the cards in an ace) separately. + +## 6. Doubling down + +- An `A` scored at 11 will never allow doubling down if there are two cards in the hand. +- Given the first point, you _should_ be able to reuse the already implemented `value_of_card` function. +- You can chain comparison operators _arbitrarily_: `y < z < x`. +- You can use the [conditional expression][conditional expression] (_sometimes called a "ternary operator"_) + to shorten simple `if`/`else` statements: `13 if letter == 'M' else 3`. + +[conditional expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions +[if syntax]: https://docs.python.org/3/tutorial/controlflow.html#if-statements +[int constructor]: https://docs.python.org/3/library/functions.html#int +[python comparisons examples]: https://www.tutorialspoint.com/python/comparison_operators_example.htm +[python comparisons tutorial]: https://docs.python.org/3/reference/expressions.html#comparisons \ No newline at end of file diff --git a/black-jack/README.md b/black-jack/README.md new file mode 100644 index 0000000..bd8d281 --- /dev/null +++ b/black-jack/README.md @@ -0,0 +1,395 @@ +# Black Jack + +Welcome to Black Jack on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +## Comparisons + +Python supports the following basic comparison operators: + +| Operator | Operation | Description | +| -------- | -------------------------- | ------------------------------------------------------------------------- | +| `>` | "greater than" | `a > b` is `True` if `a` is **strictly** greater in value than `b` | +| `<` | "less than" | `a < b` is `True` if `a` is **strictly** less in value than `b` | +| `==` | "equal to" | `a == b` is `True` if `a` is **strictly** equal to `b` in value | +| `>=` | "greater than or equal to" | `a >= b` is `True` if `a > b` OR `a == b` in value | +| `<=` | "less than or equal to" | `a <= b` is `True` if `a < b` or `a == b` in value | +| `!=` | "not equal to" | `a != b` is `True` if `a == b` is `False` | +| `is` | "identity" | `a is b` is `True` if **_and only if_** `a` and `b` are the same _object_ | +| `is not` | "negated identity" | `a is not b` is `True` if `a` and `b` are **not** the same _object_ | +| `in` | "containment test" | `a in b` is `True` if `a` is member, subset, or element of `b` | +| `not in` | "negated containment test" | `a not in b` is `True` if `a` is not a member, subset, or element of `b` | + +They all have the same priority (_which is higher than that of [Boolean operations][boolean operations], but lower than that of arithmetic or bitwise operations_). + +## Comparison between different data types + +Objects that are different types (_except numeric types_) never compare equal by default. +Non-identical instances of a `class` will also _**not**_ compare as equal unless the `class` defines special [rich comparison][rich comparisons] methods that customize the default `object` comparison behavior. +Customizing via `rich comparisons` will be covered in a follow-on exercise. +For (much) more detail on this topic, see [Value comparisons][value comparisons] in the Python documentation. + +Numeric types are (mostly) an exception to this type matching rule. +An `integer` **can** be considered equal to a `float` (_or an [`octal`][octal] equal to a [`hexadecimal`][hex]_), as long as the types can be implicitly converted for comparison. + +For the other numeric types in the Python standard library ([complex][complex numbers], [decimal][decimal numbers], [fractions][rational numbers]), comparison operators are defined where they "make sense" (_where implicit conversion does not change the outcome_), but throw a `TypeError` if the underlying objects cannot be accurately converted for comparison. +For more information on the rules that python uses for _numeric conversion_, see [arithmetic conversions][arithmetic conversions] in the Python documentation. + +```python +>>> import fractions + +# A string cannot be converted to an int. +>>> 17 == '17' +False + +# An int can be converted to float for comparison. +>>> 17 == 17.0 +True + +# The fraction 6/3 can be converted to the int 2 +# The int 2 can be converted to 0b10 in binary. +>>> 6/3 == 0b10 +True + +# An int can be converted to a complex number with a 0 imaginary part. +>>> 17 == complex(17) +True + +# The fraction 2/5 can be converted to the float 0.4 +>>> 0.4 == 2/5 +True + +>>> complex(2/5, 1/2) == complex(0.4, 0.5) +True +``` + +Any ordered comparison of a number to a `NaN` (_not a number_) type is `False`. +A confusing side effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`. + +```python +>>> x = float('NaN') + +>>> 3 < x +False + +>>> x < 3 +False + +# NaN never compares equal to NaN +>>> x == x +False +``` + +## Comparing Strings + +Unlike numbers, strings (`str`) are compared [_lexicographically_][lexographic order], using their individual Unicode code points (_the result of passing each code point in the `str` to the built-in function [`ord()`][ord], which returns an `int`_). +If all code points in both strings match and are _**in the same order**_, the two strings are considered equal. +This comparison is done in a 'pair-wise' fashion - first-to-first, second-to-second, etc. +In Python 3.x, `str` and `bytes` cannot be directly coerced/compared. + +```python +>>> 'Python' > 'Rust' +False + +>>> 'Python' > 'JavaScript' +True + +# Examples with Mandarin. +# hello < goodbye +>>> '你好' < '再见' +True + +# ord() of first characters +>>> ord('你'), ord('再') +(20320, 20877) + +# ord() of second characters +>>> ord('好'), ord('见') +(22909, 35265) + +# And with Korean words. +# Pretty < beautiful. +>>> '예쁜' < '아름다운' +False + +>>> ord('예'), ord('아') +(50696, 50500) +``` + +## Comparison Chaining + +Comparison operators can be chained _arbitrarily_ -- meaning that they can be used in any combination of any length. +Note that the evaluation of an expression takes place from `left` to `right`. + +As an example, `x < y <= z` is equivalent to `x < y` `and` `y <= z`, except that `y` is evaluated **only once**. +In both cases, `z` is _not_ evaluated **at all** when `x < y` is found to be `False`. +This is often called `short-circuit evaluation` - the evaluation stops if the truth value of the expression has already been determined. + +`Short circuiting` is supported by various boolean operators, functions, and also by comparison chaining in Python. +Unlike many other programming languages, including `C`, `C++`, `C#`, and `Java`, chained expressions like `a < b < c` in Python have a conventional [mathematical interpretation][three way boolean comparison] and precedence. + +```python +>>> x = 2 +>>> y = 5 +>>> z = 10 + +>>> x < y < z +True + +>>> x < y > z +False + +>>> x > y < z +False +``` + +## Comparing object identity + +The operators `is` and `is not` test for object [_identity_][object identity], as opposed to object _value_. +An object's identity never changes after creation and can be found by using the [`id()`][id function] function. + +` is ` evaluates to `True` if _**and only if**_ `id()` == `id()`. +` is not ` yields the inverse. + +Due to their singleton status, `None` and `NotImplemented` should always be compared to items using `is` and `is not`. +See the Python reference docs on [value comparisons][value comparisons none] and [PEP8][pep8 programming recommendations] for more details on this convention. + +```python +>>> my_fav_numbers = [1, 2, 3] + +>>> your_fav_numbers = my_fav_numbers + +>>> my_fav_numbers is your_fav_numbers +True + +# The returned id will differ by system and python version. +>>> id(my_fav_numbers) +4517478208 + +# your_fav_numbers is only an alias pointing to the original my_fav_numbers object. +# Assigning a new name does not create a new object. +>>> id(your_fav_numbers) +4517478208 + + +>>> my_fav_numbers is not your_fav_numbers +False + +>>> my_fav_numbers is not None +True + +>>> my_fav_numbers is NotImplemented +False +``` + +## Membership comparisons + +The operators `in` and `not in` test for _membership_. +` in ` evaluates to `True` if `` is a member of `` (_if `` is a subset of or is contained within ``_), and evaluates `False` otherwise. +` not in ` returns the negation, or _opposite of_ ` in `. + +For string and bytes types, ` in ` is `True` _**if and only if**_ `` is a substring of ``. + +```python +# A set of lucky numbers. +>>> lucky_numbers = {11, 22, 33} +>>> 22 in lucky_numbers +True + +>>> 44 in lucky_numbers +False + +# A dictionary of employee information. +>>> employee = {'name': 'John Doe', + 'id': 67826, 'age': 33, + 'title': 'ceo'} + +# Checking for the membership of certain keys. +>>> 'age' in employee +True + +>>> 33 in employee +False + +>>> 'lastname' not in employee +True + +# Checking for substring membership +>>> name = 'Super Batman' +>>> 'Bat' in name +True + +>>> 'Batwoman' in name +False +``` + +[arithmetic conversions]: https://docs.python.org/3/reference/expressions.html?highlight=number%20conversion#arithmetic-conversions +[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not +[complex numbers]: https://docs.python.org/3/library/functions.html#complex +[decimal numbers]: https://docs.python.org/3/library/decimal.html +[hex]: https://docs.python.org/3/library/functions.html?highlight=hex#hex +[id function]: https://docs.python.org/3/library/functions.html#id +[lexographic order]: https://en.wikipedia.org/wiki/Lexicographic_order +[object identity]: https://docs.python.org/3/reference/datamodel.html +[octal]: https://docs.python.org/3/library/functions.html?#oct +[ord]: https://docs.python.org/3/library/functions.html#ord +[pep8 programming recommendations]: https://pep8.org/#programming-recommendations +[rational numbers]: https://docs.python.org/3/library/fractions.html +[rich comparisons]: https://docs.python.org/3/reference/datamodel.html#object.__lt__ +[three way boolean comparison]: https://en.wikipedia.org/wiki/Three-way_comparison +[value comparisons none]: https://docs.python.org/3/reference/expressions.html?highlight=none#value-comparisons +[value comparisons]: https://docs.python.org/3/reference/expressions.html?highlight=nan#value-comparisons + +## Instructions + +In this exercise you are going to implement some rules of [Blackjack][blackjack], +such as the way the game is played and scored. + +**Note** : In this exercise, _`A`_ means ace, _`J`_ means jack, _`Q`_ means queen, and _`K`_ means king. +Jokers are discarded. +A [standard French-suited 52-card deck][standard_deck] is assumed, but in most versions, several decks are shuffled together for play. + +## 1. Calculate the value of a card + +In Blackjack, it is up to each individual player if an ace is worth 1 or 11 points (_more on that later_). +Face cards (`J`, `Q`, `K`) are scored at 10 points and any other card is worth its "pip" (_numerical_) value. + +Define the `value_of_card()` function with parameter `card`. +The function should return the _numerical value_ of the passed-in card string. +Since an ace can take on multiple values (1 **or** 11), this function should fix the value of an ace card at 1 for the time being. +Later on, you will implement a function to determine the value of an ace card, given an existing hand. + +```python +>>> value_of_card('K') +10 + +>>> value_of_card('4') +4 + +>>> value_of_card('A') +1 +``` + +## 2. Determine which card has a higher value + +Define the `higher_card(, )` function having parameters `card_one` and `card_two`. +For scoring purposes, the value of `J`, `Q` or `K` is 10. +The function should return which card has the higher value for scoring. +If both cards have an equal value, return both. +Returning both cards can be done by using a comma in the `return` statement: + +```python +# Using a comma in a return creates a Tuple. Tuples will be covered in a later exercise. +>>> def returning_two_values(value_one, value_two): + return value_one, value_two + +>>> returning_two_values('K', '3') +('K', '3') +``` + +An ace can take on multiple values, so we will fix `A` cards to a value of 1 for this task. + +```python +>>> higher_card('K', '10') +('K', '10') + +>>> higher_card('4', '6') +'6' + +>>> higher_card('K', 'A') +'K' +``` + +## 3. Calculate the value of an ace + +As mentioned before, an ace can be worth _either_ 1 **or** 11 points. +Players try to get as close as possible to a score of 21, without going _over_ 21 (_going "bust"_). + +Define the `value_of_ace(, )` function with parameters `card_one` and `card_two`, which are a pair of cards already in the hand _before_ getting an ace card. +Your function will have to decide if the upcoming ace will get a value of 1 or a value of 11, and return that value. +Remember: the value of the hand with the ace needs to be as high as possible _without_ going over 21. + +**Hint**: if we already have an ace in hand, then the value for the upcoming ace would be 1. + +```python +>>> value_of_ace('6', 'K') +1 + +>>> value_of_ace('7', '3') +11 +``` + +## 4. Determine a "Natural" or "Blackjack" Hand + +If a player is dealt an ace (`A`) and a ten-card (10, `K`, `Q`, or `J`) as their first two cards, then the player has a score of 21. +This is known as a **blackjack** hand. + + +Define the `is_blackjack(, )` function with parameters `card_one` and `card_two`, which are a pair of cards. +Determine if the two-card hand is a **blackjack**, and return the boolean `True` if it is, `False` otherwise. + +**Note** : The score _calculation_ can be done in many ways. +But if possible, we'd like you to check if there is an ace and a ten-card **_in_** the hand (_or at a certain position_), as opposed to _summing_ the hand values. + +```python +>>> is_blackjack('A', 'K') +True + +>>> is_blackjack('10', '9') +False +``` + +## 5. Splitting pairs + +If the players first two cards are of the same value, such as two sixes, or a `Q` and `K` a player may choose to treat them as two separate hands. +This is known as "splitting pairs". + +Define the `can_split_pairs(, )` function with parameters `card_one` and `card_two`, which are a pair of cards. +Determine if this two-card hand can be split into two pairs. +If the hand can be split, return the boolean `True` otherwise, return `False` + +```python +>>> can_split_pairs('Q', 'K') +True + +>>> can_split_pairs('10', 'A') +False +``` + +## 6. Doubling down + +When the original two cards dealt total 9, 10, or 11 points, a player can place an additional bet equal to their original bet. +This is known as "doubling down". + +Define the `can_double_down(, )` function with parameters `card_one` and `card_two`, which are a pair of cards. +Determine if the two-card hand can be "doubled down", and return the boolean `True` if it can, `False` otherwise. + +```python +>>> can_double_down('A', '9') +True + +>>> can_double_down('10', '2') +False +``` + +[blackjack]: https://bicyclecards.com/how-to-play/blackjack/ +[standard_deck]: https://en.wikipedia.org/wiki/Standard_52-card_deck + +## Source + +### Created by + +- @Ticktakto +- @Yabby1997 +- @limm-jk +- @OMEGA-Y +- @wnstj2007 +- @pranasziaukas +- @bethanyG + +### Contributed to by + +- @PaulT89 \ No newline at end of file diff --git a/black-jack/black_jack.py b/black-jack/black_jack.py new file mode 100644 index 0000000..c252b88 --- /dev/null +++ b/black-jack/black_jack.py @@ -0,0 +1,110 @@ +""" +Functions to help play and score a game of blackjack. + +How to play blackjack: https://bicyclecards.com/how-to-play/blackjack/ +"Standard" playing cards: https://en.wikipedia.org/wiki/Standard_52-card_deck +""" + + +def value_of_card(card) -> int: + """ + Determine the scoring value of a card. + + :param card: str - given card. + :return: int - value of a given card. See below for values. + + 1. 'J', 'Q', or 'K' (otherwise known as "face cards") = 10 + 2. 'A' (ace card) = 1 + 3. '2' - '10' = numerical value. + """ + if card in 'JKQ': + return 10 + elif card == 'A': + return 1 + + return int(card) + + +def higher_card(card_one, card_two) -> str | tuple[str, str]: + """ + Determine which card has a higher value in the hand. + + :param card_one, card_two: str - cards dealt in hand. See below for values. + :return: str or tuple - resulting Tuple contains both cards if they are of equal value. + + 1. 'J', 'Q', or 'K' (otherwise known as "face cards") = 10 + 2. 'A' (ace card) = 1 + 3. '2' - '10' = numerical value. + """ + if value_of_card(card_one) == value_of_card(card_two): + return card_one, card_two + elif value_of_card(card_one) > value_of_card(card_two): + return card_one + + return card_two + + +def value_of_ace(card_one, card_two) -> int: + """ + Calculate the most advantageous value for the ace card. + + :param card_one, card_two: str - card dealt. See below for values. + :return: int - either 1 or 11 value of the upcoming ace card. + + 1. 'J', 'Q', or 'K' (otherwise known as "face cards") = 10 + 2. 'A' (ace card) = 11 (if already in hand) + 3. '2' - '10' = numerical value. + """ + total: int = value_of_card(card_one) + value_of_card(card_two) + # Hint: if we already have an ace in hand, then the value for the upcoming ace would be 1. + if card_one == 'A' or card_two == 'A': + return 1 + # The value of the hand with the ace needs to be as high as possible without going over 21. + elif 21 - total >= 11: + return 11 + + return 1 + + +def is_blackjack(card_one, card_two) -> bool: + """ + Determine if the hand is a 'natural' or 'blackjack'. + + :param card_one, card_two: str - card dealt. See below for values. + :return: bool - is the hand is a blackjack (two cards worth 21). + + 1. 'J', 'Q', or 'K' (otherwise known as "face cards") = 10 + 2. 'A' (ace card) = 11 (if already in hand) + 3. '2' - '10' = numerical value. + """ + # If a player is dealt an ace (A) and a ten-card (10, K, Q, or J) + # as their first two cards, then the player has a score of 21. + if card_one == 'A' and card_two in ('J', 'Q', 'K', '10'): + return True + elif card_two == 'A' and card_one in ('J', 'Q', 'K', '10'): + return True + + return False + + +def can_split_pairs(card_one, card_two) -> bool: + """ + Determine if a player can split their hand into two hands. + + :param card_one, card_two: str - cards dealt. + :return: bool - can the hand be split into two pairs? (i.e. cards are of the same value). + """ + if value_of_card(card_one) == value_of_card(card_two): + return True + + return False + + +def can_double_down(card_one, card_two) -> bool: + """ + Determine if a blackjack player can place a double down bet. + + :param card_one, card_two: str - first and second cards in hand. + :return: bool - can the hand can be doubled down? (i.e. totals 9, 10 or 11 points). + """ + return 9 <= value_of_card(card_one) + value_of_card(card_two) <= 11 diff --git a/black-jack/black_jack_test.py b/black-jack/black_jack_test.py new file mode 100644 index 0000000..0962781 --- /dev/null +++ b/black-jack/black_jack_test.py @@ -0,0 +1,114 @@ +import unittest +import pytest + +from black_jack import ( + value_of_card, + higher_card, + value_of_ace, + is_blackjack, + can_split_pairs, + can_double_down + ) + + +class BlackJackTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_value_of_card(self): + test_data = [('2', 2), ('5', 5), ('8', 8), + ('A', 1), ('10', 10), ('J', 10), + ('Q', 10), ('K', 10)] + + for variant, (card, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', card=card, expected=expected): + actual_result = value_of_card(card) + error_msg = (f'Called value_of_card({card}). ' + f'The function returned {actual_result} as the value of the {card} card, ' + f'but the test expected {expected} as the {card} card value.') + + self.assertEqual(actual_result, expected, msg=error_msg) + + + @pytest.mark.task(taskno=2) + def test_higher_card(self): + test_data = [('A', 'A', ('A', 'A')), + ('10', 'J', ('10', 'J')), + ('3', 'A', '3'), + ('3', '6', '6'), + ('Q', '10', ('Q', '10')), + ('4', '4', ('4', '4')), + ('9', '10', '10'), + ('6', '9', '9'), + ('4', '8', '8')] + + for variant, (card_one, card_two, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, expected=expected): + actual_result = higher_card(card_one, card_two) + error_msg = (f'Called higher_card({card_one}, {card_two}). ' + f'The function returned {actual_result}, ' + f'but the test expected {expected} as the result for the cards {card_one, card_two}.') + + self.assertEqual(actual_result, expected, msg=error_msg) + + @pytest.mark.task(taskno=3) + def test_value_of_ace(self): + test_data = [('2', '3', 11), ('3', '6', 11), ('5', '2', 11), + ('8', '2', 11), ('5', '5', 11), ('Q', 'A', 1), + ('10', '2', 1), ('7', '8', 1), ('J', '9', 1), + ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1)] + + for variant, (card_one, card_two, ace_value) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, ace_value=ace_value): + actual_result = value_of_ace(card_one, card_two) + error_msg = (f'Called value_of_ace({card_one}, {card_two}). ' + f'The function returned {actual_result}, ' + f'but the test expected {ace_value} as the value of an ace card ' + f'when the hand includes {card_one, card_two}.') + + self.assertEqual(value_of_ace(card_one, card_two), ace_value, msg=error_msg) + + @pytest.mark.task(taskno=4) + def test_is_blackjack(self): + test_data = [(('A', 'K'), True), (('10', 'A'), True), + (('10', '9'), False), (('A', 'A'), False), + (('4', '7'), False), (('9', '2'), False), + (('Q', 'K'), False)] + + for variant, (hand, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = is_blackjack(*hand) + error_msg = (f'Called is_blackjack({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"is" if expected else "is not"} a blackjack.') + + self.assertEqual(actual_result, expected, msg=error_msg) + + @pytest.mark.task(taskno=5) + def test_can_split_pairs(self): + test_data = [(('Q', 'K'), True), (('6', '6'), True), + (('A', 'A'), True),(('10', 'A'), False), + (('10', '9'), False)] + + for variant, (hand, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', input=hand, expected=expected): + actual_result = can_split_pairs(*hand) + error_msg = (f'Called can_split_pairs({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"can" if expected else "cannot"} be split into pairs.') + + self.assertEqual(actual_result, expected, msg=error_msg) + + @pytest.mark.task(taskno=6) + def test_can_double_down(self): + test_data = [(('A', '9'), True), (('K', 'A'), True), + (('4', '5'), True),(('A', 'A'), False), + (('10', '2'), False), (('10', '9'), False)] + + for variant, (hand, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = can_double_down(*hand) + error_msg = (f'Called can_double_down({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"can" if expected else "cannot"} be doubled down.') + + self.assertEqual(actual_result, expected, msg=error_msg) From eacb4b4a54a5a33b2527b5baeabf7f6b3313efef Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Wed, 6 Aug 2025 23:34:13 -0700 Subject: [PATCH 21/75] Little Sister's Vocabulary --- little-sisters-vocab/.exercism/config.json | 19 ++ little-sisters-vocab/.exercism/metadata.json | 1 + little-sisters-vocab/HELP.md | 130 +++++++ little-sisters-vocab/HINTS.md | 37 ++ little-sisters-vocab/README.md | 340 +++++++++++++++++++ little-sisters-vocab/strings.py | 57 ++++ little-sisters-vocab/strings_test.py | 126 +++++++ 7 files changed, 710 insertions(+) create mode 100644 little-sisters-vocab/.exercism/config.json create mode 100644 little-sisters-vocab/.exercism/metadata.json create mode 100644 little-sisters-vocab/HELP.md create mode 100644 little-sisters-vocab/HINTS.md create mode 100644 little-sisters-vocab/README.md create mode 100644 little-sisters-vocab/strings.py create mode 100644 little-sisters-vocab/strings_test.py diff --git a/little-sisters-vocab/.exercism/config.json b/little-sisters-vocab/.exercism/config.json new file mode 100644 index 0000000..2e1cd93 --- /dev/null +++ b/little-sisters-vocab/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "aldraco", + "BethanyG" + ], + "files": { + "solution": [ + "strings.py" + ], + "test": [ + "strings_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "two-fer", + "blurb": "Learn about strings by helping your little sister with her vocabulary homework." +} diff --git a/little-sisters-vocab/.exercism/metadata.json b/little-sisters-vocab/.exercism/metadata.json new file mode 100644 index 0000000..41ae959 --- /dev/null +++ b/little-sisters-vocab/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"little-sisters-vocab","id":"5d58d945fc384c4fbeb6508378f84cec","url":"https://exercism.org/tracks/python/exercises/little-sisters-vocab","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/little-sisters-vocab/HELP.md b/little-sisters-vocab/HELP.md new file mode 100644 index 0000000..67c4eaa --- /dev/null +++ b/little-sisters-vocab/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit strings.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/little-sisters-vocab/HINTS.md b/little-sisters-vocab/HINTS.md new file mode 100644 index 0000000..dafc089 --- /dev/null +++ b/little-sisters-vocab/HINTS.md @@ -0,0 +1,37 @@ +# Hints + +## General + +- The Python Docs [Tutorial for strings][python-str-doc] has an overview of the Python `str` type. +- String methods [`str.join()`][str-join] and [`str.split()`][str-split] ar very helpful when processing strings. +- The Python Docs on [Sequence Types][common sequence operations] has a rundown of operations common to all sequences, including `strings`, `lists`, `tuples`, and `ranges`. + +There's four activities in the assignment, each with a set of text or words to work with. + +## 1. Add a prefix to a word + +- Small strings can be concatenated with the `+` operator. + +## 2. Add prefixes to word groups + +- Believe it or not, [`str.join()`][str-join] is all you need here. +- Like [`str.split()`][str-split]`, `str.join()` can take an arbitrary-length string, made up of any unicode code points. + +## 3. Remove a suffix from a word + +- Strings can be indexed or sliced from either the left (starting at 0) or the right (starting at -1). +- If you want the last code point of an arbitrary-length string, you can use [-1]. +- The last three letters in a string can be "sliced off" using a negative index. e.g. 'beautiful'[:-3] == 'beauti' + +## 4. Extract and transform a word + +- Using [`str.split()`][str-split] returns a `list` of strings broken on white space. +- `lists` are sequences, and can be indexed. +- [`str.split()`][str-split] can be directly indexed: `'Exercism rocks!'.split()[0] == 'Exercism'` +- Be careful of punctuation! Periods can be removed via slice: `'dark.'[:-1] == 'dark'` + + +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str +[python-str-doc]: https://docs.python.org/3/tutorial/introduction.html#strings +[str-join]: https://docs.python.org/3/library/stdtypes.html#str.join +[str-split]: https://docs.python.org/3/library/stdtypes.html#str.split \ No newline at end of file diff --git a/little-sisters-vocab/README.md b/little-sisters-vocab/README.md new file mode 100644 index 0000000..1327736 --- /dev/null +++ b/little-sisters-vocab/README.md @@ -0,0 +1,340 @@ +# Little Sister's Vocabulary + +Welcome to Little Sister's Vocabulary on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +A `str` in Python is an [immutable sequence][text sequence] of [Unicode code points][unicode code points]. +These could include letters, diacritical marks, positioning characters, numbers, currency symbols, emoji, punctuation, space and line break characters, and more. + Being immutable, a `str` object's value in memory doesn't change; methods that appear to modify a string return a new copy or instance of that `str` object. + + +A `str` literal can be declared via single `'` or double `"` quotes. The escape `\` character is available as needed. + + +```python + +>>> single_quoted = 'These allow "double quoting" without "escape" characters.' + +>>> double_quoted = "These allow embedded 'single quoting', so you don't have to use an 'escape' character". + +>>> escapes = 'If needed, a \'slash\' can be used as an escape character within a string when switching quote styles won\'t work.' +``` + +Multi-line strings are declared with `'''` or `"""`. + + +```python +>>> triple_quoted = '''Three single quotes or "double quotes" in a row allow for multi-line string literals. + Line break characters, tabs and other whitespace are fully supported. + + You\'ll most often encounter these as "doc strings" or "doc tests" written just below the first line of a function or class definition. + They\'re often used with auto documentation ✍ tools. + ''' +``` + +Strings can be concatenated using the `+` operator. + This method should be used sparingly, as it is not very performant or easily maintained. + + +```python +language = "Ukrainian" +number = "nine" +word = "дев'ять" + +sentence = word + " " + "means" + " " + number + " in " + language + "." + +>>> print(sentence) +... +"дев'ять means nine in Ukrainian." +``` + +If a `list`, `tuple`, `set` or other collection of individual strings needs to be combined into a single `str`, [`.join()`][str-join], is a better option: + + +```python +# str.join() makes a new string from the iterables elements. +>>> chickens = ["hen", "egg", "rooster"] +>>> ' '.join(chickens) +'hen egg rooster' + +# Any string can be used as the joining element. +>>> ' :: '.join(chickens) +'hen :: egg :: rooster' + +>>> ' 🌿 '.join(chickens) +'hen 🌿 egg 🌿 rooster' +``` + +Code points within a `str` can be referenced by `0-based index` number from the left: + + +```python +creative = '창의적인' + +>>> creative[0] +'창' + +>>> creative[2] +'적' + +>>> creative[3] +'인' +``` + +Indexing also works from the right, starting with a `-1-based index`: + + +```python +creative = '창의적인' + +>>> creative[-4] +'창' + +>>> creative[-2] +'적' + +>>> creative[-1] +'인' + +``` + + +There is no separate “character” or "rune" type in Python, so indexing a string produces a new `str` of length 1: + + +```python + +>>> website = "exercism" +>>> type(website[0]) + + +>>> len(website[0]) +1 + +>>> website[0] == website[0:1] == 'e' +True +``` + +Substrings can be selected via _slice notation_, using [`[:stop:]`][common sequence operations] to produce a new string. + Results exclude the `stop` index. + If no `start` is given, the starting index will be 0. + If no `stop` is given, the `stop` index will be the end of the string. + + +```python +moon_and_stars = '🌟🌟🌙🌟🌟⭐' +sun_and_moon = '🌞🌙🌞🌙🌞🌙🌞🌙🌞' + +>>> moon_and_stars[1:4] +'🌟🌙🌟' + +>>> moon_and_stars[:3] +'🌟🌟🌙' + +>>> moon_and_stars[3:] +'🌟🌟⭐' + +>>> moon_and_stars[:-1] +'🌟🌟🌙🌟🌟' + +>>> moon_and_stars[:-3] +'🌟🌟🌙' + +>>> sun_and_moon[::2] +'🌞🌞🌞🌞🌞' + +>>> sun_and_moon[:-2:2] +'🌞🌞🌞🌞' + +>>> sun_and_moon[1:-1:2] +'🌙🌙🌙🌙' +``` + +Strings can also be broken into smaller strings via [`.split()`][str-split], which will return a `list` of substrings. + The list can then be further indexed or split, if needed. + Using `.split()` without any arguments will split the string on whitespace. + + +```python +>>> cat_ipsum = "Destroy house in 5 seconds mock the hooman." +>>> cat_ipsum.split() +... +['Destroy', 'house', 'in', '5', 'seconds', 'mock', 'the', 'hooman.'] + + +>>> cat_ipsum.split()[-1] +'hooman.' + + +>>> cat_words = "feline, four-footed, ferocious, furry" +>>> cat_words.split(', ') +... +['feline', 'four-footed', 'ferocious', 'furry'] +``` + + +Separators for `.split()` can be more than one character. +The **whole string** is used for split matching. + + +```python + +>>> colors = """red, +orange, +green, +purple, +yellow""" + +>>> colors.split(',\n') +['red', 'orange', 'green', 'purple', 'yellow'] +``` + +Strings support all [common sequence operations][common sequence operations]. + Individual code points can be iterated through in a loop via `for item in `. + Indexes _with_ items can be iterated through in a loop via `for index, item in enumerate()`. + + +```python + +>>> exercise = 'လေ့ကျင့်' + +# Note that there are more code points than perceived glyphs or characters +>>> for code_point in exercise: +... print(code_point) +... +လ +ေ +့ +က +ျ +င +် +့ + +# Using enumerate will give both the value and index position of each element. +>>> for index, code_point in enumerate(exercise): +... print(index, ": ", code_point) +... +0 : လ +1 : ေ +2 : ့ +3 : က +4 : ျ +5 : င +6 : ် +7 : ့ +``` + + +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[str-join]: https://docs.python.org/3/library/stdtypes.html#str.join +[str-split]: https://docs.python.org/3/library/stdtypes.html#str.split +[text sequence]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str +[unicode code points]: https://stackoverflow.com/questions/27331819/whats-the-difference-between-a-character-a-code-point-a-glyph-and-a-grapheme + +## Instructions + +You are helping your younger sister with her English vocabulary homework, which she is finding very tedious. + Her class is learning to create new words by adding _prefixes_ and _suffixes_. + Given a set of words, the teacher is looking for correctly transformed words with correct spelling by adding the prefix to the beginning or the suffix to the ending. + +The assignment has four activities, each with a set of text or words to work with. + + +## 1. Add a prefix to a word + +One of the most common prefixes in English is `un`, meaning "not". + In this activity, your sister needs to make negative, or "not" words by adding `un` to them. + +Implement the `add_prefix_un()` function that takes `word` as a parameter and returns a new `un` prefixed word: + + +```python +>>> add_prefix_un("happy") +'unhappy' + +>>> add_prefix_un("manageable") +'unmanageable' +``` + + +## 2. Add prefixes to word groups + +There are four more common prefixes that your sister's class is studying: + `en` (_meaning to 'put into' or 'cover with'_), + `pre` (_meaning 'before' or 'forward'_), + `auto` (_meaning 'self' or 'same'_), + and `inter` (_meaning 'between' or 'among'_). + + In this exercise, the class is creating groups of vocabulary words using these prefixes, so they can be studied together. + Each prefix comes in a list with common words it's used with. + The students need to apply the prefix and produce a string that shows the prefix applied to all of the words. + +Implement the `make_word_groups()` function that takes a `vocab_words` as a parameter in the following form: + `[, , .... ]`, and returns a string with the prefix applied to each word that looks like: + `' :: :: :: '`. + + +```python +>>> make_word_groups(['en', 'close', 'joy', 'lighten']) +'en :: enclose :: enjoy :: enlighten' + +>>> make_word_groups(['pre', 'serve', 'dispose', 'position']) +'pre :: preserve :: predispose :: preposition' + +>> make_word_groups(['auto', 'didactic', 'graph', 'mate']) +'auto :: autodidactic :: autograph :: automate' + +>>> make_word_groups(['inter', 'twine', 'connected', 'dependent']) +'inter :: intertwine :: interconnected :: interdependent' +``` + + +## 3. Remove a suffix from a word + +`ness` is a common suffix that means _'state of being'_. + In this activity, your sister needs to find the original root word by removing the `ness` suffix. + But of course there are pesky spelling rules: If the root word originally ended in a consonant followed by a 'y', then the 'y' was changed to 'i'. + Removing 'ness' needs to restore the 'y' in those root words. e.g. `happiness` --> `happi` --> `happy`. + +Implement the `remove_suffix_ness()` function that takes in a `word`, and returns the root word without the `ness` suffix. + + +```python +>>> remove_suffix_ness("heaviness") +'heavy' + +>>> remove_suffix_ness("sadness") +'sad' +``` + +## 4. Extract and transform a word + +Suffixes are often used to change the part of speech a word is assigned to. + A common practice in English is "verbing" or "verbifying" -- where an adjective _becomes_ a verb by adding an `en` suffix. + +In this task, your sister is going to practice "verbing" words by extracting an adjective from a sentence and turning it into a verb. + Fortunately, all the words that need to be transformed here are "regular" - they don't need spelling changes to add the suffix. + +Implement the `adjective_to_verb(, )` function that takes two parameters. + A `sentence` using the vocabulary word, and the `index` of the word, once that sentence is split apart. + The function should return the extracted adjective as a verb. + + +```python +>>> adjective_to_verb('I need to make that bright.', -1 ) +'brighten' + +>>> adjective_to_verb('It got dark as the sun set.', 2) +'darken' +``` + +## Source + +### Created by + +- @aldraco +- @BethanyG \ No newline at end of file diff --git a/little-sisters-vocab/strings.py b/little-sisters-vocab/strings.py new file mode 100644 index 0000000..3a97ce5 --- /dev/null +++ b/little-sisters-vocab/strings.py @@ -0,0 +1,57 @@ +"""Functions for creating, transforming, and adding prefixes to strings.""" + + +def add_prefix_un(word: str) -> str: + """ + Take the given word and add the 'un' prefix. + + :param word: str - containing the root word. + :return: str - of root word prepended with 'un'. + """ + return f'un{word}' + + +def make_word_groups(vocab_words: list) -> str: + """ + Transform a list containing a prefix and words into a string with the prefix + followed by the words with prefix prepended. + + :param vocab_words: list - of vocabulary words with prefix in first index. + :return: str - of prefix followed by vocabulary words with + prefix applied. + + This function takes a `vocab_words` list and returns a string + with the prefix and the words with prefix applied, separated + by ' :: '. + + For example: list('en', 'close', 'joy', 'lighten'), + produces the following string: 'en :: enclose :: enjoy :: enlighten'. + """ + return f' :: {vocab_words[0]}'.join(vocab_words) + + +def remove_suffix_ness(word: str) -> str: + """ + Remove the suffix from the word while keeping spelling in mind. + + :param word: str - of word to remove suffix from. + :return: str - of word with suffix removed & spelling adjusted. + + For example: "heaviness" becomes "heavy", but "sadness" becomes "sad". + """ + return f'{word[:-5]}y' if word[-5] == 'i' else word[:-4] + + +def adjective_to_verb(sentence: str, index: int): + """ + Change the adjective within the sentence to a verb. + + :param sentence: str - that uses the word in sentence. + :param index: int - index of the word to remove and transform. + :return: str - word that changes the extracted adjective to a verb. + + For example, ("It got dark as the sun set.", 2) becomes "darken". + """ + words: list[str] = sentence.split() + word: str = words[index].strip('.,!?') + return f'{word}en' diff --git a/little-sisters-vocab/strings_test.py b/little-sisters-vocab/strings_test.py new file mode 100644 index 0000000..b13d4e9 --- /dev/null +++ b/little-sisters-vocab/strings_test.py @@ -0,0 +1,126 @@ +import unittest +import pytest +from strings import (add_prefix_un, + make_word_groups, + remove_suffix_ness, + adjective_to_verb) + + +class LittleSistersVocabTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_add_prefix_un(self): + input_data = ['happy', 'manageable', 'fold', 'eaten', 'avoidable', 'usual'] + result_data = [f'un{item}' for item in input_data] + + for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', word=word, expected=expected): + + actual_result = add_prefix_un(word) + error_message = (f'Called add_prefix_un("{word}"). ' + f'The function returned "{actual_result}", but the ' + f'tests expected "{expected}" after adding "un" as a prefix.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_make_word_groups_en(self): + input_data = ['en', 'circle', 'fold', 'close', 'joy', 'lighten', 'tangle', 'able', 'code', 'culture'] + expected = ('en :: encircle :: enfold :: enclose :: enjoy :: enlighten ::' + ' entangle :: enable :: encode :: enculture') + + actual_result = make_word_groups(input_data) + error_message = (f'Called make_word_groups({input_data}). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the ' + 'word groups.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_make_word_groups_pre(self): + input_data = ['pre', 'serve', 'dispose', 'position', 'requisite', 'digest', + 'natal', 'addressed', 'adolescent', 'assumption', 'mature', 'compute'] + expected = ('pre :: preserve :: predispose :: preposition :: prerequisite :: ' + 'predigest :: prenatal :: preaddressed :: preadolescent :: preassumption :: ' + 'premature :: precompute') + + actual_result = make_word_groups(input_data) + error_message = (f'Called make_word_groups({input_data}). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the ' + 'word groups.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_make_word_groups_auto(self): + input_data = ['auto', 'didactic', 'graph', 'mate', 'chrome', 'centric', 'complete', + 'echolalia', 'encoder', 'biography'] + expected = ('auto :: autodidactic :: autograph :: automate :: autochrome :: ' + 'autocentric :: autocomplete :: autoecholalia :: autoencoder :: ' + 'autobiography') + + actual_result = make_word_groups(input_data) + error_message = (f'Called make_word_groups({input_data}). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the ' + 'word groups.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_make_words_groups_inter(self): + input_data = ['inter', 'twine', 'connected', 'dependent', 'galactic', 'action', + 'stellar', 'cellular', 'continental', 'axial', 'operative', 'disciplinary'] + expected = ('inter :: intertwine :: interconnected :: interdependent :: ' + 'intergalactic :: interaction :: interstellar :: intercellular :: ' + 'intercontinental :: interaxial :: interoperative :: interdisciplinary') + + actual_result = make_word_groups(input_data) + error_message = (f'Called make_word_groups({input_data}). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the ' + 'word groups.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_remove_suffix_ness(self): + input_data = ['heaviness', 'sadness', 'softness', 'crabbiness', 'lightness', 'artiness', 'edginess'] + result_data = ['heavy', 'sad', 'soft', 'crabby', 'light', 'arty', 'edgy'] + + for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', word=word, expected=expected): + actual_result = remove_suffix_ness(word) + error_message = (f'Called remove_suffix_ness("{word}"). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" after the ' + 'suffix was removed.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_adjective_to_verb(self): + input_data = ['Look at the bright sky.', + 'His expression went dark.', + 'The bread got hard after sitting out.', + 'The butter got soft in the sun.', + 'Her eyes were light blue.', + 'The morning fog made everything damp with mist.', + 'He cut the fence pickets short by mistake.', + 'Charles made weak crying noises.', + 'The black oil got on the white dog.'] + index_data = [-2, -1, 3, 3, -2, -3, 5, 2, 1] + result_data = ['brighten', 'darken', 'harden', 'soften', + 'lighten', 'dampen', 'shorten', 'weaken', 'blacken'] + + for variant, (sentence, index, expected) in enumerate(zip(input_data, index_data, result_data), start=1): + with self.subTest(f'variation #{variant}', sentence=sentence, index=index, expected=expected): + actual_result = adjective_to_verb(sentence, index) + error_message = (f'Called adjective_to_verb("{sentence}", {index}). ' + f'The function returned "{actual_result}", but the tests ' + f'expected "{expected}" as the verb for ' + f'the word at index {index}.') + + self.assertEqual(actual_result, expected, msg=error_message) From a4f990589aebda8e769b528fed1da94f4c183654 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Wed, 6 Aug 2025 23:39:48 -0700 Subject: [PATCH 22/75] Update strings.py --- little-sisters-vocab/strings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/little-sisters-vocab/strings.py b/little-sisters-vocab/strings.py index 3a97ce5..f94c355 100644 --- a/little-sisters-vocab/strings.py +++ b/little-sisters-vocab/strings.py @@ -11,7 +11,7 @@ def add_prefix_un(word: str) -> str: return f'un{word}' -def make_word_groups(vocab_words: list) -> str: +def make_word_groups(vocab_words: list[str]) -> str: """ Transform a list containing a prefix and words into a string with the prefix followed by the words with prefix prepended. @@ -42,7 +42,7 @@ def remove_suffix_ness(word: str) -> str: return f'{word[:-5]}y' if word[-5] == 'i' else word[:-4] -def adjective_to_verb(sentence: str, index: int): +def adjective_to_verb(sentence: str, index: int) -> str: """ Change the adjective within the sentence to a verb. @@ -53,5 +53,5 @@ def adjective_to_verb(sentence: str, index: int): For example, ("It got dark as the sun set.", 2) becomes "darken". """ words: list[str] = sentence.split() - word: str = words[index].strip('.,!?') + word: str = words[index].strip('.,!?;:') return f'{word}en' From 7a6f4dab3521f2c31ec736f30323d2da645ed8ab Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Thu, 7 Aug 2025 00:11:46 -0700 Subject: [PATCH 23/75] Card Games --- card-games/.exercism/config.json | 24 ++ card-games/.exercism/metadata.json | 1 + card-games/HELP.md | 130 ++++++++++ card-games/HINTS.md | 47 ++++ card-games/README.md | 393 +++++++++++++++++++++++++++++ card-games/lists.py | 88 +++++++ card-games/lists_test.py | 137 ++++++++++ 7 files changed, 820 insertions(+) create mode 100644 card-games/.exercism/config.json create mode 100644 card-games/.exercism/metadata.json create mode 100644 card-games/HELP.md create mode 100644 card-games/HINTS.md create mode 100644 card-games/README.md create mode 100644 card-games/lists.py create mode 100644 card-games/lists_test.py diff --git a/card-games/.exercism/config.json b/card-games/.exercism/config.json new file mode 100644 index 0000000..f7d39ca --- /dev/null +++ b/card-games/.exercism/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "itamargal", + "isaacg", + "bethanyg" + ], + "contributors": [ + "valentin-p", + "pranasziaukas" + ], + "files": { + "solution": [ + "lists.py" + ], + "test": [ + "lists_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "poker", + "blurb": "Learn about lists by tracking hands in card games." +} diff --git a/card-games/.exercism/metadata.json b/card-games/.exercism/metadata.json new file mode 100644 index 0000000..a6a97ce --- /dev/null +++ b/card-games/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"card-games","id":"d469c70514b141cba1dca4d2b26615c1","url":"https://exercism.org/tracks/python/exercises/card-games","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/card-games/HELP.md b/card-games/HELP.md new file mode 100644 index 0000000..7ff7fcc --- /dev/null +++ b/card-games/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit lists.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/card-games/HINTS.md b/card-games/HINTS.md new file mode 100644 index 0000000..3c10eca --- /dev/null +++ b/card-games/HINTS.md @@ -0,0 +1,47 @@ +# Hints + +## General + +## 1. Tracking Poker Rounds + +- Lists in Python may be [constructed][constructed] in multiple ways. +- This function should [return][return] a `list`. + +## 2. Keeping all Rounds in the Same Place + +- Sequence types such as `list` support [common operations][common sequence operations]. +- This function should [return][return] a `list`. + +## 3. Finding Prior Rounds + +- Sequence types such as `list` support a few [common operations][common sequence operations]. +- This function should [return][return] a `bool`. + +## 4. Averaging Card Values + +- To get the average, this function should count how many items are in the `list` and sum up their values. Then, return the sum divided by the count. + +## 5. Alternate Averages + +- Sequence types such as `list` support a few [common operations][common sequence operations]. +- To access an element, use the square brackets (`[]`) notation. +- Remember that the first element of the `list` is at index 0 from the **left-hand** side. +- In Python, negative indexing starts at -1 from the **right-hand** side. This means that you can find the last element of a `list` by using `[-1]`. +- Think about how you could reuse the code from the functions that you have already implemented. + +## 6. More Averaging Techniques + +- Sequence types such as `list` already support a few [common operations][common sequence operations]. +- Think about reusing the code from the functions that you just implemented. +- The slice syntax supports a _step value_ (`[::]`). + +## 7. Bonus Round Rules + +- Lists are _mutable_. Once a `list` is created, you can modify, delete or add any type of element you wish. +- Python provides a wide range of [ways to modify `lists`][ways to modify `lists`]. + + +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[constructed]: https://docs.python.org/3/library/stdtypes.html#list +[return]: https://www.w3schools.com/python/ref_keyword_return.asp +[ways to modify `lists`]: https://realpython.com/python-lists-tuples/#lists-are-mutable \ No newline at end of file diff --git a/card-games/README.md b/card-games/README.md new file mode 100644 index 0000000..a8334e3 --- /dev/null +++ b/card-games/README.md @@ -0,0 +1,393 @@ +# Card Games + +Welcome to Card Games on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +A [`list`][list] is a mutable collection of items in _sequence_. +Like most collections (_see the built-ins [`tuple`][tuple], [`dict`][dict] and [`set`][set]_), lists can hold reference to any (or multiple) data type(s) - including other lists. +Like any [sequence][sequence type], items can be accessed via `0-based index` number from the left and `-1-based index` from the right. +Lists can be copied in whole or in part via [slice notation][slice notation] or `.copy()`. + +Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`. +List elements can be iterated over using the `for item in ` construct. + `for index, item in enumerate()` can be used when both the element index and the element value are needed. + +Under the hood, `lists` are implemented as [dynamic arrays][dynamic array] -- similar to Java's [`ArrayList`][arraylist] type, and are most often used to store groups of similar data (_strings, numbers, sets etc._) of unknown length. +Lists are an extremely flexible and useful data structure and many built-in methods and operations in Python produce lists as their output. + + +## Construction + +A `list` can be declared as a _literal_ with square `[]` brackets and commas between elements: + + +```python +>>> no_elements = [] + +>>> no_elements +[] + +>>> one_element = ["Guava"] + +>>> one_element +['Guava'] + +>>> elements_separated_with_commas = ["Parrot", "Bird", 334782] + +>>> elements_separated_with_commas +['Parrot', 'Bird', 334782] +``` + +For readability, line breaks can be used when there are many elements or nested data structures within a `list`: + + +```python +>>> lots_of_entries = [ + "Rose", + "Sunflower", + "Poppy", + "Pansy", + "Tulip", + "Fuchsia", + "Cyclamen", + "Lavender" + ] + +>>> lots_of_entries +['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender'] + +# Each data structure is on its own line to help clarify what they are. +>>> nested_data_structures = [ + {"fish": "gold", "monkey": "brown", "parrot": "grey"}, + ("fish", "mammal", "bird"), + ['water', 'jungle', 'sky'] + ] + +>>> nested_data_structures +[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']] +``` + +The `list()` constructor can be used empty or with an _iterable_ as an argument. + Elements in the iterable are cycled through by the constructor and added to the `list` in order: + + +```python +>>> no_elements = list() + +>>> no_elements +[] + +# The tuple is unpacked and each element is added. +>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782)) + +>>> multiple_elements_from_tuple +['Parrot', 'Bird', 334782] + +# The set is unpacked and each element is added. +>>> multiple_elements_from_set = list({2, 3, 5, 7, 11}) + +>>> multiple_elements_from_set +[2, 3, 5, 7, 11] +``` + +Results when using a `list` constructor with a `string` or a `dict` may be surprising: + + +```python +# String elements (Unicode code points) are iterated through and added *individually*. +>>> multiple_elements_string = list("Timbuktu") + +>>> multiple_elements_string +['T', 'i', 'm', 'b', 'u', 'k', 't', 'u'] + +# Unicode separators and positioning code points are also added *individually*. +>>> multiple_code_points_string = list('अभ्यास') + +>>> multiple_code_points_string +['अ', 'भ', '्', 'य', 'ा', 'स'] + +# The iteration default for dictionaries is over the keys, so only key data is inserted into the list. +>>> source_data = {"fish": "gold", "monkey": "brown"} + +>>> multiple_elements_dict_1 = list(source_data) +['fish', 'monkey'] +``` + +Because the `list` constructor will only take _iterables_ (or nothing) as arguments, objects that are _not_ iterable will throw a type error. + Consequently, it is much easier to create a one-item `list` via the literal method. + +```python +# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails. +>>> one_element = list(16) +Traceback (most recent call last): + File "", line 1, in +TypeError: 'int' object is not iterable + +# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward +>>> one_element_from_iterable = list((16,)) + +>>> one_element_from_iterable +[16] +``` + +## Accessing elements + +Items inside lists (_as well as items in other sequence types `str` & `tuple`_) can be accessed via `0-based index` and _bracket notation_. + Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_). + + + + + + +
index from left ⟹






+ +| 0
👇🏾 | 1
👇🏾 | 2
👇🏾 | 3
👇🏾 | 4
👇🏾 | 5
👇🏾 | +|:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| P | y | t | h | o | n | +| 👆🏾
-6 | 👆🏾
-5 | 👆🏾
-4 | 👆🏾
-3 | 👆🏾
-2 | 👆🏾
-1 | +





⟸ index from right
+ + +```python +>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"] + +# Oatmeal is at index 0 or index -4. +>>> breakfast_foods[0] +'Oatmeal' + +>>> breakfast_foods[-4] +'Oatmeal' + +# Eggs are at index -2 or 2 +>>> breakfast_foods[-2] +'Eggs' + +>>> breakfast_foods[2] +'Eggs' + +# Toast is at -1 +>>> breakfast_foods[-1] +'Toast' +``` + +A section of the elements inside a `list` can be accessed via _slice notation_ (`[start:stop]`). + A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`. + _Slicing_ returns a copy of the "sliced" items and does not modify the original `list`. + + +A `step` parameter can also be used `[start:stop:step]` to "skip over" or filter the `list` elements (_for example, a `step` of 2 will select every other element in the range_): + + +```python +>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"] + +# If there is no step parameter, the step is assumed to be 1. +>>> middle_colors = colors[2:6] + +>>> middle_colors +['Green', 'Yellow', 'Orange', 'Pink'] + +# If the start or stop parameters are omitted, the slice will +# start at index zero, and will stop at the end of the list. +>>> primary_colors = colors[::3] + +>>> primary_colors +['Red', 'Yellow', 'Blue'] +``` + +## Working with lists + +The usage of the built-in `sum()` function on a list will return the sum of all the numbers in the list: + +```python +>>> number_list = [1, 2, 3, 4] +>>> sum(number_list) +10 +``` + +You can also get the _length_ of a list by using the `len()` function: + +```python +>>> long_list = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] +>>> len(long_list) +10 +``` + +Lists can be also combined in various ways: + +```python +# Using the plus + operator unpacks each list and creates a new list, but it is not efficient. +>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"] + +>>> new_via_concatenate +['George', 5, 'cat', 'Tabby'] + +# Likewise, using the multiplication operator * is the equivalent of using + n times. +>>> first_group = ["cat", "dog", "elephant"] +>>> multiplied_group = first_group * 3 + +>>> multiplied_group +['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant'] +``` + +Lists supply an _iterator_, and can be looped through/over in the same manner as other _sequence types_. + +```python +# Looping through the list and printing out each element. +>>> colors = ["Orange", "Green", "Grey", "Blue"] + +>>> for item in colors: +... print(item) +... +Orange +Green +Grey +Blue +``` + +_For a more in-depth explanation, of `loops` and `iterators`, complete the `loops` concept._ + +[arraylist]: https://beginnersbook.com/2013/12/java-arraylist/ +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[dict]: https://docs.python.org/3/library/stdtypes.html#dict +[dynamic array]: https://en.wikipedia.org/wiki/Dynamic_array +[list]: https://docs.python.org/3/library/stdtypes.html#list +[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#typesseq-mutable +[sequence type]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[set]: https://docs.python.org/3/library/stdtypes.html#set +[slice notation]: https://docs.python.org/3/reference/expressions.html#slicings +[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple + +## Instructions + +Elyse is really looking forward to playing some poker (and other card games) during her upcoming trip to Vegas. + Being a big fan of "self-tracking" she wants to put together some small functions that will help her with tracking tasks and has asked for your help thinking them through. + +## 1. Tracking Poker Rounds + +Elyse is especially fond of poker, and wants to track how many rounds she plays - and _which rounds_ those are. + Every round has its own number, and every table shows the round number currently being played. + Elyse chooses a table and sits down to play her first round. She plans on playing three rounds. + +Implement a function `get_rounds()` that takes the current round number and returns a single `list` with that round and the _next two_ that are coming up: + +```python +>>> get_rounds(27) +[27, 28, 29] +``` + +## 2. Keeping all Rounds in the Same Place + +Elyse played a few rounds at the first table, then took a break and played some more rounds at a second table ... but ended up with a different list for each table! + She wants to put the two lists together, so she can track all of the poker rounds in the same place. + +Implement a function `concatenate_rounds(, )` that takes two lists and returns a single `list` consisting of all the rounds in the first `list`, followed by all the rounds in the second `list`: + +```python +>>> concatenate_rounds([27, 28, 29], [35, 36]) +[27, 28, 29, 35, 36] +``` + +## 3. Finding Prior Rounds + +Talking about some of the prior Poker rounds, another player remarks how similarly two of them played out. + Elyse is not sure if she played those rounds or not. + +Implement a function `list_contains_round(, )` that takes two arguments, a list of rounds played and a round number. + The function will return `True` if the round is in the list of rounds played, `False` if not: + +```python +>>> list_contains_round([27, 28, 29, 35, 36], 29) +True + +>>> list_contains_round([27, 28, 29, 35, 36], 30) +False +``` + +## 4. Averaging Card Values + +Elyse wants to try out a new game called Black Joe. + It's similar to Black Jack - where your goal is to have the cards in your hand add up to a target value - but in Black Joe the goal is to get the _average_ of the card values to be 7. + The average can be found by summing up all the card values and then dividing that sum by the number of cards in the hand. + +Implement a function `card_average()` that will return the average value of a hand of Black Joe. + +```python +>>> card_average([5, 6, 7]) +6.0 +``` + +## 5. Alternate Averages + +In Black Joe, speed is important. Elyse is going to try and find a faster way of finding the average. + +She has thought of two ways of getting an _average-like_ number: + +- Take the average of the _first_ and _last_ number in the hand. +- Using the median (middle card) of the hand. + +Implement the function `approx_average_is_average()`, given `hand`, a list containing the values of the cards in your hand. + +Return `True` if either _one_ `or` _both_ of the, above named, strategies result in a number _equal_ to the _actual average_. + +Note: _The length of all hands are odd, to make finding a median easier._ + +```python +>>> approx_average_is_average([1, 2, 3]) +True + +>>> approx_average_is_average([2, 3, 4, 8, 8]) +True + +>>> approx_average_is_average([1, 2, 3, 5, 9]) +False +``` + +## 6. More Averaging Techniques + +Intrigued by the results of her averaging experiment, Elyse is wondering if taking the average of the cards at the _even_ positions versus the average of the cards at the _odd_ positions would give the same results. + Time for another test function! + +Implement a function `average_even_is_average_odd()` that returns a Boolean indicating if the average of the cards at even indexes is the same as the average of the cards at odd indexes. + +```python +>>> average_even_is_average_odd([1, 2, 3]) +True + +>>> average_even_is_average_odd([1, 2, 3, 4]) +False +``` + +## 7. Bonus Round Rules + +Every 11th hand in Black Joe is a bonus hand with a bonus rule: if the last card you draw is a Jack, you double its value. + +Implement a function `maybe_double_last()` that takes a hand and checks if the last card is a Jack (11). + If the last card **is** a Jack (11), double its value before returning the hand. + +```python +>>> hand = [5, 9, 11] +>>> maybe_double_last(hand) +[5, 9, 22] + +>>> hand = [5, 9, 10] +>>> maybe_double_last(hand) +[5, 9, 10] +``` + +## Source + +### Created by + +- @itamargal +- @isaacg +- @bethanyg + +### Contributed to by + +- @valentin-p +- @pranasziaukas \ No newline at end of file diff --git a/card-games/lists.py b/card-games/lists.py new file mode 100644 index 0000000..4a0fd4d --- /dev/null +++ b/card-games/lists.py @@ -0,0 +1,88 @@ +""" +Functions for tracking poker hands and assorted card tasks. + +Python list documentation: https://docs.python.org/3/tutorial/datastructures.html +""" + + +def get_rounds(number: int) -> list[int]: + """ + Create a list containing the current and next two round numbers. + + :param number: int - current round number. + :return: list - current round and the two that follow. + """ + return [number, number + 1, number + 2] + + +def concatenate_rounds(rounds_1: list[int], + rounds_2: list[int]) -> list[int]: + """ + Concatenate two lists of round numbers. + + :param rounds_1: list - first rounds played. + :param rounds_2: list - second set of rounds played. + :return: list - all rounds played. + """ + return rounds_1 + rounds_2 + + +def list_contains_round(rounds: list[int], number: int) -> bool: + """ + Check if the list of rounds contains the specified number. + + :param rounds: list - rounds played. + :param number: int - round number. + :return: bool - was the round played? + """ + return number in rounds + + +def card_average(hand: list) -> float: + """ + Calculate and returns the average card value from the list. + + :param hand: list - cards in hand. + :return: float - average value of the cards in the hand. + """ + return sum(hand) / len(hand) + + +def approx_average_is_average(hand: list[int]) -> bool: + """ + Return if the (average of first and last card values) OR ('middle' card) == calculated average. + + :param hand: list - cards in hand. + :return: bool - does one of the approximate averages equal the `true average`? + """ + avg: float = card_average(hand) + return (hand[0] + hand[-1]) / 2 == avg or hand[len(hand) // 2] == avg + + +def average_even_is_average_odd(hand: list[int]) -> bool: + """ + Return if the (average of even indexed card values) == (average of odd indexed card values). + + :param hand: list - cards in hand. + :return: bool - are even and odd averages equal? + """ + even: list = [] + odd: list = [] + + for i, n in enumerate(hand): + if i % 2 == 0: + even.append(n) + else: + odd.append(n) + + return sum(even) / len(even) == sum(odd) / len(odd) + + +def maybe_double_last(hand: list[int]) -> list[int]: + """ + Multiply a Jack card value in the last index position by 2. + + :param hand: list - cards in hand. + :return: list - hand with Jacks (if present) value doubled. + """ + return hand if hand[-1] != 11 else hand[:-1] + [(hand[-1] * 2)] diff --git a/card-games/lists_test.py b/card-games/lists_test.py new file mode 100644 index 0000000..e550112 --- /dev/null +++ b/card-games/lists_test.py @@ -0,0 +1,137 @@ +import unittest +import pytest + +from lists import ( + get_rounds, + concatenate_rounds, + list_contains_round, + card_average, + approx_average_is_average, + average_even_is_average_odd, + maybe_double_last, +) + + +class CardGamesTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_get_rounds(self): + + input_data = [0, 1, 10, 27, 99, 666] + result_data = [[0, 1, 2], [1, 2, 3], + [10, 11, 12], [27, 28, 29], + [99, 100, 101], [666, 667, 668]] + + for variant, (number, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', number=number, expected=expected): + actual_result = get_rounds(number) + error_message = (f'Called get_rounds({number}). ' + f'The function returned {actual_result}, ' + f'but the tests expected rounds {expected} ' + f'given the current round {number}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_concatenate_rounds(self): + + input_data = [([], []), ([0, 1], []), ([], [1, 2]), + ([1], [2]), ([27, 28, 29], [35, 36]), + ([1, 2, 3], [4, 5, 6])] + + result_data = [[], [0, 1], [1, 2], [1, 2], + [27, 28, 29, 35, 36], + [1, 2, 3, 4, 5, 6]] + + for variant, ((rounds_1, rounds_2), expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', rounds_1=rounds_1, rounds_2=rounds_2, expected=expected): + actual_result = concatenate_rounds(rounds_1, rounds_2) + error_message = (f'Called concatenate_rounds({rounds_1}, {rounds_2}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} as the concatenation ' + f'of {rounds_1} and {rounds_2}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_list_contains_round(self): + + input_data = [([], 1), ([1, 2, 3], 0), + ([27, 28, 29, 35, 36], 30), + ([1], 1), ([1, 2, 3], 1), + ([27, 28, 29, 35, 36], 29)] + result_data = [False, False, False, True, True, True] + + for variant, ((rounds, round_number), expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', rounds=rounds, round_number=round_number, expected=expected): + actual_result = list_contains_round(rounds, round_number) + error_message = (f'Called list_contains_round({rounds}, {round_number}). ' + f'The function returned {actual_result}, but round {round_number} ' + f'{"is" if expected else "is not"} in {rounds}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_card_average(self): + + input_data = [[1], [5, 6, 7], [1, 2, 3, 4], [1, 10, 100]] + result_data = [1.0, 6.0, 2.5, 37.0] + + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = card_average(hand) + error_message = (f'Called card_average({hand}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} as the average of {hand}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=5) + def test_approx_average_is_average(self): + + input_data = [[0, 1, 5], [3, 6, 9, 12, 150], [1, 2, 3, 5, 9], + [2, 3, 4, 7, 8], [1, 2, 3], [2, 3, 4], + [2, 3, 4, 8, 8], [1, 2, 4, 5, 8]] + + result_data = [False, False, False, False, True, True, True, True] + + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = approx_average_is_average(hand) + error_message = (f'Called approx_average_is_average({hand}). ' + f'The function returned {actual_result}, but ' + f'the hand {hand} {"does" if expected else "does not"} ' + f'yield the same approximate average.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=6) + def test_average_even_is_average_odd(self): + + input_data = [[5, 6, 8], [1, 2, 3, 4], [1, 2, 3], [5, 6, 7], [1, 3, 5, 7, 9]] + result_data = [False, False, True, True, True] + + for variant, (input_hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', input_hand=input_hand, expected=expected): + actual_result = average_even_is_average_odd(input_hand) + error_message = (f'Called average_even_is_average_odd({input_hand}). ' + f'The function returned {actual_result}, but ' + f'the hand {"does" if expected else "does not"} ' + f'yield the same odd-even average.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=7) + def test_maybe_double_last(self): + + input_data = [(1, 2, 11), (5, 9, 11), (5, 9, 10), (1, 2, 3), (1, 11, 8)] + result_data = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3], [1, 11, 8]] + + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=list(hand), expected=expected): + actual_result = maybe_double_last(list(hand)) + error_message = (f'Called maybe_double_last({list(hand)}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} as the maybe-doubled version of {list(hand)}.') + + self.assertEqual(actual_result, expected, msg=error_message) From 8c5ba97e097b9e48af8a494a0b9b010392e037aa Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Thu, 7 Aug 2025 00:15:26 -0700 Subject: [PATCH 24/75] Update lists.py --- card-games/lists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/card-games/lists.py b/card-games/lists.py index 4a0fd4d..21508f0 100644 --- a/card-games/lists.py +++ b/card-games/lists.py @@ -38,7 +38,7 @@ def list_contains_round(rounds: list[int], number: int) -> bool: return number in rounds -def card_average(hand: list) -> float: +def card_average(hand: list[list]) -> float: """ Calculate and returns the average card value from the list. From fdb5924781a83625dae50606c97f75250b89707c Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Thu, 7 Aug 2025 00:30:33 -0700 Subject: [PATCH 25/75] Update lists.py --- card-games/lists.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/card-games/lists.py b/card-games/lists.py index 21508f0..1fc2e68 100644 --- a/card-games/lists.py +++ b/card-games/lists.py @@ -66,15 +66,8 @@ def average_even_is_average_odd(hand: list[int]) -> bool: :param hand: list - cards in hand. :return: bool - are even and odd averages equal? """ - even: list = [] - odd: list = [] - - for i, n in enumerate(hand): - if i % 2 == 0: - even.append(n) - else: - odd.append(n) - + even = hand[::2] + odd = hand[1::2] return sum(even) / len(even) == sum(odd) / len(odd) From 47da3abd57b69623bbdf658301eb4c5c018d2a04 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 11 Aug 2025 17:19:58 -0700 Subject: [PATCH 26/75] Chaitana's Colossal Coaster --- .../.exercism/config.json | 24 + .../.exercism/metadata.json | 1 + chaitanas-colossal-coaster/HELP.md | 130 ++++++ chaitanas-colossal-coaster/HINTS.md | 39 ++ chaitanas-colossal-coaster/README.md | 419 ++++++++++++++++++ chaitanas-colossal-coaster/list_methods.py | 95 ++++ .../list_methods_test.py | 316 +++++++++++++ 7 files changed, 1024 insertions(+) create mode 100644 chaitanas-colossal-coaster/.exercism/config.json create mode 100644 chaitanas-colossal-coaster/.exercism/metadata.json create mode 100644 chaitanas-colossal-coaster/HELP.md create mode 100644 chaitanas-colossal-coaster/HINTS.md create mode 100644 chaitanas-colossal-coaster/README.md create mode 100644 chaitanas-colossal-coaster/list_methods.py create mode 100644 chaitanas-colossal-coaster/list_methods_test.py diff --git a/chaitanas-colossal-coaster/.exercism/config.json b/chaitanas-colossal-coaster/.exercism/config.json new file mode 100644 index 0000000..5374c4d --- /dev/null +++ b/chaitanas-colossal-coaster/.exercism/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "mohanrajanr", + "BethanyG" + ], + "contributors": [ + "BethanyG", + "valentin-p", + "pranasziaukas" + ], + "files": { + "solution": [ + "list_methods.py" + ], + "test": [ + "list_methods_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "spiral-matrix", + "blurb": "Learn useful list methods helping Chaitana manage the lines for her colossal roller coaster." +} diff --git a/chaitanas-colossal-coaster/.exercism/metadata.json b/chaitanas-colossal-coaster/.exercism/metadata.json new file mode 100644 index 0000000..ad5441b --- /dev/null +++ b/chaitanas-colossal-coaster/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"chaitanas-colossal-coaster","id":"c7de01a0134641389ec4655d159c69e0","url":"https://exercism.org/tracks/python/exercises/chaitanas-colossal-coaster","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/chaitanas-colossal-coaster/HELP.md b/chaitanas-colossal-coaster/HELP.md new file mode 100644 index 0000000..0f5ca33 --- /dev/null +++ b/chaitanas-colossal-coaster/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit list_methods.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/chaitanas-colossal-coaster/HINTS.md b/chaitanas-colossal-coaster/HINTS.md new file mode 100644 index 0000000..df85ae9 --- /dev/null +++ b/chaitanas-colossal-coaster/HINTS.md @@ -0,0 +1,39 @@ +# Hints + +- Make sure you have a good understanding of how to create and update lists. +- The Python [documentation on `lists`][python lists] can be really helpful. +- The Python [tutorial section on `lists`][more on lists] is also a good resource. + +## 1. Add Me to the queue + +- An `if-else` statement can help you find which ticket type you are dealing with. +- You can then `append()` the person to the queue based on the ticket type. + +## 2. Where are my friends + +- You need to find the `index()` of the friend name from the queue. + +## 3. Can I please join them? + +- Since you know the `index()`, you can `insert()` the friend into the queue at that point. + +## 4. Mean person in the queue + +- You know the mean persons name, so you can `remove()` them from the queue. + +## 5. Namefellows + +- `count()`-ing the occurrences of the `name` in the queue could be a good strategy here. + +## 6. Remove the last person + +- Although you could `remove()` the person by name, `pop()`-ing them out might be quicker. + +## 7. Sort the Queue List + +- Don't forget that You need to avoid mutating the queue and losing its original order. +- Once you have a `copy()`, `sort()`-ing should be straightforward. +- We're looking for an _ascending_ sort, or _alphabetical from a-z_. + +[python lists]: https://docs.python.org/3.11/library/stdtypes.html#list +[more on lists]: https://docs.python.org/3.11/tutorial/datastructures.html#more-on-lists \ No newline at end of file diff --git a/chaitanas-colossal-coaster/README.md b/chaitanas-colossal-coaster/README.md new file mode 100644 index 0000000..d356814 --- /dev/null +++ b/chaitanas-colossal-coaster/README.md @@ -0,0 +1,419 @@ +# Chaitana's Colossal Coaster + +Welcome to Chaitana's Colossal Coaster on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +A [`list`][list] is a mutable collection of items in _sequence_. + Like most collections (_see the built-ins [`tuple`][tuple], [`dict`][dict] and [`set`][set]_), lists can hold reference to any (or multiple) data type(s) - including other lists. + Lists can be copied in whole or in part via [slice notation][slice notation] or through the use of `.copy()`. + Like any [sequence][sequence type], elements within `lists` are referenced by `0-based index` number from the left, or `-1-based index` number from the right. + +Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`. + Elements inside a `list` can be iterated over using the `for item in ` construct. + `for index, item in enumerate()` can be used when both the element index and element value are needed. + +Python also provides many useful [list-methods][list-methods] for working with lists. + A selection of these `list methods` is covered below. + + +Note that when you manipulate a `list` with a `list-method`, **you alter the list** object that has been passed. + If you do not wish to mutate the original `list`, you will need to at least make a `shallow copy` of it via slice or `.copy()`. + + +## Adding Items + +To add an item to the end or "right-hand side" of an existing list, use `.append()`: + +```python +>>> numbers = [1, 2, 3] +>>> numbers.append(9) + +>>> numbers +[1, 2, 3, 9] +``` + +Rather than _appending_, `.insert()` gives you the ability to add the item to a _specific index_ in the list. +It takes 2 parameters: + +1. the `` at which you want the item to be inserted. +2. the `` to be inserted. + +**Note**: If the given `index` is 0, the item will be added to the start ("left-hand side") of the `list`. + If the supplied `index` is greater than the final `index` on the `list`, the item will be added in the final position -- the equivalent of using `.append()`. + + +```python +>>> numbers = [1, 2, 3] +>>> numbers.insert(0, -2) + +>>> numbers +[-2, 1, 2, 3] + +>>> numbers.insert(1, 0) + +>>> numbers +[-2, 0, 1, 2, 3] +``` + + +`.extend()` can be used to combine an existing list with the elements from another iterable (for example, a `set`, `tuple`, `str`, or `list`). + The iterable is _unpacked_ and elements are appended in order (_Using `.append()` in this circumstance would add the entire iterable as a **single item**._). + + +```python +>>> numbers = [1, 2, 3] +>>> other_numbers = [5, 6, 7] + +>>> numbers.extend(other_numbers) + +>>> numbers +[1, 2, 3, 5, 6, 7] + +>>> numbers.extend([8, 9]) + +>>> numbers +[1, 2, 3, 5, 6, 7, 8, 9] + +>>> numbers.append([8,9]) + +>>> numbers +[1, 2, 3, 5, 6, 7, 8, 9, [8, 9]] +``` + + +## Removing Items + +To delete an item from a list use `.remove()`, passing the item to be removed as an argument. + `.remove()` will throw a `ValueError` if the item is not present in the `list`. + + +```python +>>> numbers = [1, 2, 3] +>>> numbers.remove(2) + +>>> numbers +[1, 3] + +# Trying to remove a value that is not in the list throws a ValueError +>>> numbers.remove(0) +ValueError: list.remove(x): x not in list +``` + + +Alternatively, using the `.pop()` method will both remove **and** `return` an element for use. + + +`.pop()` takes one optional parameter: the `index` of the item to be removed and returned. + If the (optional) `index` argument is not specified, the final element of the `list` will be removed and returned. + If the `index` specified is higher than the final item `index`, an `IndexError` is raised. + + +```python +>>> numbers = [1, 2, 3] + +>>> numbers.pop(0) +1 + +>>> numbers +[2, 3] + +>>> numbers.pop() +3 + +>>> numbers +[2] + +>>> numbers.pop(1) +Traceback (most recent call last): + File "", line 1, in +IndexError: pop index out of range +``` + +All elements can be removed from a `list` with `list.clear()`. It doesn't take any parameters. + +```python +>>> numbers = [1, 2, 3] +>>> numbers.clear() + +>>> numbers +[] +``` + +## Reversing and reordering + +The `.reverse()` method will reverse the order of elements **in-place**. + + +```python +>>> numbers = [1, 2, 3] +>>> numbers.reverse() + +>>> numbers +[3, 2, 1] +``` + + +A list can be re-ordered _**in place**_ with the help of [`.sort()`][sort]. +Default sort order is _ascending_ from the left. +The Python docs offer [additional tips and techniques for sorting][sorting how to]. + +~~~~exercism/note + From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward. + +[powersort]: https://www.wild-inter.net/publications/munro-wild-2018 +[timsort]: https://en.wikipedia.org/wiki/Timsort +~~~~ + + +```python +>>> names = ["Tony", "Natasha", "Thor", "Bruce"] + +# The default sort order is *ascending*. +>>> names.sort() + +>>> names +["Bruce", "Natasha", "Thor", "Tony"] +``` + +If a _descending_ order is desired, pass the `reverse=True` argument: + +```python +>>> names = ["Tony", "Natasha", "Thor", "Bruce"] +>>> names.sort(reverse=True) + +>>> names +["Tony", "Thor", "Natasha", "Bruce"] +``` + +For cases where mutating the original list is undesirable, the built-in [`sorted()`][sorted] function can be used to return a sorted **copy**. + + +```python +>>> names = ["Tony", "Natasha", "Thor", "Bruce"] + +>>> sorted(names) +['Bruce', 'Natasha', 'Thor', 'Tony'] +``` + + +## Occurrences of an item in a list + +The number of occurrences of an element in a list can be calculated with the help of `list.count()`. + It takes the `item` to be counted as its argument and returns the total number of times that element appears in the `list`. + + +```python +>>> items = [1, 4, 7, 8, 2, 9, 2, 1, 1, 0, 4, 3] + +>>> items.count(1) +3 +``` + +## Finding the index of items + +`.index()` will return the `index` number of the _first occurrence_ of an item passed in. + If there are no occurrences, a `ValueError` is raised. + If the exact position of an item isn't needed, the built-in `in` operator is more efficient for checking if a list contains a given value. + + +Indexing is zero-based from the left, so the position of the "first" item is `0`. +Indexing will also work from the right, beginning with `-1`. + + +```python +>>> items = [7, 4, 1, 0, 2, 5] + +>>> items.index(4) +1 + +>>> items.index(10) +ValueError: 10 is not in list +``` + +`start` and `end` indices can also be provided to narrow the search to a specific section of the `list`: + +```python +>>> names = ["Tina", "Leo", "Thomas", "Tina", "Emily", "Justin"] + +>>> names.index("Tina") +0 + +>>> names.index("Tina", 2, 5) +3 +``` + +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[dict]: https://docs.python.org/3/library/stdtypes.html#dict +[list-methods]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists +[list]: https://docs.python.org/3/library/stdtypes.html#list +[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#typesseq-mutable +[sequence type]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[set]: https://docs.python.org/3/library/stdtypes.html#set +[slice notation]: https://docs.python.org/3/reference/expressions.html#slicings +[sort]: https://docs.python.org/3/library/stdtypes.html#list.sort +[sorted]: https://docs.python.org/3/library/functions.html#sorted +[sorting how to]: https://docs.python.org/3/howto/sorting.html +[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple + +## Instructions + +Chaitana owns a very popular theme park. + She only has one ride in the very center of beautifully landscaped grounds: The Biggest Roller Coaster in the World(TM). + Although there is only this one attraction, people travel from all over the world and stand in line for hours for the opportunity to ride Chaitana's hypercoaster. + +There are two queues for this ride, each represented as a `list`: + +1. Normal Queue +2. Express Queue (_also known as the Fast-track_) - where people pay extra for priority access. + + +You have been asked to write some code to better manage the guests at the park. + You need to implement the following functions as soon as possible before the guests (and your boss, Chaitana!) get cranky. + Make sure you read carefully. + Some tasks ask that you change or update the existing queue, while others ask you to make a copy of it. + + +## 1. Add me to the queue + +Define the `add_me_to_the_queue()` function that takes 4 parameters `, , , ` and returns the appropriate queue updated with the person's name. + + +1. `` is an `int` with 1 == express_queue and 0 == normal_queue. +2. `` is the name (as a `str`) of the person to be added to the respective queue. + + +```python +>>> add_me_to_the_queue(express_queue=["Tony", "Bruce"], normal_queue=["RobotGuy", "WW"], ticket_type=1, person_name="RichieRich") +... +["Tony", "Bruce", "RichieRich"] + +>>> add_me_to_the_queue(express_queue=["Tony", "Bruce"], normal_queue=["RobotGuy", "WW"], ticket_type=0, person_name="HawkEye") +.... +["RobotGuy", "WW", "HawkEye"] +``` + +## 2. Where are my friends? + +One person arrived late at the park but wants to join the queue where their friends are waiting. + But they have no idea where their friends are standing and there isn't any phone reception to call them. + +Define the `find_my_friend()` function that takes 2 parameters `queue` and `friend_name` and returns the position in the queue of the person's name. + + +1. `` is the `list` of people standing in the queue. +2. `` is the name of the friend whose index (place in the queue) you need to find. + +Remember: Indexing starts at 0 from the left, and -1 from the right. + + +```python +>>> find_my_friend(queue=["Natasha", "Steve", "T'challa", "Wanda", "Rocket"], friend_name="Steve") +... +1 +``` + + +## 3. Can I please join them? + +Now that their friends have been found (in task #2 above), the late arriver would like to join them at their place in the queue. +Define the `add_me_with_my_friends()` function that takes 3 parameters `queue`, `index`, and `person_name`. + + +1. `` is the `list` of people standing in the queue. +2. `` is the position at which the new person should be added. +3. `` is the name of the person to add at the index position. + +Return the queue updated with the late arrivals name. + + +```python +>>> add_me_with_my_friends(queue=["Natasha", "Steve", "T'challa", "Wanda", "Rocket"], index=1, person_name="Bucky") +... +["Natasha", "Bucky", "Steve", "T'challa", "Wanda", "Rocket"] +``` + +## 4. Mean person in the queue + +You just heard from the queue that there is a really mean person shoving, shouting, and making trouble. + You need to throw that miscreant out for bad behavior! + + +Define the `remove_the_mean_person()` function that takes 2 parameters `queue` and `person_name`. + + +1. `` is the `list` of people standing in the queue. +2. `` is the name of the person that needs to be kicked out. + +Return the queue updated without the mean person's name. + +```python +>>> remove_the_mean_person(queue=["Natasha", "Steve", "Eltran", "Wanda", "Rocket"], person_name="Eltran") +... +["Natasha", "Steve", "Wanda", "Rocket"] +``` + + +## 5. Namefellows + +You may not have seen two unrelated people who look exactly the same, but you have _definitely_ seen unrelated people with the exact same name (_namefellows_)! + Today, it looks like there are a lot of them in attendance. + You want to know how many times a particular name occurs in the queue. + +Define the `how_many_namefellows()` function that takes 2 parameters `queue` and `person_name`. + +1. `` is the `list` of people standing in the queue. +2. `` is the name you think might occur more than once in the queue. + + +Return the number of occurrences of `person_name`, as an `int`. + + +```python +>>> how_many_namefellows(queue=["Natasha", "Steve", "Eltran", "Natasha", "Rocket"], person_name="Natasha") +... +2 +``` + +## 6. Remove the last person + +Sadly, it's overcrowded at the park today and you need to remove the last person in the normal line (_you will give them a voucher to come back in the fast-track on another day_). + You will have to define the function `remove_the_last_person()` that takes 1 parameter `queue`, which is the list of people standing in the queue. + +You should update the `list` and also `return` the name of the person who was removed, so you can write them a voucher. + + +```python +>>> remove_the_last_person(queue=["Natasha", "Steve", "Eltran", "Natasha", "Rocket"]) +... +'Rocket' +``` + +## 7. Sort the Queue List + +For administrative purposes, you need to get all the names in a given queue in alphabetical order. + + +Define the `sorted_names()` function that takes 1 argument, `queue`, (the `list` of people standing in the queue), and returns a `sorted` copy of the `list`. + + +```python +>>> sorted_names(queue=["Natasha", "Steve", "Eltran", "Natasha", "Rocket"]) +... +['Eltran', 'Natasha', 'Natasha', 'Rocket', 'Steve'] +``` + +## Source + +### Created by + +- @mohanrajanr +- @BethanyG + +### Contributed to by + +- @BethanyG +- @valentin-p +- @pranasziaukas \ No newline at end of file diff --git a/chaitanas-colossal-coaster/list_methods.py b/chaitanas-colossal-coaster/list_methods.py new file mode 100644 index 0000000..029b503 --- /dev/null +++ b/chaitanas-colossal-coaster/list_methods.py @@ -0,0 +1,95 @@ +"""Functions to manage and organize queues at Chaitana's roller coaster.""" + + +def add_me_to_the_queue(express_queue: list[str], + normal_queue: list[str], + ticket_type: int, + person_name: str) -> list[str]: + """ + Add a person to the 'express' or 'normal' queue depending on the ticket number. + + :param express_queue: list[str] - names in the Fast-track queue. + :param normal_queue: list[str] - names in the normal queue. + :param ticket_type: int - type of ticket. 1 = express, 0 = normal. + :param person_name: str - name of person to add to a queue. + :return: list - the (updated) queue the name was added to. + """ + if ticket_type: + express_queue.append(person_name) + return express_queue + + normal_queue.append(person_name) + return normal_queue + + +def find_my_friend(queue: list[str], + friend_name: str) -> int: + """ + Search the queue for a name and return their queue position (index). + + :param queue: list - names in the queue. + :param friend_name: str - name of friend to find. + :return: int - index at which the friends name was found. + """ + return queue.index(friend_name) + + +def add_me_with_my_friends(queue: list[str], + index: int, + person_name: str) -> list[str]: + """ + Insert the late arrival's name at a specific index of the queue. + + :param queue: list - names in the queue. + :param index: int - the index at which to add the new name. + :param person_name: str - the name to add. + :return: list - queue updated with new name. + """ + queue.insert(index, person_name) + return queue + + +def remove_the_mean_person(queue: list[str], + person_name: str) -> list[str]: + """ + Remove the mean person from the queue by the provided name. + + :param queue: list[str] - names in the queue. + :param person_name: str - name of mean person. + :return: list - queue update with the mean persons name removed. + """ + queue.remove(person_name) + return queue + + +def how_many_namefellows(queue: list[str], + person_name: str) -> int: + """ + Count how many times the provided name appears in the queue. + + :param queue: list - names in the queue. + :param person_name: str - name you wish to count or track. + :return: int - the number of times the name appears in the queue. + """ + return queue.count(person_name) + + +def remove_the_last_person(queue: list[str]) -> str: + """ + Remove the person in the last index from the queue and return their name. + + :param queue: list - names in the queue. + :return: str - name that has been removed from the end of the queue. + """ + name: str = queue.pop() + return name + + +def sorted_names(queue: list[str]) -> list[str]: + """ + Sort the names in the queue in alphabetical order and return the result. + + :param queue: list - names in the queue. + :return: list - copy of the queue in alphabetical order. + """ + return sorted(queue) diff --git a/chaitanas-colossal-coaster/list_methods_test.py b/chaitanas-colossal-coaster/list_methods_test.py new file mode 100644 index 0000000..7a754b7 --- /dev/null +++ b/chaitanas-colossal-coaster/list_methods_test.py @@ -0,0 +1,316 @@ +import unittest +from copy import deepcopy +import pytest + + +from list_methods import ( + add_me_to_the_queue, + find_my_friend, + add_me_with_my_friends, + remove_the_mean_person, + how_many_namefellows, + remove_the_last_person, + sorted_names, +) + + +class ListMethodsTest(unittest.TestCase): + @pytest.mark.task(taskno=1) + def test_add_me_to_the_queue(self): + test_data = [ + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ] + + for variant, (params, expected) in enumerate(test_data, start=1): + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + express_queue, normal_queue, ticket_type, person_name = deepcopy(params) + + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = add_me_to_the_queue(*params) + + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The function returned {actual_result},\n' + f' but the tests expected {expected} after {person_name} was added.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=1) + def test_add_me_to_the_queue_validate_queue(self): + test_data = [ + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ] + + for variant, (params, expected) in enumerate(test_data, start=1): + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + express_queue, normal_queue, ticket_type, person_name = deepcopy(params) + express, normal, ticket, name = params + + with self.subTest(f'variation #{variant}', + express=express, normal=normal, + ticket=ticket, name=name, expected=expected): + + actual_result = add_me_to_the_queue(express, normal, ticket, name) + + if type == 1: + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The queue == {express}, but the tests expected\n' + f'queue == {expected} after {person_name} was added.' + ) + + self.assertIs(actual_result, express, msg=error_message) + + if type == 0: + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The queue == {normal}, but the tests expected \n' + f'queue == {expected} after {person_name} was added.' + ) + + self.assertIs(actual_result, normal, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_find_my_friend(self): + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Rocket'), + ] + + result_data = (0,1,4) + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = find_my_friend(*params) + error_message = ( + f'\nCalled find_my_friend{params}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when looking for\n' + f'{params[-1]} in the queue.' + ) + + self.assertIs(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_add_me_with_my_friends(self): + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky'), + ] + + result_data = [ + ['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky'], + ] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + queue, index, person_name = deepcopy(params) + + with self.subTest(f'variation #{variant}', params=params, expected=expected): + + actual_result = add_me_with_my_friends(*params) + error_message = ( + f'\nCalled add_me_with_my_friends{queue, index, person_name}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when adding\n' + f'{person_name} to position {index} in the queue.' + ) + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_add_me_with_my_friends_validate_queue(self): + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky'), + ] + + result_data = [ + ['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky'], + ] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + start_queue, add_index, person_name = deepcopy(params) + queue, _, _ = params + + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = add_me_with_my_friends(*params) + error_message = ( + f'\nCalled add_me_with_my_friends{start_queue, add_index, person_name}.\n' + f'The function returned {actual_result},\n' + 'but the original queue was unmodified. The tests expected the \n' + f'*original* queue to be modified by adding "{person_name}".' + ) + + self.assertIs(actual_result, queue, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_remove_the_mean_person(self): + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), + (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), + (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Steve'), + ] + + result_data = [ + ['Natasha', 'Steve', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Wanda', 'Ultron'], + ['Ultron', 'Natasha', 'Wanda', 'Rocket'], + ] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + start_queue, person_name = deepcopy(params) + + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = remove_the_mean_person(*params) + error_message = ( + f'\nCalled remove_the_mean_person{start_queue, person_name}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when removing\n' + f'{person_name} from the queue.' + ) + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_remove_the_mean_person_validate_queue(self): + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), + (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), + (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Steve'), + ] + + result_data = [ + ['Natasha', 'Steve', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Wanda', 'Ultron'], + ['Ultron', 'Natasha', 'Wanda', 'Rocket'], + ] + + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + start_queue, person_name = deepcopy(params) + queue, _ = params + + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = remove_the_mean_person(*params) + error_message = ( + f'\nCalled remove_the_mean_person{start_queue, person_name}.\n' + f'The function returned {actual_result}, queue == {queue}.\n' + f'But the tests expected queue == {expected} when removing\n' + f'{person_name}.' + ) + + self.assertIs(actual_result, queue, msg=error_message) + + + @pytest.mark.task(taskno=5) + def test_how_many_namefellows(self): + test_data = [(['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Bucky'), + (['Natasha', 'Steve', 'Ultron', 'Rocket'], 'Natasha'), + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Natasha')] + + result_data = (0,1,2) + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = how_many_namefellows(*params) + + error_message = (f'Called how_many_namefellows{params}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} when counting ' + f'namefellows in the queue for {params[-1]}.') + + self.assertIs(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=6) + def test_remove_the_last_person(self): + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket'], 'Ultron'), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron'], 'Natasha') + ] + for variant, (queue, modified, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, modified=modified, expected=expected): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + unmodified_queue = deepcopy(queue) + expected_result = expected + actual_result = remove_the_last_person(queue) + expected_queue = modified + + error_message = (f'\nCalled remove_the_last_person({unmodified_queue}).\n' + f'The function was expected to remove and return the name "{expected_result}" ' + f'and change the queue to {expected_queue},\n' + f'but the name "{actual_result}" was returned and the queue == {queue}.') + + self.assertEqual((actual_result, queue), (expected_result, expected_queue), msg=error_message) + + + @pytest.mark.task(taskno=7) + def test_sorted_names(self): + test_data =( + (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), + (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), + (['Gamora', 'Loki', 'Tony', 'Peggy', 'Okoye'], ['Gamora', 'Loki', 'Okoye', 'Peggy', 'Tony']), + ) + + for variant, (queue, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, expected=expected): + actual_result = sorted_names(queue) + expected_result = expected + + error_message = (f'\nCalled sorted_names({queue}).\n' + f'The function returned {actual_result}, but \n' + f'the tests expect {expected_result}.') + + self.assertEqual(actual_result, expected_result, msg=error_message) + + @pytest.mark.task(taskno=7) + def test_sorted_names_validate_queue(self): + test_data = ( + (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), + (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), + (['Gamora', 'Loki', 'Tony', 'Peggy', 'Okoye'], ['Gamora', 'Loki', 'Okoye', 'Peggy', 'Tony']), + ) + + for variant, (queue, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, expected=expected): + + # Deepcopy() is needed here because the input lists might be mutated. + # That mutation wrecks havoc with the verification and error messaging. + original_queue = deepcopy(queue) + actual_result = sorted_names(queue) + expected_result = expected + + error_message = (f'\nCalled sorted_names({original_queue}).\n' + f'The function returned {actual_result}, \n' + f'with a queue == {queue}.\n' + f'The tests expect {expected_result}, \n' + f'with a queue == {original_queue}.') + + self.assertIsNot(actual_result, queue, msg=error_message) From 52e2c575148723ca5344b303f0f6045fce7df7da Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:51:15 +0000 Subject: [PATCH 27/75] [Sync Iteration] python/making-the-grade/1 --- solutions/python/making-the-grade/1/loops.py | 84 ++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 solutions/python/making-the-grade/1/loops.py diff --git a/solutions/python/making-the-grade/1/loops.py b/solutions/python/making-the-grade/1/loops.py new file mode 100644 index 0000000..2c922a6 --- /dev/null +++ b/solutions/python/making-the-grade/1/loops.py @@ -0,0 +1,84 @@ +"""Functions for organizing and calculating student exam scores.""" + + +def round_scores(student_scores: list) -> list: + """ + Round all provided student scores. + + :param student_scores: list - float or int of student exam scores. + :return: list - student scores *rounded* to nearest integer value. + """ + return [round(score) for score in student_scores] + + +def count_failed_students(student_scores: list) -> int: + """ + Count the number of failing students out of the group provided. + + :param student_scores: list - containing int student scores. + :return: int - count of student scores at or below 40. + """ + return len([score for score in student_scores if score <= 40.0]) + + +def above_threshold(student_scores: list, + threshold: int) -> list: + """ + Filter out above threshold scores. + + Determine how many of the provided student scores were 'the best' + based on the provided threshold. + + :param student_scores: list - of integer scores. + :param threshold: int - threshold to cross to be the "best" score. + :return: list - of integer scores that are at or above the "best" threshold. + """ + return [score for score in student_scores if score >= threshold] + + +def letter_grades(highest: int) -> list: + """ + Create a list of grade thresholds based on the provided highest grade. + + :param highest: int - value of highest exam score. + :return: list - of lower threshold scores for each D-A letter grade interval. + For example, where the highest score is 100, and failing is <= 40, + The result would be [41, 56, 71, 86]: + + 41 <= "D" <= 55 + 56 <= "C" <= 70 + 71 <= "B" <= 85 + 86 <= "A" <= 100 + """ + interval = (highest - 40) // 4 + return [41 + i * interval for i in range(4)] + + +def student_ranking(student_scores: list, + student_names: list) -> list[str]: + """ + Organize the student's rank, name, and grade information in descending order. + + :param student_scores: list - of scores in descending order. + :param student_names: list - of string names by exam score in descending order. + :return: list - of strings in format [". : "]. + """ + return [f"{i}. {name}: {score}" for i, score, name in zip( + range(1, len(student_scores) + 1), student_scores, student_names)] + + +def perfect_score(student_info: list) -> list: + """ + Create a list that contains the name and grade of the first + student to make a perfect score on the exam. + + :param student_info: list - of [, ] lists. + :return: list - first `[, 100]` or `[]` if no + student score of 100 is found. + """ + result_lst: list = [] + for student, score in student_info: + if score == 100: + return [student, score] + + return result_lst From c40ce553b150bf220372369548cb1d433e76d8e7 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:54:35 +0000 Subject: [PATCH 28/75] [Sync Iteration] python/making-the-grade/2 --- solutions/python/making-the-grade/2/loops.py | 83 ++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 solutions/python/making-the-grade/2/loops.py diff --git a/solutions/python/making-the-grade/2/loops.py b/solutions/python/making-the-grade/2/loops.py new file mode 100644 index 0000000..afe3994 --- /dev/null +++ b/solutions/python/making-the-grade/2/loops.py @@ -0,0 +1,83 @@ +"""Functions for organizing and calculating student exam scores.""" + + +def round_scores(student_scores: list) -> list: + """ + Round all provided student scores. + + :param student_scores: list - float or int of student exam scores. + :return: list - student scores *rounded* to nearest integer value. + """ + return [round(score) for score in student_scores] + + +def count_failed_students(student_scores: list) -> int: + """ + Count the number of failing students out of the group provided. + + :param student_scores: list - containing int student scores. + :return: int - count of student scores at or below 40. + """ + return len([score for score in student_scores if score <= 40.0]) + + +def above_threshold(student_scores: list, + threshold: int) -> list: + """ + Filter out above threshold scores. + + Determine how many of the provided student scores were 'the best' + based on the provided threshold. + + :param student_scores: list - of integer scores. + :param threshold: int - threshold to cross to be the "best" score. + :return: list - of integer scores that are at or above the "best" threshold. + """ + return [score for score in student_scores if score >= threshold] + + +def letter_grades(highest: int) -> list: + """ + Create a list of grade thresholds based on the provided highest grade. + + :param highest: int - value of highest exam score. + :return: list - of lower threshold scores for each D-A letter grade interval. + For example, where the highest score is 100, and failing is <= 40, + The result would be [41, 56, 71, 86]: + + 41 <= "D" <= 55 + 56 <= "C" <= 70 + 71 <= "B" <= 85 + 86 <= "A" <= 100 + """ + interval = (highest - 40) // 4 + return [41 + i * interval for i in range(4)] + + +def student_ranking(student_scores: list, + student_names: list) -> list[str]: + """ + Organize the student's rank, name, and grade information in descending order. + + :param student_scores: list - of scores in descending order. + :param student_names: list - of string names by exam score in descending order. + :return: list - of strings in format [". : "]. + """ + return [f"{i}. {name}: {score}" for i, score, name in zip( + range(1, len(student_scores) + 1), student_scores, student_names)] + + +def perfect_score(student_info: list) -> list: + """ + Create a list that contains the name and grade of the first + student to make a perfect score on the exam. + + :param student_info: list - of [, ] lists. + :return: list - first `[, 100]` or `[]` if no + student score of 100 is found. + """ + for student, score in student_info: + if score == 100: + return [student, score] + + return [] From ace8baadf6684ce9f531e14f960408418ac07515 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 11 Aug 2025 19:57:23 -0700 Subject: [PATCH 29/75] Making the Grade --- making-the-grade/.exercism/config.json | 22 ++ making-the-grade/.exercism/metadata.json | 1 + making-the-grade/HELP.md | 130 +++++++++ making-the-grade/HINTS.md | 59 +++++ making-the-grade/README.md | 321 +++++++++++++++++++++++ making-the-grade/loops.py | 83 ++++++ making-the-grade/loops_test.py | 155 +++++++++++ 7 files changed, 771 insertions(+) create mode 100644 making-the-grade/.exercism/config.json create mode 100644 making-the-grade/.exercism/metadata.json create mode 100644 making-the-grade/HELP.md create mode 100644 making-the-grade/HINTS.md create mode 100644 making-the-grade/README.md create mode 100644 making-the-grade/loops.py create mode 100644 making-the-grade/loops_test.py diff --git a/making-the-grade/.exercism/config.json b/making-the-grade/.exercism/config.json new file mode 100644 index 0000000..d9edd46 --- /dev/null +++ b/making-the-grade/.exercism/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "mohanrajanr", + "BethanyG" + ], + "contributors": [ + "pranasziaukas" + ], + "files": { + "solution": [ + "loops.py" + ], + "test": [ + "loops_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "grep", + "blurb": "Learn about loops by grading and organizing your students exam scores." +} diff --git a/making-the-grade/.exercism/metadata.json b/making-the-grade/.exercism/metadata.json new file mode 100644 index 0000000..cd84997 --- /dev/null +++ b/making-the-grade/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"making-the-grade","id":"914830bc9f4d490ab3f718915d588c13","url":"https://exercism.org/tracks/python/exercises/making-the-grade","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/making-the-grade/HELP.md b/making-the-grade/HELP.md new file mode 100644 index 0000000..6ac515f --- /dev/null +++ b/making-the-grade/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit loops.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/making-the-grade/HINTS.md b/making-the-grade/HINTS.md new file mode 100644 index 0000000..79e03af --- /dev/null +++ b/making-the-grade/HINTS.md @@ -0,0 +1,59 @@ +# Hints + +## General + +- [`while`][while-loops] loops are used for _indefinite_ (uncounted) iteration +- [`for`][for-loops] loops are used for _definite_, (counted) iteration. +- The keywords [`break` and `continue`][control flow] help customize loop behavior. +- [`range(, stop, )`][range] can be used to generate a sequence for a loop counter. +- The built-in [`enumerate()`][enumerate] will return (``, ``) pairs to iterate over. + +Also being familiar with the following can help with completing the tasks: + +- [`lists`][list]: indexing, nested lists, [`.append`][append and pop], [`.pop()`][append and pop]. +- [`str`][str]: `str()` constructor, using the `+` to concatenate strings, optionally, [`f-strings`][f-strings]. + +## 1. Rounding Scores + +- `While` loops will continue to execute until their test condition evaluates to `False`. +- `.pop()` will remove and return the last item in a `list`. +- Empty lists evaluate to `False` (most empty objects in Python are "Falsy") + +## 2. Non-Passing Students + +- There's no need to declare `loop` counters or `index` counters when iterating through an object using a `for` loop. +- A results counter does need to be set up and _incremented_ -- you'll want to `return` the count of non-passing students when the loop terminates. + +## 3. The "Best" + +- There's no need to declare `loop` counters or `index` counters when iterating through an object using a `for` loop. +- Having an empty `list` to add the "best" marks to is helpful here. +- `.append()` can help add things to the results `list`. + +## 4. Calculating Letter Grades + +- These are _lower thresholds_. The _lower threshold_ for a "D" is a score of **41**, since an "F" is **<= 40**. +- [`range()`][range] can be helpful here to generate a sequence with the proper "F" -> "A" increments. +- [`round()`][round] without parameters should round off increments nicely. +- As with "the best" task, `.append()` could be useful here to append items from `range()` into a results `list`. + +## 5. Matching Names to Scores + +- [`enumerate()`][enumerate] could be helpful here. +- If both lists are the same length and sorted the same way, could you use the `index` from one to retrieve a `value` from the other? + +## 6. A "Perfect" Score + +- There may be or may not be a student with a score of 100, and you can't return `[]` without checking **all** scores. +- The [`control flow`][control flow] statements `continue` and `break` may be useful here to move past unwanted values. + +[append and pop]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists +[control flow]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops +[enumerate]: https://docs.python.org/3/library/functions.html#enumerate +[f-strings]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals +[for-loops]: https://docs.python.org/3/tutorial/controlflow.html#for-statements +[list]: https://docs.python.org/3/library/stdtypes.html#list +[range]: https://docs.python.org/3/tutorial/controlflow.html#the-range-function +[round]: https://docs.python.org/3/library/functions.html#round +[str]: https://docs.python.org/3/library/stdtypes.html#str +[while-loops]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement \ No newline at end of file diff --git a/making-the-grade/README.md b/making-the-grade/README.md new file mode 100644 index 0000000..fe60e12 --- /dev/null +++ b/making-the-grade/README.md @@ -0,0 +1,321 @@ +# Making the Grade + +Welcome to Making the Grade on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +Python has two looping constructs. +`while` loops for _indefinite_ (uncounted) iteration and `for` loops for _definite_, (counted) iteration. +The keywords `break`, `continue`, and `else` help customize loop behavior. +`range()` and `enumerate()` help with loop counting and indexing. + + +## While + +[`while`][while statement] loops will continue to execute as long as the `loop expression` or "test" evaluates to `True` in a [`boolean context`][truth value testing], terminating when it evaluates to `False`: + +```python + +# Lists are considered "truthy" in a boolean context if they +# contain one or more values, and "falsy" if they are empty. + +>>> placeholders = ["spam", "ham", "eggs", "green_spam", "green_ham", "green_eggs"] + +>>> while placeholders: +... print(placeholders.pop(0)) +... +'spam' +'ham' +'eggs' +'green_spam' +'green_ham' +'green_eggs' +``` + + +## For + +The basic [`for`][for statement] `loop` in Python is better described as a _`for each`_ which cycles through the values of any [iterable object][iterable], terminating when there are no values returned from calling [`next()`][next built-in]: + +```python + +>>> word_list = ["bird", "chicken", "barrel", "bongo"] + +>>> for word in word_list: +... if word.startswith("b"): +... print(f"{word.title()} starts with a B.") +... else: +... print(f"{word.title()} doesn't start with a B.") +... +'Bird starts with a B.' +'Chicken doesn\'t start with a B.' +'Barrel starts with a B.' +'Bongo starts with a B.' +``` + + +## Sequence Object range() + +When there isn't a specific `iterable` given, the special [`range()`][range] sequence is used as a loop counter. +`range()` requires an `int` before which to `stop` the sequence, and can optionally take `start` and `step` parameters. +If no `start` number is provided, the sequence will begin with 0. +`range()` objects are **lazy** (_values are generated on request_), support all [common sequence operations][common sequence operations], and take up a fixed amount of memory, no matter how long the sequence specified. + +```python +# Here we use range to produce some numbers, rather than creating a list of them in memory. +# The values will start with 1 and stop *before* 7 + +>>> for number in range(1, 7): +... if number % 2 == 0: +... print(f"{number} is even.") +... else: +... print(f"{number} is odd.") +'1 is odd.' +'2 is even.' +'3 is odd.' +'4 is even.' +'5 is odd.' +'6 is even.' + +# range() can also take a *step* parameter. +# Here we use range to produce only the "odd" numbers, starting with 3 and stopping *before* 15. + +>>> for number in range(3, 15, 2): +... if number % 2 == 0: +... print(f"{number} is even.") +... else: +... print(f"{number} is odd.") +... +'3 is odd.' +'5 is odd.' +'7 is odd.' +'9 is odd.' +'11 is odd.' +'13 is odd.' +``` + + +## Values and Indexes with enumerate() + +If both values and their indexes are needed, the built-in [`enumerate()`][enumerate] will return (`index`, `value`) pairs: + +```python + +>>> word_list = ["bird", "chicken", "barrel", "apple"] + +# *index* and *word* are the loop variables. +# Loop variables can be any valid python name. + +>>> for index, word in enumerate(word_list): +... if word.startswith("b"): +... print(f"{word.title()} (at index {index}) starts with a B.") +... else: +... print(f"{word.title()} (at index {index}) doesn't start with a B.") +... +'Bird (at index 0) starts with a B.' +'Chicken (at index 1) doesn\'t start with a B.' +'Barrel (at index 2) starts with a B.' +'Apple (at index 3) doesn\'t start with a B.' + + +# The same method can be used as a "lookup" for pairing items between two lists. +# Of course, if the lengths or indexes don't line up, this doesn't work. + +>>> word_list = ["cat", "chicken", "barrel", "apple", "spinach"] +>>> category_list = ["mammal", "bird", "thing", "fruit", "vegetable"] + +>>> for index, word in enumerate(word_list): +... print(f"{word.title()} is in category: {category_list[index]}.") +... +'Cat is in category: mammal.' +'Chicken is in category: bird.' +'Barrel is in category: thing.' +'Apple is in category: fruit.' +'Spinach is in category: vegetable.' +``` + + +## Altering Loop Behavior + +The [`continue`][continue statement] keyword can be used to skip forward to the next iteration cycle: + +```python +word_list = ["bird", "chicken", "barrel", "bongo", "sliver", "apple", "bear"] + +# This will skip *bird*, at index 0 +for index, word in enumerate(word_list): + if index == 0: + continue + if word.startswith("b"): + print(f"{word.title()} (at index {index}) starts with a b.") + +'Barrel (at index 2) starts with a b.' +'Bongo (at index 3) starts with a b.' +'Bear (at index 6) starts with a b.' +``` + + +The [`break`][break statement] (_like in many C-related languages_) keyword can be used to stop the iteration and "break out" of the innermost enclosing `loop`: + +```python +>>> word_list = ["bird", "chicken", "barrel", "bongo", "sliver", "apple"] + +>>> for index, word in enumerate(word_list): +... if word.startswith("b"): +... print(f"{word.title()} (at index {index}) starts with a B.") +... elif word == "sliver": +... break +... else: +... print(f"{word.title()} doesn't start with a B.") +... print("loop broken.") +... +'Bird (at index 0) starts with a B.' +'Chicken doesn\'t start with a B.' +'Barrel (at index 2) starts with a B.' +'Bongo (at index 3) starts with a B.' +'loop broken.' +``` + +[break statement]: https://docs.python.org/3/reference/simple_stmts.html#the-break-statement +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[continue statement]: https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement +[enumerate]: https://docs.python.org/3/library/functions.html#enumerate +[for statement]: https://docs.python.org/3/reference/compound_stmts.html#for +[iterable]: https://docs.python.org/3/glossary.html#term-iterable +[next built-in]: https://docs.python.org/3/library/functions.html#next +[range]: https://docs.python.org/3/library/stdtypes.html#range +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing +[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement + +## Instructions + +You're a teaching assistant correcting student exams. +Keeping track of results manually is getting both tedious and mistake-prone. +You decide to make things a little more interesting by putting together some functions to count and calculate results for the class. + +## 1. Rounding Scores + +While you can give "partial credit" on exam questions, overall exam scores have to be `int`s. +So before you can do anything else with the class scores, you need to go through the grades and turn any `float` scores into `int`s. Lucky for you, Python has the built-in [`round()`][round] function you can use. + +Create the function `round_scores(student_scores)` that takes a `list` of `student_scores`. +This function should _consume_ the input `list` and `return` a new list with all the scores converted to `int`s. +The order of the scores in the resulting `list` is not important. + +```python +>>> student_scores = [90.33, 40.5, 55.44, 70.05, 30.55, 25.45, 80.45, 95.3, 38.7, 40.3] +>>> round_scores(student_scores) +... +[40, 39, 95, 80, 25, 31, 70, 55, 40, 90] +``` + +## 2. Non-Passing Students + +As you were grading the exam, you noticed some students weren't performing as well as you had hoped. +But you were distracted, and forgot to note exactly _how many_ students. + +Create the function `count_failed_students(student_scores)` that takes a `list` of `student_scores`. +This function should count up the number of students who don't have passing scores and return that count as an integer. +A student needs a score greater than **40** to achieve a passing grade on the exam. + +```python +>>> count_failed_students(student_scores=[90,40,55,70,30,25,80,95,38,40]) +5 +``` + +## 3. The "Best" + +The teacher you're assisting wants to find the group of students who've performed "the best" on this exam. +What qualifies as "the best" fluctuates, so you need to find the student scores that are **greater than or equal to** the current threshold. + +Create the function `above_threshold(student_scores, threshold)` taking `student_scores` (a `list` of grades), and `threshold` (the "top score" threshold) as parameters. +This function should return a `list` of all scores that are `>=` to `threshold`. + +```python +>>> above_threshold(student_scores=[90,40,55,70,30,68,70,75,83,96], threshold=75) +[90,75,83,96] +``` + +## 4. Calculating Letter Grades + +The teacher you are assisting likes to assign letter grades as well as numeric scores. +Since students rarely score 100 on an exam, the "letter grade" lower thresholds are calculated based on the highest score achieved, and increment evenly between the high score and the failing threshold of **<= 40**. + +Create the function `letter_grades(highest)` that takes the "highest" score on the exam as an argument, and returns a `list` of lower score thresholds for each "American style" grade interval: `["D", "C", "B", "A"]`. + + +```python +"""Where the highest score is 100, and failing is <= 40. + "F" <= 40 + 41 <= "D" <= 55 + 56 <= "C" <= 70 + 71 <= "B" <= 85 + 86 <= "A" <= 100 +""" + +>>> letter_grades(highest=100) +[41, 56, 71, 86] + + +"""Where the highest score is 88, and failing is <= 40. + "F" <= 40 + 41 <= "D" <= 52 + 53 <= "C" <= 64 + 65 <= "B" <= 76 + 77 <= "A" <= 88 +""" + +>>> letter_grades(highest=88) +[41, 53, 65, 77] +``` + +## 5. Matching Names to Scores + +You have a list of exam scores in descending order, and another list of student names also sorted in descending order by their exam scores. +You would like to match each student name with their exam score and print out an overall class ranking. + +Create the function `student_ranking(student_scores, student_names)` with parameters `student_scores` and `student_names`. +Match each student name on the student_names `list` with their score from the student_scores `list`. +You can assume each argument `list` will be sorted from highest score(er) to lowest score(er). +The function should return a `list` of strings with the format `. : `. + +```python +>>> student_scores = [100, 99, 90, 84, 66, 53, 47] +>>> student_names = ['Joci', 'Sara','Kora','Jan','John','Bern', 'Fred'] +>>> student_ranking(student_scores, student_names) +... +['1. Joci: 100', '2. Sara: 99', '3. Kora: 90', '4. Jan: 84', '5. John: 66', '6. Bern: 53', '7. Fred: 47'] +``` + +## 6. A "Perfect" Score + +Although a "perfect" score of 100 is rare on an exam, it is interesting to know if at least one student has achieved it. + +Create the function `perfect_score(student_info)` with parameter `student_info`. +`student_info` is a `list` of lists containing the name and score of each student: `[["Charles", 90], ["Tony", 80]]`. +The function should `return` _the first_ `[, ]` pair of the student who scored 100 on the exam. + +If no 100 scores are found in `student_info`, an empty list `[]` should be returned. + +```python +>>> perfect_score(student_info=[["Charles", 90], ["Tony", 80], ["Alex", 100]]) +["Alex", 100] + +>>> perfect_score(student_info=[["Charles", 90], ["Tony", 80]]) +[] +``` + +[round]: https://docs.python.org/3/library/functions.html#round + +## Source + +### Created by + +- @mohanrajanr +- @BethanyG + +### Contributed to by + +- @pranasziaukas \ No newline at end of file diff --git a/making-the-grade/loops.py b/making-the-grade/loops.py new file mode 100644 index 0000000..afe3994 --- /dev/null +++ b/making-the-grade/loops.py @@ -0,0 +1,83 @@ +"""Functions for organizing and calculating student exam scores.""" + + +def round_scores(student_scores: list) -> list: + """ + Round all provided student scores. + + :param student_scores: list - float or int of student exam scores. + :return: list - student scores *rounded* to nearest integer value. + """ + return [round(score) for score in student_scores] + + +def count_failed_students(student_scores: list) -> int: + """ + Count the number of failing students out of the group provided. + + :param student_scores: list - containing int student scores. + :return: int - count of student scores at or below 40. + """ + return len([score for score in student_scores if score <= 40.0]) + + +def above_threshold(student_scores: list, + threshold: int) -> list: + """ + Filter out above threshold scores. + + Determine how many of the provided student scores were 'the best' + based on the provided threshold. + + :param student_scores: list - of integer scores. + :param threshold: int - threshold to cross to be the "best" score. + :return: list - of integer scores that are at or above the "best" threshold. + """ + return [score for score in student_scores if score >= threshold] + + +def letter_grades(highest: int) -> list: + """ + Create a list of grade thresholds based on the provided highest grade. + + :param highest: int - value of highest exam score. + :return: list - of lower threshold scores for each D-A letter grade interval. + For example, where the highest score is 100, and failing is <= 40, + The result would be [41, 56, 71, 86]: + + 41 <= "D" <= 55 + 56 <= "C" <= 70 + 71 <= "B" <= 85 + 86 <= "A" <= 100 + """ + interval = (highest - 40) // 4 + return [41 + i * interval for i in range(4)] + + +def student_ranking(student_scores: list, + student_names: list) -> list[str]: + """ + Organize the student's rank, name, and grade information in descending order. + + :param student_scores: list - of scores in descending order. + :param student_names: list - of string names by exam score in descending order. + :return: list - of strings in format [". : "]. + """ + return [f"{i}. {name}: {score}" for i, score, name in zip( + range(1, len(student_scores) + 1), student_scores, student_names)] + + +def perfect_score(student_info: list) -> list: + """ + Create a list that contains the name and grade of the first + student to make a perfect score on the exam. + + :param student_info: list - of [, ] lists. + :return: list - first `[, 100]` or `[]` if no + student score of 100 is found. + """ + for student, score in student_info: + if score == 100: + return [student, score] + + return [] diff --git a/making-the-grade/loops_test.py b/making-the-grade/loops_test.py new file mode 100644 index 0000000..598e2b0 --- /dev/null +++ b/making-the-grade/loops_test.py @@ -0,0 +1,155 @@ +import unittest +import pytest + +from loops import ( + round_scores, + count_failed_students, + above_threshold, + letter_grades, + student_ranking, + perfect_score) + + +class MakingTheGradeTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_round_scores(self): + + # Because we the input list can be mutated, the test data has been created + # as tuples, which we then convert to a list when the test runs. + # this makes accurate error messages easier to create. + test_data = [tuple(), + (.5,), + (1.5,), + (90.33, 40.5, 55.44, 70.05, 30.55, 25.45, 80.45, 95.3, 38.7, 40.3), + (50, 36.03, 76.92, 40.7, 43, 78.29, 63.58, 91, 28.6, 88.0)] + result_data = [[], + [0], + [2], + [90, 40, 55, 70, 31, 25, 80, 95, 39, 40], + [50, 36, 77, 41, 43, 78, 64, 91, 29, 88]] + + for variant, (student_scores, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', student_scores=student_scores, expected=expected): + + # Because the test_input is a tuple, it has to be converted to a list for the function call. + actual_result = round_scores(list(student_scores)) + error_message = (f'Called round_scores({list(student_scores)}). ' + f'The function returned {sorted(actual_result)} after sorting, but ' + f'the tests expected {sorted(expected)} after sorting. ' + f'One or more scores were rounded incorrectly.') + + # everything is sorted for easier comparison. + self.assertEqual(sorted(actual_result), sorted(expected), msg=error_message) + + @pytest.mark.task(taskno=2) + def test_count_failed_students(self): + test_data = [[89, 85, 42, 57, 90, 100, 95, 48, 70, 96], + [40, 40, 35, 70, 30, 41, 90]] + result_data = [0,4] + + for variant, (student_scores, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + student_scores=student_scores, + expected=expected): + + actual_result = count_failed_students(student_scores) + error_message = (f'Called count_failed_students({student_scores}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'number of students who failed.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_above_threshold(self): + test_data = [([40, 39, 95, 80, 25, 31, 70, 55, 40, 90], 98), + ([88, 29, 91, 64, 78, 43, 41, 77, 36, 50], 80), + ([100, 89], 100), + ([88, 29, 91, 64, 78, 43, 41, 77, 36, 50], 78), + ([], 80)] + + result_data = [[], + [88, 91], + [100], + [88, 91, 78], + []] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = above_threshold(*params) + error_message = (f'Called above_threshold{params}. ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'scores that are above the threshold.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_letter_grades(self): + test_data = [100, 97, 85, 92, 81] + + result_data = [[41, 56, 71, 86], + [41, 55, 69, 83], + [41, 52, 63, 74], + [41, 54, 67, 80], + [41, 51, 61, 71]] + + for variant, (highest, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', highest=highest, expected=expected): + actual_result = letter_grades(highest) + error_message = (f'Called letter_grades({highest}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'letter grade cutoffs.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=5) + def test_student_ranking(self): + test_data = [([82], ['Betty']), + ([88, 73], ['Paul', 'Ernest']), + ([100, 98, 92, 86, 70, 68, 67, 60], + ['Rui', 'Betty', 'Joci', 'Yoshi', 'Kora', 'Bern', 'Jan', 'Rose'])] + + result_data = [['1. Betty: 82'], + ['1. Paul: 88', '2. Ernest: 73'], + ['1. Rui: 100', '2. Betty: 98', '3. Joci: 92', '4. Yoshi: 86', + '5. Kora: 70', '6. Bern: 68', '7. Jan: 67', '8. Rose: 60']] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = student_ranking(*params) + error_message = (f'Called student_ranking{params}. ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'student rankings.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=6) + def test_perfect_score(self): + test_data = [ + [['Joci', 100], ['Vlad', 100], ['Raiana', 100], ['Alessandro', 100]], + [['Jill', 30], ['Paul', 73]], + [], + [['Rui', 60], ['Joci', 58], ['Sara', 91], ['Kora', 93], ['Alex', 42], + ['Jan', 81], ['Lilliana', 40], ['John', 60], ['Bern', 28], ['Vlad', 55]], + + [['Yoshi', 52], ['Jan', 86], ['Raiana', 100], ['Betty', 60], + ['Joci', 100], ['Kora', 81], ['Bern', 41], ['Rose', 94]] + ] + + + result_data = [['Joci', 100],[], [], [], ['Raiana', 100]] + + for variant, (student_info, expected) in enumerate(zip(test_data, result_data), start=1): + + with self.subTest(f'variation #{variant}', student_info=student_info, expected=expected): + actual_result = perfect_score(student_info) + error_message = (f'Called perfect_score({student_info}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'first "perfect" score.') + + self.assertEqual(actual_result, expected, msg=error_message) From 0c3598663c9c33bf7bf0bf16b7e2090fb5e99595 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:04:19 +0000 Subject: [PATCH 30/75] [Sync Iteration] python/square-root/1 --- solutions/python/square-root/1/square_root.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 solutions/python/square-root/1/square_root.py diff --git a/solutions/python/square-root/1/square_root.py b/solutions/python/square-root/1/square_root.py new file mode 100644 index 0000000..d830970 --- /dev/null +++ b/solutions/python/square-root/1/square_root.py @@ -0,0 +1,16 @@ +"""Solution for Square Root.""" + +from math import sqrt + + +def square_root(number: float) -> int: + """ + Calculate square root. + + :param number: any number + :type number: float + :return: square root of any number, + only natural numbers (positive integers) as solutions. + :rtype: int + """ + return int(sqrt(number)) From 5b7164d0a4290fe4e3e7eca1b0efc8b4247253ec Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 11 Aug 2025 20:05:56 -0700 Subject: [PATCH 31/75] Square Root --- square-root/.exercism/config.json | 21 +++++ square-root/.exercism/metadata.json | 1 + square-root/HELP.md | 130 ++++++++++++++++++++++++++++ square-root/README.md | 60 +++++++++++++ square-root/square_root.py | 16 ++++ square-root/square_root_test.py | 29 +++++++ 6 files changed, 257 insertions(+) create mode 100644 square-root/.exercism/config.json create mode 100644 square-root/.exercism/metadata.json create mode 100644 square-root/HELP.md create mode 100644 square-root/README.md create mode 100644 square-root/square_root.py create mode 100644 square-root/square_root_test.py diff --git a/square-root/.exercism/config.json b/square-root/.exercism/config.json new file mode 100644 index 0000000..b1b9630 --- /dev/null +++ b/square-root/.exercism/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "meatball133", + "Bethanyg" + ], + "contributors": [], + "files": { + "solution": [ + "square_root.py" + ], + "test": [ + "square_root_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Given a natural radicand, return its square root.", + "source": "wolf99", + "source_url": "https://github.com/exercism/problem-specifications/pull/1582" +} diff --git a/square-root/.exercism/metadata.json b/square-root/.exercism/metadata.json new file mode 100644 index 0000000..b4be30a --- /dev/null +++ b/square-root/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"square-root","id":"15f9c335485f4af59694fd86171e39d9","url":"https://exercism.org/tracks/python/exercises/square-root","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/square-root/HELP.md b/square-root/HELP.md new file mode 100644 index 0000000..1659db8 --- /dev/null +++ b/square-root/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit square_root.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/square-root/README.md b/square-root/README.md new file mode 100644 index 0000000..f65bb95 --- /dev/null +++ b/square-root/README.md @@ -0,0 +1,60 @@ +# Square Root + +Welcome to Square Root on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Introduction + +We are launching a deep space exploration rocket and we need a way to make sure the navigation system stays on target. + +As the first step in our calculation, we take a target number and find its square root (that is, the number that when multiplied by itself equals the target number). + +The journey will be very long. +To make the batteries last as long as possible, we had to make our rocket's onboard computer very power efficient. +Unfortunately that means that we can't rely on fancy math libraries and functions, as they use more power. +Instead we want to implement our own square root calculation. + +## Instructions + +Your task is to calculate the square root of a given number. + +- Try to avoid using the pre-existing math libraries of your language. +- As input you'll be given a positive whole number, i.e. 1, 2, 3, 4… +- You are only required to handle cases where the result is a positive whole number. + +Some potential approaches: + +- Linear or binary search for a number that gives the input number when squared. +- Successive approximation using Newton's or Heron's method. +- Calculating one digit at a time or one bit at a time. + +You can check out the Wikipedia pages on [integer square root][integer-square-root] and [methods of computing square roots][computing-square-roots] to help with choosing a method of calculation. + +[integer-square-root]: https://en.wikipedia.org/wiki/Integer_square_root +[computing-square-roots]: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots + +## How this Exercise is Structured in Python + + +Python offers a wealth of mathematical functions in the form of the [math module][math-module] and built-ins such as the exponentiation operator `**`, [`pow()`][pow] and [`sum()`][sum]. +However, we'd like you to consider the challenge of solving this exercise without those built-ins or modules. + +While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of having only [natural numbers][nautral-number] (positive integers) as solutions. +It is possible to compute the square root of any natural number using only natural numbers in the computation. + + +[math-module]: https://docs.python.org/3/library/math.html +[pow]: https://docs.python.org/3/library/functions.html#pow +[sum]: https://docs.python.org/3/library/functions.html#sum +[nautral-number]: https://en.wikipedia.org/wiki/Natural_number + +## Source + +### Created by + +- @meatball133 +- @Bethanyg + +### Based on + +wolf99 - https://github.com/exercism/problem-specifications/pull/1582 \ No newline at end of file diff --git a/square-root/square_root.py b/square-root/square_root.py new file mode 100644 index 0000000..d830970 --- /dev/null +++ b/square-root/square_root.py @@ -0,0 +1,16 @@ +"""Solution for Square Root.""" + +from math import sqrt + + +def square_root(number: float) -> int: + """ + Calculate square root. + + :param number: any number + :type number: float + :return: square root of any number, + only natural numbers (positive integers) as solutions. + :rtype: int + """ + return int(sqrt(number)) diff --git a/square-root/square_root_test.py b/square-root/square_root_test.py new file mode 100644 index 0000000..8f94940 --- /dev/null +++ b/square-root/square_root_test.py @@ -0,0 +1,29 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json +# File last updated on 2023-07-19 + +import unittest + +from square_root import ( + square_root, +) + + +class SquareRootTest(unittest.TestCase): + def test_root_of_1(self): + self.assertEqual(square_root(1), 1) + + def test_root_of_4(self): + self.assertEqual(square_root(4), 2) + + def test_root_of_25(self): + self.assertEqual(square_root(25), 5) + + def test_root_of_81(self): + self.assertEqual(square_root(81), 9) + + def test_root_of_196(self): + self.assertEqual(square_root(196), 14) + + def test_root_of_65025(self): + self.assertEqual(square_root(65025), 255) From 405a6aa00d067525511ee6caaf495bce401ef4a9 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 05:10:21 +0000 Subject: [PATCH 32/75] [Sync Iteration] javascript/hello-world/1 --- solutions/javascript/hello-world/1/hello-world.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 solutions/javascript/hello-world/1/hello-world.js diff --git a/solutions/javascript/hello-world/1/hello-world.js b/solutions/javascript/hello-world/1/hello-world.js new file mode 100644 index 0000000..5fec175 --- /dev/null +++ b/solutions/javascript/hello-world/1/hello-world.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Hello World' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export function hello() { + return 'Hello, World!'; +} From 17b887287e9b47adaa201227439a6fda74b879b6 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 05:28:19 +0000 Subject: [PATCH 33/75] [Sync Iteration] python/leap/1 --- solutions/python/leap/1/leap.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 solutions/python/leap/1/leap.py diff --git a/solutions/python/leap/1/leap.py b/solutions/python/leap/1/leap.py new file mode 100644 index 0000000..dd1abb3 --- /dev/null +++ b/solutions/python/leap/1/leap.py @@ -0,0 +1,32 @@ +"""Solution for Leap exercise.""" + + +def leap_year(year: int) -> bool: + """ + Determine whether a given year is a leap year. + + A leap year (in the Gregorian calendar) occurs: + - In every year that is evenly divisible by 4. + - Unless the year is evenly divisible by 100, + in which case it's only a leap year if the + year is also evenly divisible by 400. + + Some examples: + + - 1997 was not a leap year as it's not divisible by 4. + - 1900 was not a leap year as it's not divisible by 400. + - 2000 was a leap year! + + :param year: any year + :type year: int + :return: whether a given year is a leap year + :rtype: bool + """ + if year % 4 == 0: + if year % 100 == 0: + if year % 400 == 0: + return True + return False + return True + + return False From 457f15fc35050c3448527a2e8c45c8d3725025c1 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 05:32:59 +0000 Subject: [PATCH 34/75] [Sync Iteration] python/leap/2 --- solutions/python/leap/2/leap.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 solutions/python/leap/2/leap.py diff --git a/solutions/python/leap/2/leap.py b/solutions/python/leap/2/leap.py new file mode 100644 index 0000000..2930657 --- /dev/null +++ b/solutions/python/leap/2/leap.py @@ -0,0 +1,25 @@ +"""Solution for Leap exercise.""" + + +def leap_year(year: int) -> bool: + """ + Determine whether a given year is a leap year. + + A leap year (in the Gregorian calendar) occurs: + - In every year that is evenly divisible by 4. + - Unless the year is evenly divisible by 100, + in which case it's only a leap year if the + year is also evenly divisible by 400. + + Some examples: + + - 1997 was not a leap year as it's not divisible by 4. + - 1900 was not a leap year as it's not divisible by 400. + - 2000 was a leap year! + + :param year: any year + :type year: int + :return: whether a given year is a leap year + :rtype: bool + """ + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) From fcdb1b975bf4c0fcc013ee4960baa36366d35788 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 11 Aug 2025 22:33:17 -0700 Subject: [PATCH 35/75] Leap --- leap/.exercism/config.json | 38 ++++++++++ leap/.exercism/metadata.json | 1 + leap/HELP.md | 130 +++++++++++++++++++++++++++++++++++ leap/README.md | 53 ++++++++++++++ leap/leap.py | 25 +++++++ leap/leap_test.py | 38 ++++++++++ 6 files changed, 285 insertions(+) create mode 100644 leap/.exercism/config.json create mode 100644 leap/.exercism/metadata.json create mode 100644 leap/HELP.md create mode 100644 leap/README.md create mode 100644 leap/leap.py create mode 100644 leap/leap_test.py diff --git a/leap/.exercism/config.json b/leap/.exercism/config.json new file mode 100644 index 0000000..2e838e9 --- /dev/null +++ b/leap/.exercism/config.json @@ -0,0 +1,38 @@ +{ + "authors": [], + "contributors": [ + "AnAccountForReportingBugs", + "behrtam", + "BethanyG", + "betojulio", + "cmccandless", + "cruxicheiros", + "Dog", + "fluxusfrequency", + "iandexter", + "ikhadykin", + "kytrinyx", + "lowks", + "N-Parsons", + "olufotebig", + "pheanex", + "sambryant4", + "sjakobi", + "tqa236", + "yawpitch" + ], + "files": { + "solution": [ + "leap.py" + ], + "test": [ + "leap_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://web.archive.org/web/20240907033714/https://coderanch.com/t/718816/Leap" +} diff --git a/leap/.exercism/metadata.json b/leap/.exercism/metadata.json new file mode 100644 index 0000000..92bd007 --- /dev/null +++ b/leap/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"leap","id":"552c24d202a746c0a8ee434d79220370","url":"https://exercism.org/tracks/python/exercises/leap","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/leap/HELP.md b/leap/HELP.md new file mode 100644 index 0000000..13ed4ec --- /dev/null +++ b/leap/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit leap.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/leap/README.md b/leap/README.md new file mode 100644 index 0000000..73840d5 --- /dev/null +++ b/leap/README.md @@ -0,0 +1,53 @@ +# Leap + +Welcome to Leap on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Introduction + +A leap year (in the Gregorian calendar) occurs: + +- In every year that is evenly divisible by 4. +- Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. + +Some examples: + +- 1997 was not a leap year as it's not divisible by 4. +- 1900 was not a leap year as it's not divisible by 400. +- 2000 was a leap year! + +~~~~exercism/note +For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE). +~~~~ + +## Instructions + +Your task is to determine whether a given year is a leap year. + +## Source + +### Contributed to by + +- @AnAccountForReportingBugs +- @behrtam +- @BethanyG +- @betojulio +- @cmccandless +- @cruxicheiros +- @Dog +- @fluxusfrequency +- @iandexter +- @ikhadykin +- @kytrinyx +- @lowks +- @N-Parsons +- @olufotebig +- @pheanex +- @sambryant4 +- @sjakobi +- @tqa236 +- @yawpitch + +### Based on + +CodeRanch Cattle Drive, Assignment 3 - https://web.archive.org/web/20240907033714/https://coderanch.com/t/718816/Leap \ No newline at end of file diff --git a/leap/leap.py b/leap/leap.py new file mode 100644 index 0000000..2930657 --- /dev/null +++ b/leap/leap.py @@ -0,0 +1,25 @@ +"""Solution for Leap exercise.""" + + +def leap_year(year: int) -> bool: + """ + Determine whether a given year is a leap year. + + A leap year (in the Gregorian calendar) occurs: + - In every year that is evenly divisible by 4. + - Unless the year is evenly divisible by 100, + in which case it's only a leap year if the + year is also evenly divisible by 400. + + Some examples: + + - 1997 was not a leap year as it's not divisible by 4. + - 1900 was not a leap year as it's not divisible by 400. + - 2000 was a leap year! + + :param year: any year + :type year: int + :return: whether a given year is a leap year + :rtype: bool + """ + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) diff --git a/leap/leap_test.py b/leap/leap_test.py new file mode 100644 index 0000000..6a1d732 --- /dev/null +++ b/leap/leap_test.py @@ -0,0 +1,38 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json +# File last updated on 2023-07-19 + +import unittest + +from leap import ( + leap_year, +) + + +class LeapTest(unittest.TestCase): + def test_year_not_divisible_by_4_in_common_year(self): + self.assertIs(leap_year(2015), False) + + def test_year_divisible_by_2_not_divisible_by_4_in_common_year(self): + self.assertIs(leap_year(1970), False) + + def test_year_divisible_by_4_not_divisible_by_100_in_leap_year(self): + self.assertIs(leap_year(1996), True) + + def test_year_divisible_by_4_and_5_is_still_a_leap_year(self): + self.assertIs(leap_year(1960), True) + + def test_year_divisible_by_100_not_divisible_by_400_in_common_year(self): + self.assertIs(leap_year(2100), False) + + def test_year_divisible_by_100_but_not_by_3_is_still_not_a_leap_year(self): + self.assertIs(leap_year(1900), False) + + def test_year_divisible_by_400_is_leap_year(self): + self.assertIs(leap_year(2000), True) + + def test_year_divisible_by_400_but_not_by_125_is_still_a_leap_year(self): + self.assertIs(leap_year(2400), True) + + def test_year_divisible_by_200_not_divisible_by_400_in_common_year(self): + self.assertIs(leap_year(1800), False) From 2a2d60a3da7762d67ba4a7ae5130dce907ec1a28 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 20:10:06 -0700 Subject: [PATCH 36/75] Update exchange.py --- currency-exchange/exchange.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/currency-exchange/exchange.py b/currency-exchange/exchange.py index 2632aa2..e1871e5 100644 --- a/currency-exchange/exchange.py +++ b/currency-exchange/exchange.py @@ -37,7 +37,8 @@ def get_change(budget: float, def get_value_of_bills(denomination: float, number_of_bills: float) -> float: """ - Return only the total value of the bills (excluding fractional amounts) the booth would give back. + Return only the total value of the bills (excluding fractional amounts) + the booth would give back. The total you receive must be divisible by the value of one "bill" or unit, which can leave behind a fraction or remainder. @@ -79,7 +80,8 @@ def exchangeable_value(budget: float, spread: int, denomination: int) -> int: """ - Return the maximum value of the new currency after calculating the *exchange rate* plus the *spread*. + Return the maximum value of the new currency after calculating + the *exchange rate* plus the *spread*. :param budget: float - the amount of your money you are planning to exchange. :param exchange_rate: float - the unit value of the foreign currency. From d86459d3af1000839c96095c293bf177dd714171 Mon Sep 17 00:00:00 2001 From: Egor Kostan` <20955183+ikostan@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:25:51 -0700 Subject: [PATCH 37/75] Create pylint.yml --- .github/workflows/pylint.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/pylint.yml diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..c73e032 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,23 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint $(git ls-files '*.py') From 7fb88fd23d61e59ffb0cc58594e4c0581d2a26c0 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 20:28:28 -0700 Subject: [PATCH 38/75] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index c73e032..7aebbcf 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.10", "3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} From c5a9f4cecc4acd61b6e6f7eb2f951a9304adffe4 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 21:37:23 -0700 Subject: [PATCH 39/75] LINTING FIXES --- .github/workflows/pylint.yml | 3 +- .pylintrc | 2 + armstrong-numbers/armstrong_numbers_test.py | 61 ++++++ black-jack/black_jack.py | 41 ++-- black-jack/black_jack_test.py | 187 +++++++++--------- card-games/lists.py | 2 +- card-games/lists_test.py | 28 ++- .../list_methods_test.py | 56 ++++-- collatz-conjecture/collatz_conjecture.py | 1 + currency-exchange/exchange.py | 9 +- currency-exchange/exchange_test.py | 3 +- guidos-gorgeous-lasagna/lasagna.py | 4 +- guidos-gorgeous-lasagna/lasagna_test.py | 59 ++++-- hello-world/hello_world_test.py | 5 +- little-sisters-vocab/strings_test.py | 41 ++-- making-the-grade/loops.py | 6 +- making-the-grade/loops_test.py | 35 +++- meltdown-mitigation/conditionals.py | 3 +- meltdown-mitigation/conditionals_test.py | 28 ++- requirements.txt | Bin 202 -> 470 bytes .../python/currency-exchange/1/exchange.py | 12 +- .../guidos-gorgeous-lasagna/1/lasagna.py | 3 +- 22 files changed, 388 insertions(+), 201 deletions(-) create mode 100644 .pylintrc diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 7aebbcf..bf97b7b 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -18,6 +18,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pylint + pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint $(git ls-files '*.py') + python -m pylint --verbose $(Get-ChildItem -Recurse -Filter *.py | Where-Object { $_.FullName -notmatch '\\\.venv\\' -and $_.FullName -notmatch '\\venv\\' } | ForEach-Object { $_.FullName }) --rcfile=.pylintrc \ No newline at end of file diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..fcbba09 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +[MASTER] +ignore=.venv diff --git a/armstrong-numbers/armstrong_numbers_test.py b/armstrong-numbers/armstrong_numbers_test.py index 4024766..2deb3d0 100644 --- a/armstrong-numbers/armstrong_numbers_test.py +++ b/armstrong-numbers/armstrong_numbers_test.py @@ -1,3 +1,5 @@ +"""Armstrong Numbers Test Suite.""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json # File last updated on 2023-07-20 @@ -10,29 +12,88 @@ class ArmstrongNumbersTest(unittest.TestCase): + """Armstrong Numbers Test.""" + def test_zero_is_an_armstrong_number(self): + """ + Test that zero is correctly identified as an Armstrong number. + + This test verifies that the function correctly determines that 0 + is an Armstrong number, as 0^1 == 0. + + :returns: None + :rtype: NoneType + """ self.assertIs(is_armstrong_number(0), True) def test_single_digit_numbers_are_armstrong_numbers(self): + """ + Test that all single digit numbers are Armstrong numbers. + + :returns: None. Asserts that a single digit number (e.g., 5) is an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(5), True) def test_there_are_no_two_digit_armstrong_numbers(self): + """ + Test that no two-digit numbers are Armstrong numbers. + + :returns: None. Asserts that a two-digit number (e.g., 10) is not an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(10), False) def test_three_digit_number_that_is_an_armstrong_number(self): + """ + Test that 153 is correctly identified as an Armstrong number. + + :returns: None. Asserts that 153 is an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(153), True) def test_three_digit_number_that_is_not_an_armstrong_number(self): + """ + Test that 100 is not identified as an Armstrong number. + + :returns: None. Asserts that 100 is not an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(100), False) def test_four_digit_number_that_is_an_armstrong_number(self): + """ + Test that 9474 is correctly identified as an Armstrong number. + + :returns: None. Asserts that 9474 is an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(9474), True) def test_four_digit_number_that_is_not_an_armstrong_number(self): + """ + Test that 9475 is not identified as an Armstrong number. + + :returns: None. Asserts that 9475 is not an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(9475), False) def test_seven_digit_number_that_is_an_armstrong_number(self): + """ + Test that 9926315 is correctly identified as an Armstrong number. + + :returns: None. Asserts that 9926315 is an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(9926315), True) def test_seven_digit_number_that_is_not_an_armstrong_number(self): + """ + Test that 9926314 is not identified as an Armstrong number. + + :returns: None. Asserts that 9926314 is not an Armstrong number. + :rtype: NoneType + """ self.assertIs(is_armstrong_number(9926314), False) diff --git a/black-jack/black_jack.py b/black-jack/black_jack.py index c252b88..fb9dec8 100644 --- a/black-jack/black_jack.py +++ b/black-jack/black_jack.py @@ -19,7 +19,8 @@ def value_of_card(card) -> int: """ if card in 'JKQ': return 10 - elif card == 'A': + + if card == 'A': return 1 return int(card) @@ -29,8 +30,10 @@ def higher_card(card_one, card_two) -> str | tuple[str, str]: """ Determine which card has a higher value in the hand. - :param card_one, card_two: str - cards dealt in hand. See below for values. - :return: str or tuple - resulting Tuple contains both cards if they are of equal value. + :param card_one: str - cards dealt in hand. See below for values. + :param card_two: str - cards dealt in hand. See below for values. + :return: str or tuple - resulting Tuple contains both cards if + they are of equal value. 1. 'J', 'Q', or 'K' (otherwise known as "face cards") = 10 2. 'A' (ace card) = 1 @@ -38,7 +41,8 @@ def higher_card(card_one, card_two) -> str | tuple[str, str]: """ if value_of_card(card_one) == value_of_card(card_two): return card_one, card_two - elif value_of_card(card_one) > value_of_card(card_two): + + if value_of_card(card_one) > value_of_card(card_two): return card_one return card_two @@ -56,11 +60,14 @@ def value_of_ace(card_one, card_two) -> int: 3. '2' - '10' = numerical value. """ total: int = value_of_card(card_one) + value_of_card(card_two) - # Hint: if we already have an ace in hand, then the value for the upcoming ace would be 1. + # Hint: if we already have an ace in hand, then the value for + # the upcoming ace would be 1. if card_one == 'A' or card_two == 'A': return 1 - # The value of the hand with the ace needs to be as high as possible without going over 21. - elif 21 - total >= 11: + # The value of the hand with the ace needs to be as high as + # possible without going over 21. + + if 21 - total >= 11: return 11 return 1 @@ -70,7 +77,10 @@ def is_blackjack(card_one, card_two) -> bool: """ Determine if the hand is a 'natural' or 'blackjack'. - :param card_one, card_two: str - card dealt. See below for values. + :param card_one: card dealt. See below for values. + :type card_one: str + :param card_two: card dealt. See below for values. + :type card_two: str :return: bool - is the hand is a blackjack (two cards worth 21). 1. 'J', 'Q', or 'K' (otherwise known as "face cards") = 10 @@ -81,7 +91,8 @@ def is_blackjack(card_one, card_two) -> bool: # as their first two cards, then the player has a score of 21. if card_one == 'A' and card_two in ('J', 'Q', 'K', '10'): return True - elif card_two == 'A' and card_one in ('J', 'Q', 'K', '10'): + + if card_two == 'A' and card_one in ('J', 'Q', 'K', '10'): return True return False @@ -91,8 +102,10 @@ def can_split_pairs(card_one, card_two) -> bool: """ Determine if a player can split their hand into two hands. - :param card_one, card_two: str - cards dealt. - :return: bool - can the hand be split into two pairs? (i.e. cards are of the same value). + :param card_one: str - cards dealt. + :param card_two: str - cards dealt. + :return: bool - can the hand be split into two pairs? + (i.e. cards are of the same value). """ if value_of_card(card_one) == value_of_card(card_two): return True @@ -104,7 +117,9 @@ def can_double_down(card_one, card_two) -> bool: """ Determine if a blackjack player can place a double down bet. - :param card_one, card_two: str - first and second cards in hand. - :return: bool - can the hand can be doubled down? (i.e. totals 9, 10 or 11 points). + :param card_one: str - first and second cards in hand. + :param card_two: str - first and second cards in hand. + :return: bool - can the hand can be doubled down? + (i.e. totals 9, 10 or 11 points). """ return 9 <= value_of_card(card_one) + value_of_card(card_two) <= 11 diff --git a/black-jack/black_jack_test.py b/black-jack/black_jack_test.py index 0962781..89c235d 100644 --- a/black-jack/black_jack_test.py +++ b/black-jack/black_jack_test.py @@ -1,114 +1,111 @@ +from parameterized import parameterized import unittest import pytest from black_jack import ( - value_of_card, - higher_card, - value_of_ace, - is_blackjack, - can_split_pairs, - can_double_down - ) + value_of_card, + higher_card, + value_of_ace, + is_blackjack, + can_split_pairs, + can_double_down +) class BlackJackTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_value_of_card(self): - test_data = [('2', 2), ('5', 5), ('8', 8), - ('A', 1), ('10', 10), ('J', 10), - ('Q', 10), ('K', 10)] - - for variant, (card, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', card=card, expected=expected): - actual_result = value_of_card(card) - error_msg = (f'Called value_of_card({card}). ' - f'The function returned {actual_result} as the value of the {card} card, ' - f'but the test expected {expected} as the {card} card value.') - - self.assertEqual(actual_result, expected, msg=error_msg) - + @parameterized.expand([ + ('2', 2), ('5', 5), ('8', 8), + ('A', 1), ('10', 10), ('J', 10), + ('Q', 10), ('K', 10), + ]) + def test_value_of_card(self, card, expected): + actual_result = value_of_card(card) + error_msg = (f'Called value_of_card({card}). ' + f'The function returned {actual_result} ' + f'as the value of ' + f'the {card} card, ' + f'but the test expected {expected} as the ' + f'{card} card value.') + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=2) - def test_higher_card(self): - test_data = [('A', 'A', ('A', 'A')), - ('10', 'J', ('10', 'J')), - ('3', 'A', '3'), - ('3', '6', '6'), - ('Q', '10', ('Q', '10')), - ('4', '4', ('4', '4')), - ('9', '10', '10'), - ('6', '9', '9'), - ('4', '8', '8')] - - for variant, (card_one, card_two, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, expected=expected): - actual_result = higher_card(card_one, card_two) - error_msg = (f'Called higher_card({card_one}, {card_two}). ' - f'The function returned {actual_result}, ' - f'but the test expected {expected} as the result for the cards {card_one, card_two}.') - - self.assertEqual(actual_result, expected, msg=error_msg) + @parameterized.expand([ + ('A', 'A', ('A', 'A')), + ('10', 'J', ('10', 'J')), + ('3', 'A', '3'), + ('3', '6', '6'), + ('Q', '10', ('Q', '10')), + ('4', '4', ('4', '4')), + ('9', '10', '10'), + ('6', '9', '9'), + ('4', '8', '8'), + ]) + def test_higher_card(self, card_one, card_two, expected): + actual_result = higher_card(card_one, card_two) + error_msg = (f'Called higher_card({card_one}, {card_two}). ' + f'The function returned {actual_result}, ' + f'but the test expected {expected} as the result for ' + f'the cards {card_one, card_two}.') + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=3) - def test_value_of_ace(self): - test_data = [('2', '3', 11), ('3', '6', 11), ('5', '2', 11), - ('8', '2', 11), ('5', '5', 11), ('Q', 'A', 1), - ('10', '2', 1), ('7', '8', 1), ('J', '9', 1), - ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1)] - - for variant, (card_one, card_two, ace_value) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, ace_value=ace_value): - actual_result = value_of_ace(card_one, card_two) - error_msg = (f'Called value_of_ace({card_one}, {card_two}). ' - f'The function returned {actual_result}, ' - f'but the test expected {ace_value} as the value of an ace card ' - f'when the hand includes {card_one, card_two}.') - - self.assertEqual(value_of_ace(card_one, card_two), ace_value, msg=error_msg) + @parameterized.expand([ + ('2', '3', 11), ('3', '6', 11), ('5', '2', 11), + ('8', '2', 11), ('5', '5', 11), ('Q', 'A', 1), + ('10', '2', 1), ('7', '8', 1), ('J', '9', 1), + ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1), + ]) + def test_value_of_ace(self, card_one, card_two, ace_value): + actual_result = value_of_ace(card_one, card_two) + error_msg = (f'Called value_of_ace({card_one}, {card_two}). ' + f'The function returned {actual_result}, ' + f'but the test expected {ace_value} ' + f'as the value of an ace card ' + f'when the hand includes {card_one, card_two}.') + self.assertEqual(actual_result, ace_value, msg=error_msg) @pytest.mark.task(taskno=4) - def test_is_blackjack(self): - test_data = [(('A', 'K'), True), (('10', 'A'), True), - (('10', '9'), False), (('A', 'A'), False), - (('4', '7'), False), (('9', '2'), False), - (('Q', 'K'), False)] - - for variant, (hand, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', hand=hand, expected=expected): - actual_result = is_blackjack(*hand) - error_msg = (f'Called is_blackjack({hand[0]}, {hand[1]}). ' - f'The function returned {actual_result}, ' - f'but hand {hand} {"is" if expected else "is not"} a blackjack.') - - self.assertEqual(actual_result, expected, msg=error_msg) + @parameterized.expand([ + (('A', 'K'), True), (('10', 'A'), True), + (('10', '9'), False), (('A', 'A'), False), + (('4', '7'), False), (('9', '2'), False), + (('Q', 'K'), False), + ]) + def test_is_blackjack(self, hand, expected): + actual_result = is_blackjack(*hand) + error_msg = (f'Called is_blackjack({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"is" if expected else "is not"} ' + f'a blackjack.') + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=5) - def test_can_split_pairs(self): - test_data = [(('Q', 'K'), True), (('6', '6'), True), - (('A', 'A'), True),(('10', 'A'), False), - (('10', '9'), False)] - - for variant, (hand, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', input=hand, expected=expected): - actual_result = can_split_pairs(*hand) - error_msg = (f'Called can_split_pairs({hand[0]}, {hand[1]}). ' - f'The function returned {actual_result}, ' - f'but hand {hand} {"can" if expected else "cannot"} be split into pairs.') - - self.assertEqual(actual_result, expected, msg=error_msg) + @parameterized.expand([ + (('Q', 'K'), True), (('6', '6'), True), + (('A', 'A'), True), (('10', 'A'), False), + (('10', '9'), False), + ]) + def test_can_split_pairs(self, hand, expected): + actual_result = can_split_pairs(*hand) + error_msg = (f'Called can_split_pairs({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} ' + f'{"can" if expected else "cannot"} ' + f'be split into pairs.') + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=6) - def test_can_double_down(self): - test_data = [(('A', '9'), True), (('K', 'A'), True), - (('4', '5'), True),(('A', 'A'), False), - (('10', '2'), False), (('10', '9'), False)] - - for variant, (hand, expected) in enumerate(test_data, 1): - with self.subTest(f'variation #{variant}', hand=hand, expected=expected): - actual_result = can_double_down(*hand) - error_msg = (f'Called can_double_down({hand[0]}, {hand[1]}). ' - f'The function returned {actual_result}, ' - f'but hand {hand} {"can" if expected else "cannot"} be doubled down.') - - self.assertEqual(actual_result, expected, msg=error_msg) + @parameterized.expand([ + (('A', '9'), True), (('K', 'A'), True), + (('4', '5'), True), (('A', 'A'), False), + (('10', '2'), False), (('10', '9'), False), + ]) + def test_can_double_down(self, hand, expected): + actual_result = can_double_down(*hand) + error_msg = (f'Called can_double_down({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"can" if expected else "cannot"} ' + f'be doubled down.') + self.assertEqual(actual_result, expected, msg=error_msg) \ No newline at end of file diff --git a/card-games/lists.py b/card-games/lists.py index 1fc2e68..55f76fc 100644 --- a/card-games/lists.py +++ b/card-games/lists.py @@ -56,7 +56,7 @@ def approx_average_is_average(hand: list[int]) -> bool: :return: bool - does one of the approximate averages equal the `true average`? """ avg: float = card_average(hand) - return (hand[0] + hand[-1]) / 2 == avg or hand[len(hand) // 2] == avg + return avg in ((hand[0] + hand[-1]) / 2, hand[len(hand) // 2]) def average_even_is_average_odd(hand: list[int]) -> bool: diff --git a/card-games/lists_test.py b/card-games/lists_test.py index e550112..0fe4e11 100644 --- a/card-games/lists_test.py +++ b/card-games/lists_test.py @@ -43,8 +43,13 @@ def test_concatenate_rounds(self): [27, 28, 29, 35, 36], [1, 2, 3, 4, 5, 6]] - for variant, ((rounds_1, rounds_2), expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', rounds_1=rounds_1, rounds_2=rounds_2, expected=expected): + for variant, ((rounds_1, rounds_2), expected) in enumerate( + zip(input_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + rounds_1=rounds_1, + rounds_2=rounds_2, + expected=expected): actual_result = concatenate_rounds(rounds_1, rounds_2) error_message = (f'Called concatenate_rounds({rounds_1}, {rounds_2}). ' f'The function returned {actual_result}, but the tests ' @@ -62,8 +67,13 @@ def test_list_contains_round(self): ([27, 28, 29, 35, 36], 29)] result_data = [False, False, False, True, True, True] - for variant, ((rounds, round_number), expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', rounds=rounds, round_number=round_number, expected=expected): + for variant, ((rounds, round_number), expected) in enumerate( + zip(input_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + rounds=rounds, + round_number=round_number, + expected=expected): actual_result = list_contains_round(rounds, round_number) error_message = (f'Called list_contains_round({rounds}, {round_number}). ' f'The function returned {actual_result}, but round {round_number} ' @@ -127,11 +137,15 @@ def test_maybe_double_last(self): input_data = [(1, 2, 11), (5, 9, 11), (5, 9, 10), (1, 2, 3), (1, 11, 8)] result_data = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3], [1, 11, 8]] - for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', hand=list(hand), expected=expected): + for variant, (hand, expected) in enumerate( + zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + hand=list(hand), + expected=expected): actual_result = maybe_double_last(list(hand)) error_message = (f'Called maybe_double_last({list(hand)}). ' f'The function returned {actual_result}, but ' - f'the tests expected {expected} as the maybe-doubled version of {list(hand)}.') + f'the tests expected {expected} as the ' + f'maybe-doubled version of {list(hand)}.') self.assertEqual(actual_result, expected, msg=error_message) diff --git a/chaitanas-colossal-coaster/list_methods_test.py b/chaitanas-colossal-coaster/list_methods_test.py index 7a754b7..78ff1c8 100644 --- a/chaitanas-colossal-coaster/list_methods_test.py +++ b/chaitanas-colossal-coaster/list_methods_test.py @@ -18,10 +18,14 @@ class ListMethodsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue(self): test_data = [ - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), - ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), - ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), + ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), + ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), + ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), + ['Drax', 'Nebula', 'Gamora']), ] for variant, (params, expected) in enumerate(test_data, start=1): @@ -29,11 +33,14 @@ def test_add_me_to_the_queue(self): # That mutation wrecks havoc with the verification and error messaging. express_queue, normal_queue, ticket_type, person_name = deepcopy(params) - with self.subTest(f'variation #{variant}', params=params, expected=expected): + with self.subTest(f'variation #{variant}', + params=params, + expected=expected): actual_result = add_me_to_the_queue(*params) error_message = ( - f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'\nCalled add_me_to_the_queue' + f'{express_queue, normal_queue, ticket_type, person_name}.\n' f'The function returned {actual_result},\n' f' but the tests expected {expected} after {person_name} was added.') @@ -42,10 +49,14 @@ def test_add_me_to_the_queue(self): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue_validate_queue(self): test_data = [ - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), - ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), - ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), + ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), + ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), + ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), + ['Drax', 'Nebula', 'Gamora']), ] for variant, (params, expected) in enumerate(test_data, start=1): @@ -55,14 +66,18 @@ def test_add_me_to_the_queue_validate_queue(self): express, normal, ticket, name = params with self.subTest(f'variation #{variant}', - express=express, normal=normal, - ticket=ticket, name=name, expected=expected): + express=express, + normal=normal, + ticket=ticket, + name=name, + expected=expected): actual_result = add_me_to_the_queue(express, normal, ticket, name) if type == 1: error_message = ( - f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'\nCalled add_me_to_the_queue' + f'{express_queue, normal_queue, ticket_type, person_name}.\n' f'The queue == {express}, but the tests expected\n' f'queue == {expected} after {person_name} was added.' ) @@ -249,9 +264,12 @@ def test_how_many_namefellows(self): @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): test_data = [ - (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), - (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket'], 'Ultron'), - (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron'], 'Natasha') + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], + ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], + ['Wanda', 'Natasha', 'Steve', 'Rocket'], 'Ultron'), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], + ['Steve', 'Wanda', 'Rocket', 'Ultron'], 'Natasha') ] for variant, (queue, modified, expected) in enumerate(test_data, start=1): with self.subTest(f'variation #{variant}', queue=queue, modified=modified, expected=expected): @@ -264,9 +282,11 @@ def test_remove_the_last_person(self): expected_queue = modified error_message = (f'\nCalled remove_the_last_person({unmodified_queue}).\n' - f'The function was expected to remove and return the name "{expected_result}" ' + f'The function was expected to remove and return the name ' + f'"{expected_result}" ' f'and change the queue to {expected_queue},\n' - f'but the name "{actual_result}" was returned and the queue == {queue}.') + f'but the name "{actual_result}" was returned and the queue == ' + f'{queue}.') self.assertEqual((actual_result, queue), (expected_result, expected_queue), msg=error_message) diff --git a/collatz-conjecture/collatz_conjecture.py b/collatz-conjecture/collatz_conjecture.py index 7755bfc..a97aab0 100644 --- a/collatz-conjecture/collatz_conjecture.py +++ b/collatz-conjecture/collatz_conjecture.py @@ -13,6 +13,7 @@ """ +# pylint: disable=R0801 def steps(number: int) -> int: """ Return the number of steps it takes to reach 1 according to diff --git a/currency-exchange/exchange.py b/currency-exchange/exchange.py index e1871e5..ee23d07 100644 --- a/currency-exchange/exchange.py +++ b/currency-exchange/exchange.py @@ -1,9 +1,11 @@ """ Functions for calculating steps in exchanging currency. -Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +Python numbers documentation: +https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex -Overview of exchanging currency when travelling: https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ +Overview of exchanging currency when travelling: +https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ """ @@ -31,7 +33,7 @@ def get_change(budget: float, :param exchanging_value: float - amount of your money you want to exchange now. :return: float - amount left of your starting currency after exchanging. """ - return budget - exchanging_value + return budget - exchanging_value # pylint: disable=R0801 def get_value_of_bills(denomination: float, @@ -75,6 +77,7 @@ def get_leftover_of_bills(amount: float, return amount % denomination +# pylint: disable=R0801 def exchangeable_value(budget: float, exchange_rate: float, spread: int, diff --git a/currency-exchange/exchange_test.py b/currency-exchange/exchange_test.py index fd3754c..773b6fb 100644 --- a/currency-exchange/exchange_test.py +++ b/currency-exchange/exchange_test.py @@ -133,7 +133,8 @@ def test_exchangeable_value(self): expected=expected): actual_result = exchangeable_value(budget, exchange_rate, spread, denomination) - error_message = (f'Called exchangeable_value{budget, exchange_rate, spread, denomination}. ' + error_message = (f'Called exchangeable_value' + f'{budget, exchange_rate, spread, denomination}. ' f'The function returned {actual_result}, but ' f'The tests expected {expected} as the maximum ' f'value of the new currency .') diff --git a/guidos-gorgeous-lasagna/lasagna.py b/guidos-gorgeous-lasagna/lasagna.py index 595a253..577645a 100644 --- a/guidos-gorgeous-lasagna/lasagna.py +++ b/guidos-gorgeous-lasagna/lasagna.py @@ -13,6 +13,7 @@ PREPARATION_TIME: int = 2 +# pylint: disable=R0801 def bake_time_remaining(elapsed_bake_time: int) -> int: """ Calculate the bake time remaining. @@ -54,7 +55,8 @@ def elapsed_time_in_minutes(number_of_layers: int, :param number_of_layers: The number of layers added to the lasagna. :type number_of_layers: int - :param elapsed_bake_time: The number of minutes the lasagna has spent baking in the oven already. + :param elapsed_bake_time: The number of minutes the lasagna has + spent baking in the oven already. :type elapsed_bake_time: int :return: Elapsed time in minutes. :rtype: int diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index 4066aa8..f7b39ab 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -1,9 +1,11 @@ import unittest import pytest -# For this first exercise, it is really important to be clear about how we are importing names for tests. -# To that end, we are putting a try/catch around imports and throwing specific messages to help students -# decode that they need to create and title their constants and functions in a specific way. +# For this first exercise, it is really important to be clear about +# how we are importing names for tests.To that end, we are putting a +# try/catch around imports and throwing specific messages to help +# students decode that they need to create and title their constants +# and functions in a specific way. try: from lasagna import (EXPECTED_BAKE_TIME, bake_time_remaining, @@ -17,13 +19,17 @@ if 'EXPECTED_BAKE_TIME' in item_name: # pylint: disable=raise-missing-from - raise ImportError(f'\n\nMISSING CONSTANT --> \nWe can not find or import the constant {item_name} in your' - " 'lasagna.py' file.\nDid you misname or forget to define it?") from None - else: - item_name = item_name[:-1] + "()'" - # pylint: disable=raise-missing-from - raise ImportError("\n\nMISSING FUNCTION --> In your 'lasagna.py' file, we can not find or import the" - f' function named {item_name}. \nDid you misname or forget to define it?') from None + raise ImportError(f'\n\nMISSING CONSTANT --> \nWe can not find ' + f'or import the constant {item_name} in your' + " 'lasagna.py' file.\nDid you misname or forget " + "to define it?") from None + + item_name = item_name[:-1] + "()'" + # pylint: disable=raise-missing-from + raise ImportError("\n\nMISSING FUNCTION --> In your 'lasagna.py' " + "file, we can not find or import the" + f' function named {item_name}. \nDid you misname ' + f'or forget to define it?') from None # Here begins the formal test cases for the exercise. @@ -39,11 +45,16 @@ def test_bake_time_remaining(self): input_data = [1, 2, 5, 10, 15, 23, 33, 39] result_data = [39, 38, 35, 30, 25, 17, 7, 1] - for variant, (time, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', time=time, expected=expected): + for variant, (time, expected) in enumerate( + zip(input_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + time=time, + expected=expected): actual_result = bake_time_remaining(time) - failure_msg = (f'Called bake_time_remaining({time}). ' - f'The function returned {actual_result}, but the tests ' + failure_msg = (f'Called bake_time_remaining({time}). ' + f'The function returned {actual_result}, ' + f'but the tests ' f'expected {expected} as the remaining bake time.') self.assertEqual(actual_result, expected, msg=failure_msg) @@ -53,11 +64,16 @@ def test_preparation_time_in_minutes(self): input_data = [1, 2, 5, 8, 11, 15] result_data = [2, 4, 10, 16, 22, 30] - for variant, (layers, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', layers=layers, expected=expected): + for variant, (layers, expected) in enumerate( + zip(input_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + layers=layers, + expected=expected): actual_result = preparation_time_in_minutes(layers) failure_msg = (f'Called preparation_time_in_minutes({layers}). ' - f'The function returned {actual_result}, but the tests ' + f'The function returned {actual_result}, ' + f'but the tests ' f'expected {expected} as the preparation time.') self.assertEqual(actual_result, expected, msg=failure_msg) @@ -68,8 +84,13 @@ def test_elapsed_time_in_minutes(self): time_data = (3, 7, 8, 4, 15, 20) result_data = [5, 11, 18, 20, 37, 50] - for variant, (layers, time, expected) in enumerate(zip(layer_data, time_data, result_data), start=1): - with self.subTest(f'variation #{variant}', layers=layers, time=time, expected=expected): + for variant, (layers, time, expected) in enumerate( + zip(layer_data, time_data, result_data), + start=1): + with self.subTest(f'variation #{variant}', + layers=layers, + time=time, + expected=expected): actual_result = elapsed_time_in_minutes(layers, time) failure_msg = (f'Called elapsed_time_in_minutes({layers}, {time}). ' f'The function returned {actual_result}, but the tests ' diff --git a/hello-world/hello_world_test.py b/hello-world/hello_world_test.py index c2d1154..5617b64 100644 --- a/hello-world/hello_world_test.py +++ b/hello-world/hello_world_test.py @@ -26,6 +26,7 @@ class HelloWorldTest(unittest.TestCase): def test_say_hi(self): - msg = ("\n\nThis test expects a return of the string 'Hello, World!' \nDid you use print('Hello, World!') by " - "mistake?") + msg = ("\n\nThis test expects a return of the string " + "'Hello, World!' \nDid you use print('Hello, World!') " + "by mistake?") self.assertEqual(hello(), "Hello, World!", msg=msg) diff --git a/little-sisters-vocab/strings_test.py b/little-sisters-vocab/strings_test.py index b13d4e9..0c670df 100644 --- a/little-sisters-vocab/strings_test.py +++ b/little-sisters-vocab/strings_test.py @@ -15,19 +15,21 @@ def test_add_prefix_un(self): for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): with self.subTest(f'variation #{variant}', word=word, expected=expected): - actual_result = add_prefix_un(word) error_message = (f'Called add_prefix_un("{word}"). ' - f'The function returned "{actual_result}", but the ' - f'tests expected "{expected}" after adding "un" as a prefix.') + f'The function returned "{actual_result}", but the ' + f'tests expected "{expected}" after adding "un" as a prefix.') self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_make_word_groups_en(self): - input_data = ['en', 'circle', 'fold', 'close', 'joy', 'lighten', 'tangle', 'able', 'code', 'culture'] - expected = ('en :: encircle :: enfold :: enclose :: enjoy :: enlighten ::' - ' entangle :: enable :: encode :: enculture') + input_data = ['en', 'circle', 'fold', 'close', + 'joy', 'lighten', 'tangle', 'able', + 'code', 'culture'] + expected = ('en :: encircle :: enfold :: enclose :: ' + 'enjoy :: enlighten ::' + ' entangle :: enable :: encode :: enculture') actual_result = make_word_groups(input_data) error_message = (f'Called make_word_groups({input_data}). ' @@ -87,11 +89,17 @@ def test_make_words_groups_inter(self): @pytest.mark.task(taskno=3) def test_remove_suffix_ness(self): - input_data = ['heaviness', 'sadness', 'softness', 'crabbiness', 'lightness', 'artiness', 'edginess'] - result_data = ['heavy', 'sad', 'soft', 'crabby', 'light', 'arty', 'edgy'] - - for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', word=word, expected=expected): + input_data = ['heaviness', 'sadness', 'softness', + 'crabbiness', 'lightness', 'artiness', + 'edginess'] + result_data = ['heavy', 'sad', 'soft', 'crabby', + 'light', 'arty', 'edgy'] + + for variant, (word, expected) in enumerate( + zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + word=word, + expected=expected): actual_result = remove_suffix_ness(word) error_message = (f'Called remove_suffix_ness("{word}"). ' f'The function returned "{actual_result}", ' @@ -115,11 +123,16 @@ def test_adjective_to_verb(self): result_data = ['brighten', 'darken', 'harden', 'soften', 'lighten', 'dampen', 'shorten', 'weaken', 'blacken'] - for variant, (sentence, index, expected) in enumerate(zip(input_data, index_data, result_data), start=1): - with self.subTest(f'variation #{variant}', sentence=sentence, index=index, expected=expected): + for variant, (sentence, index, expected) in enumerate( + zip(input_data, index_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + sentence=sentence, + index=index, + expected=expected): actual_result = adjective_to_verb(sentence, index) error_message = (f'Called adjective_to_verb("{sentence}", {index}). ' - f'The function returned "{actual_result}", but the tests ' + f'The function returned "{actual_result}", ' + f'but the tests ' f'expected "{expected}" as the verb for ' f'the word at index {index}.') diff --git a/making-the-grade/loops.py b/making-the-grade/loops.py index afe3994..06668b7 100644 --- a/making-the-grade/loops.py +++ b/making-the-grade/loops.py @@ -18,7 +18,7 @@ def count_failed_students(student_scores: list) -> int: :param student_scores: list - containing int student scores. :return: int - count of student scores at or below 40. """ - return len([score for score in student_scores if score <= 40.0]) + return len([score for score in student_scores if score <= 40.0]) # pylint: disable=R0801 def above_threshold(student_scores: list, @@ -33,7 +33,7 @@ def above_threshold(student_scores: list, :param threshold: int - threshold to cross to be the "best" score. :return: list - of integer scores that are at or above the "best" threshold. """ - return [score for score in student_scores if score >= threshold] + return [score for score in student_scores if score >= threshold] # pylint: disable=R0801 def letter_grades(highest: int) -> list: @@ -54,6 +54,7 @@ def letter_grades(highest: int) -> list: return [41 + i * interval for i in range(4)] +# pylint: disable=R0801 def student_ranking(student_scores: list, student_names: list) -> list[str]: """ @@ -67,6 +68,7 @@ def student_ranking(student_scores: list, range(1, len(student_scores) + 1), student_scores, student_names)] +# pylint: disable=R0801 def perfect_score(student_info: list) -> list: """ Create a list that contains the name and grade of the first diff --git a/making-the-grade/loops_test.py b/making-the-grade/loops_test.py index 598e2b0..f34b0dd 100644 --- a/making-the-grade/loops_test.py +++ b/making-the-grade/loops_test.py @@ -29,18 +29,26 @@ def test_round_scores(self): [90, 40, 55, 70, 31, 25, 80, 95, 39, 40], [50, 36, 77, 41, 43, 78, 64, 91, 29, 88]] - for variant, (student_scores, expected) in enumerate(zip(test_data, result_data), start=1): - with self.subTest(f'variation #{variant}', student_scores=student_scores, expected=expected): + for variant, (student_scores, expected) in enumerate( + zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + student_scores=student_scores, + expected=expected): - # Because the test_input is a tuple, it has to be converted to a list for the function call. + # Because the test_input is a tuple, it has to be converted + # to a list for the function call. actual_result = round_scores(list(student_scores)) error_message = (f'Called round_scores({list(student_scores)}). ' - f'The function returned {sorted(actual_result)} after sorting, but ' + f'The function returned {sorted(actual_result)} ' + f'after sorting, but ' f'the tests expected {sorted(expected)} after sorting. ' f'One or more scores were rounded incorrectly.') # everything is sorted for easier comparison. - self.assertEqual(sorted(actual_result), sorted(expected), msg=error_message) + self.assertEqual( + sorted(actual_result), + sorted(expected), + msg=error_message) @pytest.mark.task(taskno=2) def test_count_failed_students(self): @@ -48,7 +56,8 @@ def test_count_failed_students(self): [40, 40, 35, 70, 30, 41, 90]] result_data = [0,4] - for variant, (student_scores, expected) in enumerate(zip(test_data, result_data), start=1): + for variant, (student_scores, expected) in enumerate( + zip(test_data, result_data), start=1): with self.subTest(f'variation #{variant}', student_scores=student_scores, expected=expected): @@ -95,8 +104,11 @@ def test_letter_grades(self): [41, 54, 67, 80], [41, 51, 61, 71]] - for variant, (highest, expected) in enumerate(zip(test_data, result_data), start=1): - with self.subTest(f'variation #{variant}', highest=highest, expected=expected): + for variant, (highest, expected) in enumerate( + zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + highest=highest, + expected=expected): actual_result = letter_grades(highest) error_message = (f'Called letter_grades({highest}). ' f'The function returned {actual_result}, but ' @@ -143,9 +155,12 @@ def test_perfect_score(self): result_data = [['Joci', 100],[], [], [], ['Raiana', 100]] - for variant, (student_info, expected) in enumerate(zip(test_data, result_data), start=1): + for variant, (student_info, expected) in enumerate( + zip(test_data, result_data), start=1): - with self.subTest(f'variation #{variant}', student_info=student_info, expected=expected): + with self.subTest(f'variation #{variant}', + student_info=student_info, + expected=expected): actual_result = perfect_score(student_info) error_message = (f'Called perfect_score({student_info}). ' f'The function returned {actual_result}, but ' diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index 87b831b..f6b7af4 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -65,7 +65,8 @@ def fail_safe(temperature, neutrons_produced_per_second, threshold) -> str: if thr_percent - 10 <= current_state <= thr_percent + 10: return 'NORMAL' - elif current_state < thr_percent - 10: + + if current_state < thr_percent - 10: return 'LOW' return 'DANGER' diff --git a/meltdown-mitigation/conditionals_test.py b/meltdown-mitigation/conditionals_test.py index 5e48ca3..6e9ab56 100644 --- a/meltdown-mitigation/conditionals_test.py +++ b/meltdown-mitigation/conditionals_test.py @@ -26,13 +26,18 @@ def test_is_criticality_balanced(self): for variant, data in enumerate(test_data, start=1): temp, neutrons_emitted, expected = data - with self.subTest(f'variation #{variant}', temp=temp, neutrons_emitted=neutrons_emitted, expected=expected): + with self.subTest(f'variation #{variant}', + temp=temp, + neutrons_emitted=neutrons_emitted, + expected=expected): # pylint: disable=assignment-from-no-return actual_result = is_criticality_balanced(temp, neutrons_emitted) - failure_message = (f'Called is_criticality_balanced({temp}, {neutrons_emitted}). ' + failure_message = (f'Called is_criticality_balanced(' + f'{temp}, {neutrons_emitted}). ' f' The function returned {actual_result}, ' - f'but the test expected {expected} as the return value.') + f'but the test expected {expected} as the ' + f'return value.') self.assertEqual(actual_result, expected, failure_message) @@ -49,12 +54,16 @@ def test_reactor_efficiency(self): for variant, data in enumerate(test_data, start=1): current, expected = data - with self.subTest(f'variation #{variant}', voltage=voltage, current=current, - theoretical_max_power=theoretical_max_power, expected=expected): + with self.subTest(f'variation #{variant}', + voltage=voltage, + current=current, + theoretical_max_power=theoretical_max_power, + expected=expected): # pylint: disable=assignment-from-no-return actual_result = reactor_efficiency(voltage, current, theoretical_max_power) - failure_message =(f'Called reactor_efficiency({voltage}, {current}, {theoretical_max_power}). ' + failure_message =(f'Called reactor_efficiency(' + f'{voltage}, {current}, {theoretical_max_power}). ' f'The function returned {actual_result}, ' f'but the test expected {expected} as the return value.') @@ -70,12 +79,15 @@ def test_fail_safe(self): (400, 'LOW'), (1101, 'DANGER'), (1200, 'DANGER')) for variant, (neutrons_per_second, expected) in enumerate(test_data, start=1): - with self.subTest(f'variation #{variant}', temp=temp, neutrons_per_second=neutrons_per_second, + with self.subTest(f'variation #{variant}', + temp=temp, + neutrons_per_second=neutrons_per_second, threshold=threshold, expected=expected): # pylint: disable=assignment-from-no-return actual_result = fail_safe(temp, neutrons_per_second, threshold) - failure_message = (f'Called fail_safe({temp}, {neutrons_per_second}, {threshold}). ' + failure_message = (f'Called fail_safe(' + f'{temp}, {neutrons_per_second}, {threshold}). ' f'The function returned {actual_result}, ' f'but the test expected {expected} as the return value.') diff --git a/requirements.txt b/requirements.txt index 015de27c9da29e7b8a25ce6bff042d73151ebfaa..ad58882afce5f0a1f504e0fdc86c550ca687c885 100644 GIT binary patch literal 470 zcmY+BQEtL85Jcy@Qm;}GXn}t84qSnTG>S-4M9fEe__lA>Nd&SsjyyZFGxp!_QG=4M z`c|WpK3E&QL=`IZtS6OVEp^pV9p#=CJ+IU@I!<(_p%uiK|`fg!fKuVqY)@b*O`rAWC_Fa*(B15Ae_MFry-tT%_loCFj zwWFtDA7ZYv`d;?o>O$5V(`t=@I!pF#&j(Ix_!W-Q#LtO6YdtB9XxMX3J>U7PKZaYs ajPG<0&kpjPB>Z)-&+s$u9L4l*cJL1!dqhJ3 delta 24 gcmcb{e2S6j|G$YUN)r=QChk+2Y{BR float: """ - Return only the total value of the bills (excluding fractional amounts) the booth would give back. + Return only the total value of the bills (excluding fractional amounts) + the booth would give back. The total you receive must be divisible by the value of one "bill" or unit, which can leave behind a fraction or remainder. @@ -79,7 +82,8 @@ def exchangeable_value(budget: float, spread: int, denomination: int) -> int: """ - Return the maximum value of the new currency after calculating the *exchange rate* plus the *spread*. + Return the maximum value of the new currency after calculating + the *exchange rate* plus the *spread*. :param budget: float - the amount of your money you are planning to exchange. :param exchange_rate: float - the unit value of the foreign currency. diff --git a/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py b/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py index 595a253..a69c70b 100644 --- a/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py +++ b/solutions/python/guidos-gorgeous-lasagna/1/lasagna.py @@ -54,7 +54,8 @@ def elapsed_time_in_minutes(number_of_layers: int, :param number_of_layers: The number of layers added to the lasagna. :type number_of_layers: int - :param elapsed_bake_time: The number of minutes the lasagna has spent baking in the oven already. + :param elapsed_bake_time: The number of minutes the lasagna has spent + baking in the oven already. :type elapsed_bake_time: int :return: Elapsed time in minutes. :rtype: int From 23f6c6c27bd3df56eb345681ed82cb120d7140d9 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 21:40:26 -0700 Subject: [PATCH 40/75] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index bf97b7b..9d883d9 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - python -m pylint --verbose $(Get-ChildItem -Recurse -Filter *.py | Where-Object { $_.FullName -notmatch '\\\.venv\\' -and $_.FullName -notmatch '\\venv\\' } | ForEach-Object { $_.FullName }) --rcfile=.pylintrc \ No newline at end of file + python -m pylint --verbose $(find . -name "*.py" ! -path "*/.venv/*" ! -path "*/venv/*") --rcfile=.pylintrc \ No newline at end of file From df47e9e7bf6d16ca1e25c355c39b7b7d954636ad Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 22:03:42 -0700 Subject: [PATCH 41/75] Docstring added --- armstrong-numbers/armstrong_numbers_test.py | 42 ++++++++++- black-jack/black_jack_test.py | 77 ++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/armstrong-numbers/armstrong_numbers_test.py b/armstrong-numbers/armstrong_numbers_test.py index 2deb3d0..6a9d54d 100644 --- a/armstrong-numbers/armstrong_numbers_test.py +++ b/armstrong-numbers/armstrong_numbers_test.py @@ -1,4 +1,44 @@ -"""Armstrong Numbers Test Suite.""" +""" +Armstrong Numbers Test Suite Documentation + +## Overview + +This test suite validates the `is_armstrong_number` function, +ensuring its correct behavior for various types of numbers: + +- Single-digit and multi-digit numbers +- Known Armstrong numbers and non-Armstrong numbers + +Tests are auto-generated based on canonical data from +[Exercism problem specifications](https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json). + +## Structure + +- **Framework:** Uses Python's built-in `unittest`. +- **Target Function:** `is_armstrong_number` (imported from `armstrong_numbers` module). + +## Test Cases + +| Test Description | Input | Expected Output | +|---------------------------------------------------------|----------|-----------------| +| Zero is an Armstrong number | 0 | `True` | +| Single-digit numbers are Armstrong numbers | 5 | `True` | +| No two-digit numbers (e.g. 10) are Armstrong numbers | 10 | `False` | +| 153 is an Armstrong number | 153 | `True` | +| 100 is not an Armstrong number | 100 | `False` | +| 9474 is an Armstrong number | 9474 | `True` | +| 9475 is not an Armstrong number | 9475 | `False` | +| 9926315 is an Armstrong number | 9926315 | `True` | +| 9926314 is not an Armstrong number | 9926314 | `False` | + +## Usage + +To run the tests, ensure `is_armstrong_number` is implemented and run: + +```bash +python -m unittest armstrong_numbers_test.py + +""" # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json diff --git a/black-jack/black_jack_test.py b/black-jack/black_jack_test.py index 89c235d..9bd10af 100644 --- a/black-jack/black_jack_test.py +++ b/black-jack/black_jack_test.py @@ -1,3 +1,18 @@ +""" +Unit tests suite for blackjack game logic functions. + +This test case class covers the following functionalities: + + - :func:`value_of_card`: Evaluates the value of individual blackjack cards. + - :func:`higher_card`: Determines the higher value card or equality. + - :func:`value_of_ace`: Calculates optimal ace value given two cards. + - :func:`is_blackjack`: Checks if a hand is a 'blackjack' (natural 21). + - :func:`can_split_pairs`: Determines if a hand can be split into two pairs. + - :func:`can_double_down`: Assesses eligibility to double down. + +Tests use parameterized cases with informative error messages for each assertion. +""" + from parameterized import parameterized import unittest import pytest @@ -13,6 +28,19 @@ class BlackJackTest(unittest.TestCase): + """ + Unit test suite for blackjack game logic functions. + + Each test validates a specific utility function from the blackjack module: + - :func:`test_value_of_card`: Checks the correct card value assignment. + - :func:`test_higher_card`: Ensures the function returns the card with higher value or both if equal. + - :func:`test_value_of_ace`: Verifies optimal ace value calculation based on the current hand. + - :func:`test_is_blackjack`: Determines if a two-card hand is a blackjack. + - :func:`test_can_split_pairs`: Tests if two cards can be split into two separate hands. + - :func:`test_can_double_down`: Evaluates if the hand is eligible for doubling down. + + Tests are parameterized for thorough coverage and use descriptive assertion error messages. + """ @pytest.mark.task(taskno=1) @parameterized.expand([ @@ -21,6 +49,15 @@ class BlackJackTest(unittest.TestCase): ('Q', 10), ('K', 10), ]) def test_value_of_card(self, card, expected): + """ + Test that the value_of_card function returns the correct + value for a given card. + + :param card: Card to evaluate. + :type card: str + :param expected: Expected value for the provided card. + :type expected: int + """ actual_result = value_of_card(card) error_msg = (f'Called value_of_card({card}). ' f'The function returned {actual_result} ' @@ -43,6 +80,14 @@ def test_value_of_card(self, card, expected): ('4', '8', '8'), ]) def test_higher_card(self, card_one, card_two, expected): + """ + Test that the higher_card function correctly determines + which card has the higher value. + + :param card_one: First card to compare. + :param card_two: Second card to compare. + :param expected: The expected result for the higher card. + """ actual_result = higher_card(card_one, card_two) error_msg = (f'Called higher_card({card_one}, {card_two}). ' f'The function returned {actual_result}, ' @@ -58,6 +103,14 @@ def test_higher_card(self, card_one, card_two, expected): ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1), ]) def test_value_of_ace(self, card_one, card_two, ace_value): + """ + Test that the value_of_ace function returns the correct ace value + given the other two cards. + + :param card_one: The first card in hand. + :param card_two: The second card in hand. + :param ace_value: The expected ace value. + """ actual_result = value_of_ace(card_one, card_two) error_msg = (f'Called value_of_ace({card_one}, {card_two}). ' f'The function returned {actual_result}, ' @@ -74,6 +127,12 @@ def test_value_of_ace(self, card_one, card_two, ace_value): (('Q', 'K'), False), ]) def test_is_blackjack(self, hand, expected): + """ + Test if a given hand qualifies as blackjack. + + :param hand: List representing the player's hand. + :param expected: Expected boolean indicating if the hand is a blackjack. + """ actual_result = is_blackjack(*hand) error_msg = (f'Called is_blackjack({hand[0]}, {hand[1]}). ' f'The function returned {actual_result}, ' @@ -88,6 +147,13 @@ def test_is_blackjack(self, hand, expected): (('10', '9'), False), ]) def test_can_split_pairs(self, hand, expected): + """ + Test whether the `can_split_pairs` function correctly + determines if a given blackjack hand can be split into pairs. + + :param hand: List representing the player's hand. + :param expected: Expected boolean result indicating if split is allowed. + """ actual_result = can_split_pairs(*hand) error_msg = (f'Called can_split_pairs({hand[0]}, {hand[1]}). ' f'The function returned {actual_result}, ' @@ -103,9 +169,18 @@ def test_can_split_pairs(self, hand, expected): (('10', '2'), False), (('10', '9'), False), ]) def test_can_double_down(self, hand, expected): + """ + Test whether a hand qualifies to double down in blackjack. + + :param hand: The current cards in the player's hand. + :type hand: list + :param expected: The expected boolean result of whether the + hand can double down. + :type expected: bool + """ actual_result = can_double_down(*hand) error_msg = (f'Called can_double_down({hand[0]}, {hand[1]}). ' f'The function returned {actual_result}, ' f'but hand {hand} {"can" if expected else "cannot"} ' f'be doubled down.') - self.assertEqual(actual_result, expected, msg=error_msg) \ No newline at end of file + self.assertEqual(actual_result, expected, msg=error_msg) From 8632e0e30b6c87475a56274fe01c9cddc986bb5b Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 22:22:39 -0700 Subject: [PATCH 42/75] Docstrings added --- armstrong-numbers/armstrong_numbers_test.py | 1 + black-jack/black_jack_test.py | 4 +- card-games/lists_test.py | 54 ++++++++++- .../list_methods_test.py | 90 +++++++++++++++++++ guidos-gorgeous-lasagna/lasagna_test.py | 1 + meltdown-mitigation/conditionals.py | 1 + 6 files changed, 148 insertions(+), 3 deletions(-) diff --git a/armstrong-numbers/armstrong_numbers_test.py b/armstrong-numbers/armstrong_numbers_test.py index 6a9d54d..092f75d 100644 --- a/armstrong-numbers/armstrong_numbers_test.py +++ b/armstrong-numbers/armstrong_numbers_test.py @@ -1,3 +1,4 @@ +# pylint: disable=C0301 """ Armstrong Numbers Test Suite Documentation diff --git a/black-jack/black_jack_test.py b/black-jack/black_jack_test.py index 9bd10af..f01f2df 100644 --- a/black-jack/black_jack_test.py +++ b/black-jack/black_jack_test.py @@ -1,3 +1,4 @@ +# pylint: disable=C0301 """ Unit tests suite for blackjack game logic functions. @@ -13,8 +14,8 @@ Tests use parameterized cases with informative error messages for each assertion. """ -from parameterized import parameterized import unittest +from parameterized import parameterized import pytest from black_jack import ( @@ -27,6 +28,7 @@ ) +# pylint: disable=C0301 class BlackJackTest(unittest.TestCase): """ Unit test suite for blackjack game logic functions. diff --git a/card-games/lists_test.py b/card-games/lists_test.py index 0fe4e11..1c5386e 100644 --- a/card-games/lists_test.py +++ b/card-games/lists_test.py @@ -1,3 +1,14 @@ +# pylint: disable=C0301 +""" +Unit tests for the card game utility functions in the lists module. + +Tests cover core behaviors such as generating rounds, concatenating rounds, +verifying round presence, calculating averages, comparing approximate and +actual averages, comparing even/odd index averages, and conditionally +doubling the last card value. + +Uses unittest framework with pytest marking for grading tasks. +""" import unittest import pytest @@ -13,17 +24,26 @@ class CardGamesTest(unittest.TestCase): + """Unit tests for card games utility functions.""" @pytest.mark.task(taskno=1) def test_get_rounds(self): + """ + Test the get_rounds function with various inputs to ensure it returns + the current round and the next two rounds. + :param self: The test case instance. + """ input_data = [0, 1, 10, 27, 99, 666] result_data = [[0, 1, 2], [1, 2, 3], [10, 11, 12], [27, 28, 29], [99, 100, 101], [666, 667, 668]] - for variant, (number, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', number=number, expected=expected): + for variant, (number, expected) in enumerate( + zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + number=number, + expected=expected): actual_result = get_rounds(number) error_message = (f'Called get_rounds({number}). ' f'The function returned {actual_result}, ' @@ -34,7 +54,12 @@ def test_get_rounds(self): @pytest.mark.task(taskno=2) def test_concatenate_rounds(self): + """ + Test the concatenate_rounds function to ensure it correctly combines + two lists of rounds. + :param self: The test case instance. + """ input_data = [([], []), ([0, 1], []), ([], [1, 2]), ([1], [2]), ([27, 28, 29], [35, 36]), ([1, 2, 3], [4, 5, 6])] @@ -60,7 +85,12 @@ def test_concatenate_rounds(self): @pytest.mark.task(taskno=3) def test_list_contains_round(self): + """ + Test the list_contains_round function to check if a specific round + is present in the list of played rounds. + :param self: The test case instance. + """ input_data = [([], 1), ([1, 2, 3], 0), ([27, 28, 29, 35, 36], 30), ([1], 1), ([1, 2, 3], 1), @@ -83,7 +113,12 @@ def test_list_contains_round(self): @pytest.mark.task(taskno=4) def test_card_average(self): + """ + Test the card_average function to ensure it calculates the correct + average of card values in a hand. + :param self: The test case instance. + """ input_data = [[1], [5, 6, 7], [1, 2, 3, 4], [1, 10, 100]] result_data = [1.0, 6.0, 2.5, 37.0] @@ -98,7 +133,12 @@ def test_card_average(self): @pytest.mark.task(taskno=5) def test_approx_average_is_average(self): + """ + Test the approx_average_is_average function to check if approximate + averages match the actual average. + :param self: The test case instance. + """ input_data = [[0, 1, 5], [3, 6, 9, 12, 150], [1, 2, 3, 5, 9], [2, 3, 4, 7, 8], [1, 2, 3], [2, 3, 4], [2, 3, 4, 8, 8], [1, 2, 4, 5, 8]] @@ -117,7 +157,12 @@ def test_approx_average_is_average(self): @pytest.mark.task(taskno=6) def test_average_even_is_average_odd(self): + """ + Test the average_even_is_average_odd function to verify if averages + of even and odd indexed cards are equal. + :param self: The test case instance. + """ input_data = [[5, 6, 8], [1, 2, 3, 4], [1, 2, 3], [5, 6, 7], [1, 3, 5, 7, 9]] result_data = [False, False, True, True, True] @@ -133,7 +178,12 @@ def test_average_even_is_average_odd(self): @pytest.mark.task(taskno=7) def test_maybe_double_last(self): + """ + Test the maybe_double_last function to ensure it doubles the last card + if it is a Jack (11). + :param self: The test case instance. + """ input_data = [(1, 2, 11), (5, 9, 11), (5, 9, 10), (1, 2, 3), (1, 11, 8)] result_data = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3], [1, 11, 8]] diff --git a/chaitanas-colossal-coaster/list_methods_test.py b/chaitanas-colossal-coaster/list_methods_test.py index 78ff1c8..2d68b0b 100644 --- a/chaitanas-colossal-coaster/list_methods_test.py +++ b/chaitanas-colossal-coaster/list_methods_test.py @@ -1,3 +1,20 @@ +# pylint: disable=C0301 +""" +Unit tests for queue management functions at Chaitana's roller coaster. + +This test suite, +- Validates correct functional behavior for basic queue operations (add, remove, insert, search, and sort) for both express and normal queues. +- Ensures that each function both returns the correct result and properly mutates or does not mutate the input lists as expected. +- Employs parameterized testing patterns, subTest blocks, and deep copies to safeguard against unintended mutation of shared state, ensuring accurate and isolated assertions. +- Uses unittest and pytest frameworks for flexible test discovery and marking (with @pytest.mark.task to delineate test responsibilities). + +Each function imported from `list_methods` is tested with a range of scenarios, including: +- Standard and edge inputs, +- In-place versus return-value mutation expectations, +- Validation that the queue's structure and contents match anticipated post-conditions. + +Users can run this file as a standard unittest/pytest module. Extend or edit test cases as new queue behaviors are implemented. +""" import unittest from copy import deepcopy import pytest @@ -14,9 +31,22 @@ ) +# pylint: disable=C0301 class ListMethodsTest(unittest.TestCase): + """ + Unit test suite for list manipulation functions. + + Tests various utility methods defined in the list_methods module. + """ + @pytest.mark.task(taskno=1) def test_add_me_to_the_queue(self): + """ + Test the add_me_to_the_queue function to ensure it adds a person + to the correct queue based on ticket type and returns the updated queue. + + :param self: The test case instance. + """ test_data = [ ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), @@ -48,6 +78,12 @@ def test_add_me_to_the_queue(self): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue_validate_queue(self): + """ + Test the add_me_to_the_queue function to validate that it mutates + and returns the correct queue based on ticket type. + + :param self: The test case instance. + """ test_data = [ ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), @@ -95,6 +131,12 @@ def test_add_me_to_the_queue_validate_queue(self): @pytest.mark.task(taskno=2) def test_find_my_friend(self): + """ + Test the find_my_friend function to ensure it returns the correct + index of the friend in the queue. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), @@ -117,6 +159,12 @@ def test_find_my_friend(self): @pytest.mark.task(taskno=3) def test_add_me_with_my_friends(self): + """ + Test the add_me_with_my_friends function to ensure it inserts the person + at the specified index and returns the updated queue. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), @@ -148,6 +196,12 @@ def test_add_me_with_my_friends(self): @pytest.mark.task(taskno=3) def test_add_me_with_my_friends_validate_queue(self): + """ + Test the add_me_with_my_friends function to validate that it mutates + the original queue and returns it. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), @@ -179,6 +233,12 @@ def test_add_me_with_my_friends_validate_queue(self): @pytest.mark.task(taskno=4) def test_remove_the_mean_person(self): + """ + Test the remove_the_mean_person function to ensure it removes the specified + person and returns the updated queue. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), @@ -210,6 +270,12 @@ def test_remove_the_mean_person(self): @pytest.mark.task(taskno=4) def test_remove_the_mean_person_validate_queue(self): + """ + Test the remove_the_mean_person function to validate that it mutates + the original queue and returns it. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), @@ -244,6 +310,12 @@ def test_remove_the_mean_person_validate_queue(self): @pytest.mark.task(taskno=5) def test_how_many_namefellows(self): + """ + Test the how_many_namefellows function to ensure it correctly counts + occurrences of a name in the queue. + + :param self: The test case instance. + """ test_data = [(['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Bucky'), (['Natasha', 'Steve', 'Ultron', 'Rocket'], 'Natasha'), (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Natasha')] @@ -263,6 +335,12 @@ def test_how_many_namefellows(self): @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): + """ + Test the remove_the_last_person function to ensure it removes and returns + the last person from the queue, mutating the queue. + + :param self: The test case instance. + """ test_data = [ (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), @@ -293,6 +371,12 @@ def test_remove_the_last_person(self): @pytest.mark.task(taskno=7) def test_sorted_names(self): + """ + Test the sorted_names function to ensure it returns a sorted copy + of the queue without mutating the original. + + :param self: The test case instance. + """ test_data =( (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), @@ -312,6 +396,12 @@ def test_sorted_names(self): @pytest.mark.task(taskno=7) def test_sorted_names_validate_queue(self): + """ + Test the sorted_names function to validate that it does not mutate + the original queue. + + :param self: The test case instance. + """ test_data = ( (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index f7b39ab..f0a6114 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -32,6 +32,7 @@ f'or forget to define it?') from None +# pylint: disable=C0301 # Here begins the formal test cases for the exercise. class LasagnaTest(unittest.TestCase): diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index f6b7af4..c2fd574 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -1,6 +1,7 @@ """Functions to prevent a nuclear meltdown.""" +# pylint: disable=C0301 def is_criticality_balanced(temperature, neutrons_emitted) -> bool: """Verify criticality is balanced. From 45e219a2dc49754cd8ebe4f1ac85f9434664f96e Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 22:54:37 -0700 Subject: [PATCH 43/75] Docstrings added --- collatz-conjecture/collatz_conjecture_test.py | 16 ++++- currency-exchange/exchange_test.py | 11 ++++ ghost-gobble-arcade-game/arcade_game_test.py | 58 ++++++++++++++++++- grains/grains_test.py | 27 ++++++++- guidos-gorgeous-lasagna/lasagna_test.py | 12 ++++ hello-world/hello_world_test.py | 13 +++++ leap/leap_test.py | 11 ++++ little-sisters-vocab/strings_test.py | 57 +++++++++++++----- making-the-grade/loops_test.py | 36 +++++++++--- meltdown-mitigation/conditionals_test.py | 15 +++-- square-root/square_root_test.py | 11 ++++ 11 files changed, 232 insertions(+), 35 deletions(-) diff --git a/collatz-conjecture/collatz_conjecture_test.py b/collatz-conjecture/collatz_conjecture_test.py index 306e3db..e748c37 100644 --- a/collatz-conjecture/collatz_conjecture_test.py +++ b/collatz-conjecture/collatz_conjecture_test.py @@ -1,3 +1,11 @@ +# pylint: disable=C0301 +""" +Unit tests for the steps function implementing the Collatz Conjecture. + +Tests include validation for zero, negative, even, odd values, and large numbers. +Ensures ValueError is raised for invalid input according to Collatz rules. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json # File last updated on 2023-07-20 @@ -10,6 +18,8 @@ class CollatzConjectureTest(unittest.TestCase): + """Unit tests for the functions related to the Collatz conjecture.""" + def test_zero_steps_for_one(self): self.assertEqual(steps(1), 0) @@ -26,10 +36,12 @@ def test_zero_is_an_error(self): with self.assertRaises(ValueError) as err: steps(0) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Only positive integers are allowed") + self.assertEqual(err.exception.args[0], + "Only positive integers are allowed") def test_negative_value_is_an_error(self): with self.assertRaises(ValueError) as err: steps(-15) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Only positive integers are allowed") + self.assertEqual(err.exception.args[0], + "Only positive integers are allowed") diff --git a/currency-exchange/exchange_test.py b/currency-exchange/exchange_test.py index 773b6fb..4a33853 100644 --- a/currency-exchange/exchange_test.py +++ b/currency-exchange/exchange_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Unit tests for currency exchange functions. + +Tests cover calculations for currency exchange, change, bill values, bill counts, leftovers, and determining maximum currency exchangeable after applying spread and denomination constraints. + +Uses pytest and unittest for parametrized and task-specific testing. +""" + import unittest import pytest @@ -11,6 +20,8 @@ class CurrencyExchangeTest(unittest.TestCase): + """Unit tests for currency exchange utility functions.""" + @pytest.mark.task(taskno=1) def test_exchange_money(self): test_data = [(100000, 0.8), (700000, 10.0)] diff --git a/ghost-gobble-arcade-game/arcade_game_test.py b/ghost-gobble-arcade-game/arcade_game_test.py index 2ffdd3d..a775a5a 100644 --- a/ghost-gobble-arcade-game/arcade_game_test.py +++ b/ghost-gobble-arcade-game/arcade_game_test.py @@ -1,10 +1,66 @@ +# pylint: disable=C0301 +""" + +This `arcade_game_test.py` file is a comprehensive set of unit tests +for the Pac-Man-style "ghost gobble arcade game" logic found in +`arcade_game.py`. The test class uses Python's `unittest` framework +(with `pytest` markers for task organization) and checks the +correctness of each function (`eat_ghost`, `score`, `lose`, `win`) +provided by the game logic module. + +Structure & Coverage + +1. **eat_ghost** +- Tests if Pac-Man can eat a ghost only when: + - A power pellet is active **and** + - He's touching a ghost. +- Includes positive (should eat) and negative (should not eat) scenarios. + +2. **score** +- Tests Pac-Man scores when: + - Touching a dot. + - Touching a power pellet. + - Not scoring when touching neither. + +3. **lose** +- Tests losing logic: + - Should lose if touching a ghost without a power pellet. + - Should *not* lose if touching a ghost *with* a power pellet active. + - Should *not* lose if not touching a ghost. + +4. **win** +- Tests winning the game: + - Wins if *all dots eaten* and hasn't lost (touching ghost *without* pellet). + - Doesn't win if all dots eaten *but* touching a ghost without a pellet. + - Wins if all dots eaten *and* touching a ghost *with* a pellet (i.e., didn't lose). + - Doesn't win if not all dots eaten. + +General Comments + +- The error messages are descriptive to aid debugging if an assertion fails. +- Each test calls the appropriate function with various inputs to check all critical edge and task-specific cases. +- Follows best practices for test case isolation and clarity. + +Final Note + +This suite provides full behavioral coverage of the gameplay logic as +specified in `arcade_game.py`. To run these tests, simply execute the +file with a compatible test runner (pytest or python -m unittest). +Make sure the tested functions are correctly imported from the +`arcade_game` module (as in your header). + +If you need any enhancements, parameterizations, or help troubleshooting +failing test cases, let me know! +""" import unittest import pytest from arcade_game import eat_ghost, score, lose, win class GhostGobbleGameTest(unittest.TestCase): - + """ + Unit tests for the Ghost Gobble arcade game functions using unittest framework. + """ @pytest.mark.task(taskno=1) def test_ghost_gets_eaten(self): actual_result = eat_ghost(True, True) diff --git a/grains/grains_test.py b/grains/grains_test.py index 3c7f281..52f403a 100644 --- a/grains/grains_test.py +++ b/grains/grains_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Unit tests for the grains module. + +Tests the square and total functions to ensure correct calculations +of grains on each square, total grains, and proper error handling for +invalid input. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json # File last updated on 2023-09-27 @@ -11,6 +20,15 @@ class GrainsTest(unittest.TestCase): + """ + Unit tests for the GrainsTest class, covering the square and + total functions from the grains module. + + Verifies correct grain counts for specific chessboard squares, + total grain calculation, and proper exception handling for + invalid input values. + """ + def test_grains_on_square_1(self): self.assertEqual(square(1), 1) @@ -48,19 +66,22 @@ def test_square_0_is_invalid(self): with self.assertRaises(ValueError) as err: square(0) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "square must be between 1 and 64") + self.assertEqual(err.exception.args[0], + "square must be between 1 and 64") def test_negative_square_is_invalid(self): with self.assertRaises(ValueError) as err: square(-1) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "square must be between 1 and 64") + self.assertEqual(err.exception.args[0], + "square must be between 1 and 64") def test_square_greater_than_64_is_invalid(self): with self.assertRaises(ValueError) as err: square(65) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "square must be between 1 and 64") + self.assertEqual(err.exception.args[0], + "square must be between 1 and 64") def test_returns_the_total_number_of_grains_on_the_board(self): self.assertEqual(total(), 18446744073709551615) diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index f0a6114..4d02fc7 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -1,3 +1,14 @@ +# pylint: disable=C0301 +""" +Unit tests for Guido's gorgeous lasagna module functions and constants. + +Covers presence and values of EXPECTED_BAKE_TIME, and correctness of +bake_time_remaining, preparation_time_in_minutes, and elapsed_time_in_minutes. +Also checks that all required docstrings are present. + +Raises informative import errors to help identify naming issues during testing. +""" + import unittest import pytest @@ -35,6 +46,7 @@ # pylint: disable=C0301 # Here begins the formal test cases for the exercise. class LasagnaTest(unittest.TestCase): + """Unit tests for lasagna-related functionality.""" @pytest.mark.task(taskno=1) def test_EXPECTED_BAKE_TIME(self): diff --git a/hello-world/hello_world_test.py b/hello-world/hello_world_test.py index 5617b64..c804691 100644 --- a/hello-world/hello_world_test.py +++ b/hello-world/hello_world_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Unit tests for the 'hello' function in hello_world.py. + +Ensures the function returns 'Hello, World!' as expected. +Provides informative error messages if the function is +missing or incorrectly implemented. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/hello-world/canonical-data.json # File last updated on 2023-07-19 @@ -25,6 +34,10 @@ class HelloWorldTest(unittest.TestCase): + """ + Test case class for verifying HelloWorld functionality using unittest. + """ + def test_say_hi(self): msg = ("\n\nThis test expects a return of the string " "'Hello, World!' \nDid you use print('Hello, World!') " diff --git a/leap/leap_test.py b/leap/leap_test.py index 6a1d732..c06f951 100644 --- a/leap/leap_test.py +++ b/leap/leap_test.py @@ -1,3 +1,10 @@ +# pylint: disable=C0301 +""" +Unit tests for the leap_year function, verifying correct leap +year logic for various cases based on canonical Exercism +problem specifications. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json # File last updated on 2023-07-19 @@ -10,6 +17,10 @@ class LeapTest(unittest.TestCase): + """ + Unit tests for verifying leap year calculation functionality. + """ + def test_year_not_divisible_by_4_in_common_year(self): self.assertIs(leap_year(2015), False) diff --git a/little-sisters-vocab/strings_test.py b/little-sisters-vocab/strings_test.py index 0c670df..dc45889 100644 --- a/little-sisters-vocab/strings_test.py +++ b/little-sisters-vocab/strings_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Unit tests for the string manipulation functions in the 'strings' module. + +Tests adding the 'un' prefix, transforming word groups with a prefix, removing +the '-ness' suffix, and converting adjectives to verbs within sentences. +Uses pytest markers for task organization and detailed error messages for clarity. +""" + import unittest import pytest from strings import (add_prefix_un, @@ -7,18 +16,28 @@ class LittleSistersVocabTest(unittest.TestCase): + """ + Unit tests for vocabulary string manipulation functions in + the 'strings' module. + """ @pytest.mark.task(taskno=1) def test_add_prefix_un(self): - input_data = ['happy', 'manageable', 'fold', 'eaten', 'avoidable', 'usual'] + input_data = ['happy', 'manageable', 'fold', + 'eaten', 'avoidable', 'usual'] result_data = [f'un{item}' for item in input_data] - for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', word=word, expected=expected): + for variant, (word, expected) in enumerate( + zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + word=word, + expected=expected): actual_result = add_prefix_un(word) error_message = (f'Called add_prefix_un("{word}"). ' - f'The function returned "{actual_result}", but the ' - f'tests expected "{expected}" after adding "un" as a prefix.') + f'The function returned "{actual_result}", ' + f'but the ' + f'tests expected "{expected}" after adding ' + f'"un" as a prefix.') self.assertEqual(actual_result, expected, msg=error_message) @@ -41,10 +60,12 @@ def test_make_word_groups_en(self): @pytest.mark.task(taskno=2) def test_make_word_groups_pre(self): - input_data = ['pre', 'serve', 'dispose', 'position', 'requisite', 'digest', - 'natal', 'addressed', 'adolescent', 'assumption', 'mature', 'compute'] - expected = ('pre :: preserve :: predispose :: preposition :: prerequisite :: ' - 'predigest :: prenatal :: preaddressed :: preadolescent :: preassumption :: ' + input_data = ['pre', 'serve', 'dispose', 'position', + 'requisite', 'digest', 'natal', 'addressed', + 'adolescent', 'assumption', 'mature', 'compute'] + expected = ('pre :: preserve :: predispose :: preposition :: ' + 'prerequisite :: predigest :: prenatal :: ' + 'preaddressed :: preadolescent :: preassumption :: ' 'premature :: precompute') actual_result = make_word_groups(input_data) @@ -57,7 +78,8 @@ def test_make_word_groups_pre(self): @pytest.mark.task(taskno=2) def test_make_word_groups_auto(self): - input_data = ['auto', 'didactic', 'graph', 'mate', 'chrome', 'centric', 'complete', + input_data = ['auto', 'didactic', 'graph', 'mate', + 'chrome', 'centric', 'complete', 'echolalia', 'encoder', 'biography'] expected = ('auto :: autodidactic :: autograph :: automate :: autochrome :: ' 'autocentric :: autocomplete :: autoecholalia :: autoencoder :: ' @@ -73,8 +95,9 @@ def test_make_word_groups_auto(self): @pytest.mark.task(taskno=2) def test_make_words_groups_inter(self): - input_data = ['inter', 'twine', 'connected', 'dependent', 'galactic', 'action', - 'stellar', 'cellular', 'continental', 'axial', 'operative', 'disciplinary'] + input_data = ['inter', 'twine', 'connected', 'dependent', + 'galactic', 'action', 'stellar', 'cellular', + 'continental', 'axial', 'operative', 'disciplinary'] expected = ('inter :: intertwine :: interconnected :: interdependent :: ' 'intergalactic :: interaction :: interstellar :: intercellular :: ' 'intercontinental :: interaxial :: interoperative :: interdisciplinary') @@ -103,8 +126,8 @@ def test_remove_suffix_ness(self): actual_result = remove_suffix_ness(word) error_message = (f'Called remove_suffix_ness("{word}"). ' f'The function returned "{actual_result}", ' - f'but the tests expected "{expected}" after the ' - 'suffix was removed.') + f'but the tests expected "{expected}" ' + f'after the suffix was removed.') self.assertEqual(actual_result, expected, msg=error_message) @@ -121,7 +144,8 @@ def test_adjective_to_verb(self): 'The black oil got on the white dog.'] index_data = [-2, -1, 3, 3, -2, -3, 5, 2, 1] result_data = ['brighten', 'darken', 'harden', 'soften', - 'lighten', 'dampen', 'shorten', 'weaken', 'blacken'] + 'lighten', 'dampen', 'shorten', 'weaken', + 'blacken'] for variant, (sentence, index, expected) in enumerate( zip(input_data, index_data, result_data), start=1): @@ -130,7 +154,8 @@ def test_adjective_to_verb(self): index=index, expected=expected): actual_result = adjective_to_verb(sentence, index) - error_message = (f'Called adjective_to_verb("{sentence}", {index}). ' + error_message = (f'Called adjective_to_verb("' + f'{sentence}", {index}). ' f'The function returned "{actual_result}", ' f'but the tests ' f'expected "{expected}" as the verb for ' diff --git a/making-the-grade/loops_test.py b/making-the-grade/loops_test.py index f34b0dd..2fd534b 100644 --- a/making-the-grade/loops_test.py +++ b/making-the-grade/loops_test.py @@ -1,3 +1,12 @@ +# pylint: disable=C0301 +""" +Test suite for validating functions from the 'loops' module related +to student exam score calculations, including rounding scores, +counting failed students, filtering scores above a threshold, +generating letter grade cutoffs, ranking students, and identifying +perfect scores. +""" + import unittest import pytest @@ -11,18 +20,23 @@ class MakingTheGradeTest(unittest.TestCase): + """ + Test case class for verifying functions in the making-the-grade module. + """ @pytest.mark.task(taskno=1) def test_round_scores(self): - # Because we the input list can be mutated, the test data has been created - # as tuples, which we then convert to a list when the test runs. - # this makes accurate error messages easier to create. + # Because we the input list can be mutated, the test data has + # been created as tuples, which we then convert to a list when + # the test runs. This makes accurate error messages easier to create. test_data = [tuple(), (.5,), (1.5,), - (90.33, 40.5, 55.44, 70.05, 30.55, 25.45, 80.45, 95.3, 38.7, 40.3), - (50, 36.03, 76.92, 40.7, 43, 78.29, 63.58, 91, 28.6, 88.0)] + (90.33, 40.5, 55.44, 70.05, 30.55, 25.45, + 80.45, 95.3, 38.7, 40.3), + (50, 36.03, 76.92, 40.7, 43, 78.29, 63.58, + 91, 28.6, 88.0)] result_data = [[], [0], [2], @@ -41,7 +55,8 @@ def test_round_scores(self): error_message = (f'Called round_scores({list(student_scores)}). ' f'The function returned {sorted(actual_result)} ' f'after sorting, but ' - f'the tests expected {sorted(expected)} after sorting. ' + f'the tests expected {sorted(expected)} after ' + f'sorting. ' f'One or more scores were rounded incorrectly.') # everything is sorted for easier comparison. @@ -142,11 +157,14 @@ def test_student_ranking(self): @pytest.mark.task(taskno=6) def test_perfect_score(self): test_data = [ - [['Joci', 100], ['Vlad', 100], ['Raiana', 100], ['Alessandro', 100]], + [['Joci', 100], ['Vlad', 100], ['Raiana', 100], + ['Alessandro', 100]], [['Jill', 30], ['Paul', 73]], [], - [['Rui', 60], ['Joci', 58], ['Sara', 91], ['Kora', 93], ['Alex', 42], - ['Jan', 81], ['Lilliana', 40], ['John', 60], ['Bern', 28], ['Vlad', 55]], + [['Rui', 60], ['Joci', 58], ['Sara', 91], ['Kora', 93], + ['Alex', 42], + ['Jan', 81], ['Lilliana', 40], ['John', 60], ['Bern', 28], + ['Vlad', 55]], [['Yoshi', 52], ['Jan', 86], ['Raiana', 100], ['Betty', 60], ['Joci', 100], ['Kora', 81], ['Bern', 41], ['Rose', 94]] diff --git a/meltdown-mitigation/conditionals_test.py b/meltdown-mitigation/conditionals_test.py index 6e9ab56..a348d8b 100644 --- a/meltdown-mitigation/conditionals_test.py +++ b/meltdown-mitigation/conditionals_test.py @@ -1,3 +1,11 @@ +# pylint: disable=C0301 +""" +Unit tests for the meltdown mitigation functions, verifying +correct behavior for criticality balance, reactor efficiency, +and fail safe status using a variety of boundary and +representative input values. +""" + import unittest import pytest from conditionals import (is_criticality_balanced, @@ -6,15 +14,14 @@ class MeltdownMitigationTest(unittest.TestCase): - """Test cases for Meltdown mitigation exercise. - """ + """Test cases for Meltdown mitigation exercise.""" @pytest.mark.task(taskno=1) def test_is_criticality_balanced(self): - """Testing border cases around typical points. + """ + Testing border cases around typical points. T, n == (800, 500), (625, 800), (500, 1000), etc. - """ test_data = ((750, 650, True), (799, 501, True), (500, 600, True), diff --git a/square-root/square_root_test.py b/square-root/square_root_test.py index 8f94940..89a2d6c 100644 --- a/square-root/square_root_test.py +++ b/square-root/square_root_test.py @@ -1,3 +1,10 @@ +# pylint: disable=C0301 +""" +Unit tests for the square_root function, verifying +correct calculation of integer square roots for various +inputs using test data from Exercism. +""" + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json # File last updated on 2023-07-19 @@ -10,6 +17,10 @@ class SquareRootTest(unittest.TestCase): + """ + Test suite for verifying the functionality of the square_root function. + """ + def test_root_of_1(self): self.assertEqual(square_root(1), 1) From 9aa621c00f57500e5b1f0f5a9cd04947ab5a76f8 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 22:55:04 -0700 Subject: [PATCH 44/75] Update lasagna_test.py --- guidos-gorgeous-lasagna/lasagna_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index 4d02fc7..07c23a4 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -49,7 +49,7 @@ class LasagnaTest(unittest.TestCase): """Unit tests for lasagna-related functionality.""" @pytest.mark.task(taskno=1) - def test_EXPECTED_BAKE_TIME(self): + def test_expected_bake_time(self): failure_msg = 'Expected a constant of EXPECTED_BAKE_TIME with a value of 40.' self.assertEqual(EXPECTED_BAKE_TIME, 40, msg=failure_msg) From f7ab7cd29a7aac6551ef7388415cdd96342b0b78 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 12 Aug 2025 23:00:11 -0700 Subject: [PATCH 45/75] Update README.md --- README.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6fd8298..7770891 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ -# Exercism Python Track +# [Exercism Python Track](https://exercism.io/my/tracks/python) -Learn and master concepts to achieve fluency in Python. +
+ + +
+ +## Exercism exercises in Python + +### About Exercism +Exercism is an online platform designed to help you improve your coding skills through practice and mentorship. + +Exercism provides you with thousands of exercises spread across numerous language tracks. Once you start a language track you are presented with a core set of exercises to complete. Each one is a fun and interesting challenge designed to teach you a little more about the features of a language. + +You complete a challenge by downloading the exercise to your computer and solving it in your normal working environment. Once you've finished you submit it online and one of our mentors will give you feedback on how you could improve it using features of the language that you may not be familiar with. After a couple of rounds of refactoring, your exercise will be complete and you will unlock both the next core exercise and also a series of related side-exercises for you to practice with. + +Exercism is entirely open source and relies on the contributions of thousands of wonderful people. + +Exercism is designed to be fun and friendly, and we place a strong emphasis on empathetic communication. + +Sign up and have fun. Exercism is 100% free :) -143 coding exercises for Python on Exercism. From Error Handling to ISBN Verifier. From 4446b5d5a50d0f2143dc092ce0e533965545466d Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 19:57:27 -0700 Subject: [PATCH 46/75] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 9d883d9..2c8c785 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.10", "3.12"] + python-version: ["3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} From 7136c784ffd874cddda544482806c08169029da7 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:11:49 -0700 Subject: [PATCH 47/75] # pylint: disable=C0116 --- guidos-gorgeous-lasagna/lasagna_test.py | 2 +- hello-world/hello_world_test.py | 1 + leap/leap_test.py | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guidos-gorgeous-lasagna/lasagna_test.py b/guidos-gorgeous-lasagna/lasagna_test.py index 07c23a4..6a21967 100644 --- a/guidos-gorgeous-lasagna/lasagna_test.py +++ b/guidos-gorgeous-lasagna/lasagna_test.py @@ -43,7 +43,7 @@ f'or forget to define it?') from None -# pylint: disable=C0301 +# pylint: disable=C0301, C0116 # Here begins the formal test cases for the exercise. class LasagnaTest(unittest.TestCase): """Unit tests for lasagna-related functionality.""" diff --git a/hello-world/hello_world_test.py b/hello-world/hello_world_test.py index c804691..811bf6a 100644 --- a/hello-world/hello_world_test.py +++ b/hello-world/hello_world_test.py @@ -33,6 +33,7 @@ ) from None +# pylint: disable=C0116 class HelloWorldTest(unittest.TestCase): """ Test case class for verifying HelloWorld functionality using unittest. diff --git a/leap/leap_test.py b/leap/leap_test.py index c06f951..42e425a 100644 --- a/leap/leap_test.py +++ b/leap/leap_test.py @@ -16,10 +16,9 @@ ) +# pylint: disable=C0116 class LeapTest(unittest.TestCase): - """ - Unit tests for verifying leap year calculation functionality. - """ + """Unit tests for verifying leap year calculation functionality.""" def test_year_not_divisible_by_4_in_common_year(self): self.assertIs(leap_year(2015), False) From 9e5d829f2c31bd2c2620064e5a3bc7eeeaa012b3 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:15:52 -0700 Subject: [PATCH 48/75] # pylint: disable=C0116 --- collatz-conjecture/collatz_conjecture_test.py | 1 + currency-exchange/exchange_test.py | 1 + grains/grains_test.py | 1 + hello-world/hello_world.py | 1 + little-sisters-vocab/strings_test.py | 1 + making-the-grade/loops_test.py | 1 + meltdown-mitigation/conditionals_test.py | 1 + square-root/square_root_test.py | 1 + 8 files changed, 8 insertions(+) diff --git a/collatz-conjecture/collatz_conjecture_test.py b/collatz-conjecture/collatz_conjecture_test.py index e748c37..1b2b3f5 100644 --- a/collatz-conjecture/collatz_conjecture_test.py +++ b/collatz-conjecture/collatz_conjecture_test.py @@ -17,6 +17,7 @@ ) +# pylint: disable=C0116 class CollatzConjectureTest(unittest.TestCase): """Unit tests for the functions related to the Collatz conjecture.""" diff --git a/currency-exchange/exchange_test.py b/currency-exchange/exchange_test.py index 4a33853..7f1f495 100644 --- a/currency-exchange/exchange_test.py +++ b/currency-exchange/exchange_test.py @@ -19,6 +19,7 @@ exchangeable_value) +# pylint: disable=C0116 class CurrencyExchangeTest(unittest.TestCase): """Unit tests for currency exchange utility functions.""" diff --git a/grains/grains_test.py b/grains/grains_test.py index 52f403a..526319a 100644 --- a/grains/grains_test.py +++ b/grains/grains_test.py @@ -19,6 +19,7 @@ ) +# pylint: disable=C0116 class GrainsTest(unittest.TestCase): """ Unit tests for the GrainsTest class, covering the square and diff --git a/hello-world/hello_world.py b/hello-world/hello_world.py index d695ea1..5efcac4 100644 --- a/hello-world/hello_world.py +++ b/hello-world/hello_world.py @@ -1,2 +1,3 @@ +# pylint: disable=C0116 def hello(): return 'Hello, World!' diff --git a/little-sisters-vocab/strings_test.py b/little-sisters-vocab/strings_test.py index dc45889..1b2999c 100644 --- a/little-sisters-vocab/strings_test.py +++ b/little-sisters-vocab/strings_test.py @@ -15,6 +15,7 @@ adjective_to_verb) +# pylint: disable=C0116 class LittleSistersVocabTest(unittest.TestCase): """ Unit tests for vocabulary string manipulation functions in diff --git a/making-the-grade/loops_test.py b/making-the-grade/loops_test.py index 2fd534b..47d6a34 100644 --- a/making-the-grade/loops_test.py +++ b/making-the-grade/loops_test.py @@ -19,6 +19,7 @@ perfect_score) +# pylint: disable=C0116 class MakingTheGradeTest(unittest.TestCase): """ Test case class for verifying functions in the making-the-grade module. diff --git a/meltdown-mitigation/conditionals_test.py b/meltdown-mitigation/conditionals_test.py index a348d8b..f0a4b8e 100644 --- a/meltdown-mitigation/conditionals_test.py +++ b/meltdown-mitigation/conditionals_test.py @@ -13,6 +13,7 @@ fail_safe) +# pylint: disable=C0116 class MeltdownMitigationTest(unittest.TestCase): """Test cases for Meltdown mitigation exercise.""" diff --git a/square-root/square_root_test.py b/square-root/square_root_test.py index 89a2d6c..fcd2bb7 100644 --- a/square-root/square_root_test.py +++ b/square-root/square_root_test.py @@ -16,6 +16,7 @@ ) +# pylint: disable=C0116 class SquareRootTest(unittest.TestCase): """ Test suite for verifying the functionality of the square_root function. From 409aae5ec4a98b463f363b49a3044a0ecfc4add3 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:16:51 -0700 Subject: [PATCH 49/75] Update arcade_game_test.py --- ghost-gobble-arcade-game/arcade_game_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghost-gobble-arcade-game/arcade_game_test.py b/ghost-gobble-arcade-game/arcade_game_test.py index a775a5a..336eefd 100644 --- a/ghost-gobble-arcade-game/arcade_game_test.py +++ b/ghost-gobble-arcade-game/arcade_game_test.py @@ -1,4 +1,4 @@ -# pylint: disable=C0301 +# pylint: disable=C0301, C0116 """ This `arcade_game_test.py` file is a comprehensive set of unit tests From e491b56e9fc91d9c8dfc968fc06d99b3b219985b Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:20:13 -0700 Subject: [PATCH 50/75] Update hello_world.py --- hello-world/hello_world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hello-world/hello_world.py b/hello-world/hello_world.py index 5efcac4..4d0815d 100644 --- a/hello-world/hello_world.py +++ b/hello-world/hello_world.py @@ -1,3 +1,3 @@ -# pylint: disable=C0116 +# pylint: disable=C0116, C0114 def hello(): return 'Hello, World!' From 4161efa7b2ab594d44d05dd522a20a9c20c32903 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:20:15 -0700 Subject: [PATCH 51/75] Update .pylintrc --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index fcbba09..e0ba4ac 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,2 +1,3 @@ [MASTER] ignore=.venv +ignore-paths=*/solutions/* \ No newline at end of file From 1341cb0421b762353dd5420a0a09ee396cf64957 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:23:03 -0700 Subject: [PATCH 52/75] Update .pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index e0ba4ac..78aa507 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,3 +1,3 @@ [MASTER] ignore=.venv -ignore-paths=*/solutions/* \ No newline at end of file +ignore-paths=.*/solutions/.* \ No newline at end of file From d8b9a716c5cc97a1c8c8e66a9dba4fd16b2ee39c Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:29:57 -0700 Subject: [PATCH 53/75] Update .pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 78aa507..97d344e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,3 +1,3 @@ [MASTER] ignore=.venv -ignore-paths=.*/solutions/.* \ No newline at end of file +ignore-paths=^(.*/|)solutions/.*$ \ No newline at end of file From 3f235013f1cae11b94cae2a7e2fbe13638600282 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:31:56 -0700 Subject: [PATCH 54/75] Update conditionals.py --- meltdown-mitigation/conditionals.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index c2fd574..271d883 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -18,7 +18,8 @@ def is_criticality_balanced(temperature, neutrons_emitted) -> bool: def reactor_efficiency(voltage, current, theoretical_max_power) -> str: - """Assess reactor efficiency zone. + """ + Assess reactor efficiency zone. :param voltage: int or float - voltage value. :param current: int or float - current value. @@ -41,9 +42,11 @@ def reactor_efficiency(voltage, current, theoretical_max_power) -> str: if efficiency < 30: return 'black' - elif 30 <= efficiency < 60: + + if 30 <= efficiency < 60: return 'red' - elif 60 <= efficiency < 80: + + if 60 <= efficiency < 80: return 'orange' return 'green' From 20287ea8ac00fe0ed55ec2e4cafa4636d47773ce Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 03:45:03 +0000 Subject: [PATCH 55/75] [Sync Iteration] python/meltdown-mitigation/2 --- .../meltdown-mitigation/2/conditionals.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 solutions/python/meltdown-mitigation/2/conditionals.py diff --git a/solutions/python/meltdown-mitigation/2/conditionals.py b/solutions/python/meltdown-mitigation/2/conditionals.py new file mode 100644 index 0000000..32fc5f5 --- /dev/null +++ b/solutions/python/meltdown-mitigation/2/conditionals.py @@ -0,0 +1,78 @@ +"""Functions to prevent a nuclear meltdown.""" + + +# pylint: disable=C0301 +def is_criticality_balanced(temperature, neutrons_emitted) -> bool: + """ + Verify criticality is balanced. + + :param temperature: int or float - temperature value in kelvin. + :param neutrons_emitted: int or float - number of neutrons emitted per second. + :return: bool - is criticality balanced? + + A reactor is said to be critical if it satisfies the following conditions: + - The temperature is less than 800 K. + - The number of neutrons emitted per second is greater than 500. + - The product of temperature and neutrons emitted per second is less than 500000. + """ + return temperature < 800 and neutrons_emitted > 500 and (temperature * neutrons_emitted) < 500000 + + +def reactor_efficiency(voltage, current, theoretical_max_power) -> str: + """ + Assess reactor efficiency zone. + + :param voltage: int or float - voltage value. + :param current: int or float - current value. + :param theoretical_max_power: int or float - power that corresponds to a 100% efficiency. + :return: str - one of ('green', 'orange', 'red', or 'black'). + + Efficiency can be grouped into 4 bands: + + 1. green -> efficiency of 80% or more, + 2. orange -> efficiency of less than 80% but at least 60%, + 3. red -> efficiency below 60%, but still 30% or more, + 4. black -> less than 30% efficient. + + The percentage value is calculated as + (generated power/ theoretical max power)*100 + where generated power = voltage * current + """ + generated_power = voltage * current + efficiency = (generated_power/theoretical_max_power)*100 + + if efficiency < 30: + return 'black' + + if 30 <= efficiency < 60: + return 'red' + + if 60 <= efficiency < 80: + return 'orange' + + return 'green' + + +def fail_safe(temperature, neutrons_produced_per_second, threshold) -> str: + """ + Assess and return status code for the reactor. + + :param temperature: int or float - value of the temperature in kelvin. + :param neutrons_produced_per_second: int or float - neutron flux. + :param threshold: int or float - threshold for category. + :return: str - one of ('LOW', 'NORMAL', 'DANGER'). + + 1. 'LOW' -> `temperature * neutrons per second` < 90% of `threshold` + 2. 'NORMAL' -> `temperature * neutrons per second` +/- 10% of `threshold` + 3. 'DANGER' -> `temperature * neutrons per second` is not in the above-stated ranges + """ + current_state = (temperature * neutrons_produced_per_second) / 100 + thr_percent = threshold / 100 + + if thr_percent - 10 <= current_state <= thr_percent + 10: + return 'NORMAL' + + if current_state < thr_percent - 10: + return 'LOW' + + return 'DANGER' From f30d1ee02ec61f3036953ba6c4da2f0cb5f1b058 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:47:29 -0700 Subject: [PATCH 56/75] Create config.json --- triangle/.exercism/config.json | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 triangle/.exercism/config.json diff --git a/triangle/.exercism/config.json b/triangle/.exercism/config.json new file mode 100644 index 0000000..041bf28 --- /dev/null +++ b/triangle/.exercism/config.json @@ -0,0 +1,36 @@ +{ + "authors": [], + "contributors": [ + "behrtam", + "BethanyG", + "cmccandless", + "Dog", + "ikhadykin", + "kytrinyx", + "lowks", + "mpatibandla", + "N-Parsons", + "Nishant23", + "pheanex", + "rozuur", + "sjakobi", + "Stigjb", + "tqa236", + "xitanggg", + "YuriyCherniy" + ], + "files": { + "solution": [ + "triangle.py" + ], + "test": [ + "triangle_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", + "source": "The Ruby Koans triangle project, parts 1 & 2", + "source_url": "https://web.archive.org/web/20220831105330/http://rubykoans.com" +} From 6b476bf5661b7c75362456b24da84d87a7e456eb Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 20:47:35 -0700 Subject: [PATCH 57/75] Update conditionals.py --- meltdown-mitigation/conditionals.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index 271d883..7029096 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -3,7 +3,8 @@ # pylint: disable=C0301 def is_criticality_balanced(temperature, neutrons_emitted) -> bool: - """Verify criticality is balanced. + """ + Verify criticality is balanced. :param temperature: int or float - temperature value in kelvin. :param neutrons_emitted: int or float - number of neutrons emitted per second. @@ -53,7 +54,8 @@ def reactor_efficiency(voltage, current, theoretical_max_power) -> str: def fail_safe(temperature, neutrons_produced_per_second, threshold) -> str: - """Assess and return status code for the reactor. + """ + Assess and return status code for the reactor. :param temperature: int or float - value of the temperature in kelvin. :param neutrons_produced_per_second: int or float - neutron flux. From f40cb84322b0b47f7cf03e420a618d766c9f6fb7 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 04:09:39 +0000 Subject: [PATCH 58/75] [Sync Iteration] python/triangle/1 --- solutions/python/triangle/1/triangle.py | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 solutions/python/triangle/1/triangle.py diff --git a/solutions/python/triangle/1/triangle.py b/solutions/python/triangle/1/triangle.py new file mode 100644 index 0000000..4d60e40 --- /dev/null +++ b/solutions/python/triangle/1/triangle.py @@ -0,0 +1,49 @@ +"""Determine if a triangle is equilateral, isosceles, or scalene.""" + + +def equilateral(sides: list) -> bool: + """ + An equilateral triangle has all three sides the same length. + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] == sides[1] == sides[2] + return False + + +def isosceles(sides: list) -> bool: + """ + An isosceles triangle has at least two sides the same length. + (It is sometimes specified as having exactly two sides the same length, + but for the purposes of this exercise we'll say at least two.) + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] == sides[1] or sides[1] == sides[2] or sides[0] == sides[2] + return False + + +def scalene(sides: list) -> bool: + """ + A scalene triangle has all sides of different lengths. + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] != sides[1] and sides[1] != sides[2] and sides[0] != sides[2] + return False + + +def inequality_violation(sides: list) -> bool: + """ + Let a, b, and c be sides of the triangle. + Then all three of the following expressions must be true: + + a + b ≥ c + b + c ≥ a + a + c ≥ b + """ + return sides[0] + sides[1] >= sides[2] and sides[0] + sides[2] >= sides[1] and sides[2] + sides[1] >= sides[0] + + +def check_for_zeroes(sides: list) -> bool: + """No zeroes allowed.""" + if 0 in sides: + return False + return True From bd80626983e63bd73da5e13d6a3cc7189fb0ea93 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:11:30 -0700 Subject: [PATCH 59/75] Create pytest.yml --- .github/workflows/pytest.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..0065102 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,28 @@ +name: Pytest Workflow + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + + - name: Run pytest + run: pytest --verbose --ignore=solutions \ No newline at end of file From b96847851aa580494c4ff1f4f199b5ee3c5cf5b8 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:12:57 -0700 Subject: [PATCH 60/75] Update pytest.yml --- .github/workflows/pytest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 0065102..c9c9d86 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,6 +22,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install -r requirements.txt pip install pytest - name: Run pytest From 564ba7d76fb88b340ad66061e85dadd7d03f4a64 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:15:17 -0700 Subject: [PATCH 61/75] Update pylint.yml --- .github/workflows/pylint.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 2c8c785..4e975a7 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -1,6 +1,10 @@ name: Pylint -on: [push] +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] jobs: build: From d6d6bbe2254d2e868ada1a234a1722d643dc3848 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:24:27 -0700 Subject: [PATCH 62/75] Triangle --- triangle/.exercism/metadata.json | 1 + triangle/HELP.md | 130 +++++++++++++++++++++++++++++++ triangle/README.md | 60 ++++++++++++++ triangle/triangle.py | 49 ++++++++++++ triangle/triangle_test.py | 80 +++++++++++++++++++ 5 files changed, 320 insertions(+) create mode 100644 triangle/.exercism/metadata.json create mode 100644 triangle/HELP.md create mode 100644 triangle/README.md create mode 100644 triangle/triangle.py create mode 100644 triangle/triangle_test.py diff --git a/triangle/.exercism/metadata.json b/triangle/.exercism/metadata.json new file mode 100644 index 0000000..0b73088 --- /dev/null +++ b/triangle/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"triangle","id":"6f8c14ada1ce403c975161c5a18528c2","url":"https://exercism.org/tracks/python/exercises/triangle","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/triangle/HELP.md b/triangle/HELP.md new file mode 100644 index 0000000..2d9233f --- /dev/null +++ b/triangle/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit triangle.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/triangle/README.md b/triangle/README.md new file mode 100644 index 0000000..961c673 --- /dev/null +++ b/triangle/README.md @@ -0,0 +1,60 @@ +# Triangle + +Welcome to Triangle on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +Determine if a triangle is equilateral, isosceles, or scalene. + +An _equilateral_ triangle has all three sides the same length. + +An _isosceles_ triangle has at least two sides the same length. +(It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) + +A _scalene_ triangle has all sides of different lengths. + +## Note + +For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. + +In equations: + +Let `a`, `b`, and `c` be sides of the triangle. +Then all three of the following expressions must be true: + +```text +a + b ≥ c +b + c ≥ a +a + c ≥ b +``` + +See [Triangle Inequality][triangle-inequality] + +[triangle-inequality]: https://en.wikipedia.org/wiki/Triangle_inequality + +## Source + +### Contributed to by + +- @behrtam +- @BethanyG +- @cmccandless +- @Dog +- @ikhadykin +- @kytrinyx +- @lowks +- @mpatibandla +- @N-Parsons +- @Nishant23 +- @pheanex +- @rozuur +- @sjakobi +- @Stigjb +- @tqa236 +- @xitanggg +- @YuriyCherniy + +### Based on + +The Ruby Koans triangle project, parts 1 & 2 - https://web.archive.org/web/20220831105330/http://rubykoans.com \ No newline at end of file diff --git a/triangle/triangle.py b/triangle/triangle.py new file mode 100644 index 0000000..4d60e40 --- /dev/null +++ b/triangle/triangle.py @@ -0,0 +1,49 @@ +"""Determine if a triangle is equilateral, isosceles, or scalene.""" + + +def equilateral(sides: list) -> bool: + """ + An equilateral triangle has all three sides the same length. + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] == sides[1] == sides[2] + return False + + +def isosceles(sides: list) -> bool: + """ + An isosceles triangle has at least two sides the same length. + (It is sometimes specified as having exactly two sides the same length, + but for the purposes of this exercise we'll say at least two.) + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] == sides[1] or sides[1] == sides[2] or sides[0] == sides[2] + return False + + +def scalene(sides: list) -> bool: + """ + A scalene triangle has all sides of different lengths. + """ + if check_for_zeroes(sides) and inequality_violation(sides): + return sides[0] != sides[1] and sides[1] != sides[2] and sides[0] != sides[2] + return False + + +def inequality_violation(sides: list) -> bool: + """ + Let a, b, and c be sides of the triangle. + Then all three of the following expressions must be true: + + a + b ≥ c + b + c ≥ a + a + c ≥ b + """ + return sides[0] + sides[1] >= sides[2] and sides[0] + sides[2] >= sides[1] and sides[2] + sides[1] >= sides[0] + + +def check_for_zeroes(sides: list) -> bool: + """No zeroes allowed.""" + if 0 in sides: + return False + return True diff --git a/triangle/triangle_test.py b/triangle/triangle_test.py new file mode 100644 index 0000000..b279c83 --- /dev/null +++ b/triangle/triangle_test.py @@ -0,0 +1,80 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json +# File last updated on 2023-07-19 + +import unittest + +from triangle import ( + equilateral, + isosceles, + scalene, +) + + +class EquilateralTriangleTest(unittest.TestCase): + def test_all_sides_are_equal(self): + self.assertIs(equilateral([2, 2, 2]), True) + + def test_any_side_is_unequal(self): + self.assertIs(equilateral([2, 3, 2]), False) + + def test_no_sides_are_equal(self): + self.assertIs(equilateral([5, 4, 6]), False) + + def test_all_zero_sides_is_not_a_triangle(self): + self.assertIs(equilateral([0, 0, 0]), False) + + def test_sides_may_be_floats(self): + self.assertIs(equilateral([0.5, 0.5, 0.5]), True) + + +class IsoscelesTriangleTest(unittest.TestCase): + def test_last_two_sides_are_equal(self): + self.assertIs(isosceles([3, 4, 4]), True) + + def test_first_two_sides_are_equal(self): + self.assertIs(isosceles([4, 4, 3]), True) + + def test_first_and_last_sides_are_equal(self): + self.assertIs(isosceles([4, 3, 4]), True) + + def test_equilateral_triangles_are_also_isosceles(self): + self.assertIs(isosceles([4, 4, 4]), True) + + def test_no_sides_are_equal(self): + self.assertIs(isosceles([2, 3, 4]), False) + + def test_first_triangle_inequality_violation(self): + self.assertIs(isosceles([1, 1, 3]), False) + + def test_second_triangle_inequality_violation(self): + self.assertIs(isosceles([1, 3, 1]), False) + + def test_third_triangle_inequality_violation(self): + self.assertIs(isosceles([3, 1, 1]), False) + + def test_sides_may_be_floats(self): + self.assertIs(isosceles([0.5, 0.4, 0.5]), True) + + +class ScaleneTriangleTest(unittest.TestCase): + def test_no_sides_are_equal(self): + self.assertIs(scalene([5, 4, 6]), True) + + def test_all_sides_are_equal(self): + self.assertIs(scalene([4, 4, 4]), False) + + def test_first_and_second_sides_are_equal(self): + self.assertIs(scalene([4, 4, 3]), False) + + def test_first_and_third_sides_are_equal(self): + self.assertIs(scalene([3, 4, 3]), False) + + def test_second_and_third_sides_are_equal(self): + self.assertIs(scalene([4, 3, 3]), False) + + def test_may_not_violate_triangle_inequality(self): + self.assertIs(scalene([7, 3, 2]), False) + + def test_sides_may_be_floats(self): + self.assertIs(scalene([0.5, 0.4, 0.6]), True) From f56652b07b2d6b121bdb043ffc1c77d0fabdcd9c Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 04:30:29 +0000 Subject: [PATCH 63/75] [Sync Iteration] python/triangle/2 --- solutions/python/triangle/2/triangle.py | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 solutions/python/triangle/2/triangle.py diff --git a/solutions/python/triangle/2/triangle.py b/solutions/python/triangle/2/triangle.py new file mode 100644 index 0000000..bac8e96 --- /dev/null +++ b/solutions/python/triangle/2/triangle.py @@ -0,0 +1,47 @@ +"""Determine if a triangle is equilateral, isosceles, or scalene.""" + + +def equilateral(sides: list) -> bool: + """ + An equilateral triangle has all three sides the same length. + """ + if all_sides_positive(sides) and no_inequality_violation(sides): + return sides[0] == sides[1] == sides[2] + return False + + +def isosceles(sides: list) -> bool: + """ + An isosceles triangle has at least two sides the same length. + (It is sometimes specified as having exactly two sides the same length, + but for the purposes of this exercise we'll say at least two.) + """ + if all_sides_positive(sides) and no_inequality_violation(sides): + return sides[0] == sides[1] or sides[1] == sides[2] or sides[0] == sides[2] + return False + + +def scalene(sides: list) -> bool: + """ + A scalene triangle has all sides of different lengths. + """ + if all_sides_positive(sides) and no_inequality_violation(sides): + return sides[0] != sides[1] and sides[1] != sides[2] and sides[0] != sides[2] + return False + + +def no_inequality_violation(sides: list) -> bool: + """ + Let a, b, and c be sides of the triangle. + Then all three of the following expressions must be true: + + a + b ≥ c + b + c ≥ a + a + c ≥ b + """ + return sides[0] + sides[1] >= sides[2] and sides[0] + sides[2] >= sides[1] and sides[2] + sides[1] >= sides[0] + + +def all_sides_positive(sides: list) -> bool: + """No zeroes or negative numbers allowed.""" + return all(side > 0 for side in sides) and len(sides) == 3 From 6256a73bbbcbbc5560f1dd012d87afb9e3f81acb Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:36:33 -0700 Subject: [PATCH 64/75] Update triangle_test.py --- triangle/triangle_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/triangle/triangle_test.py b/triangle/triangle_test.py index b279c83..b76a82f 100644 --- a/triangle/triangle_test.py +++ b/triangle/triangle_test.py @@ -11,6 +11,7 @@ ) +# pylint: disable=C0116 class EquilateralTriangleTest(unittest.TestCase): def test_all_sides_are_equal(self): self.assertIs(equilateral([2, 2, 2]), True) @@ -28,6 +29,7 @@ def test_sides_may_be_floats(self): self.assertIs(equilateral([0.5, 0.5, 0.5]), True) +# pylint: disable=C0116 class IsoscelesTriangleTest(unittest.TestCase): def test_last_two_sides_are_equal(self): self.assertIs(isosceles([3, 4, 4]), True) @@ -57,6 +59,7 @@ def test_sides_may_be_floats(self): self.assertIs(isosceles([0.5, 0.4, 0.5]), True) +# pylint: disable=C0116 class ScaleneTriangleTest(unittest.TestCase): def test_no_sides_are_equal(self): self.assertIs(scalene([5, 4, 6]), True) From 1f1bc6ac9c422ef8247d72e39b4342b650934627 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:36:36 -0700 Subject: [PATCH 65/75] Update triangle.py --- triangle/triangle.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/triangle/triangle.py b/triangle/triangle.py index 4d60e40..ead093f 100644 --- a/triangle/triangle.py +++ b/triangle/triangle.py @@ -5,7 +5,7 @@ def equilateral(sides: list) -> bool: """ An equilateral triangle has all three sides the same length. """ - if check_for_zeroes(sides) and inequality_violation(sides): + if all_sides_positive(sides) and no_inequality_violation(sides): return sides[0] == sides[1] == sides[2] return False @@ -16,7 +16,7 @@ def isosceles(sides: list) -> bool: (It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) """ - if check_for_zeroes(sides) and inequality_violation(sides): + if all_sides_positive(sides) and no_inequality_violation(sides): return sides[0] == sides[1] or sides[1] == sides[2] or sides[0] == sides[2] return False @@ -25,12 +25,12 @@ def scalene(sides: list) -> bool: """ A scalene triangle has all sides of different lengths. """ - if check_for_zeroes(sides) and inequality_violation(sides): + if all_sides_positive(sides) and no_inequality_violation(sides): return sides[0] != sides[1] and sides[1] != sides[2] and sides[0] != sides[2] return False -def inequality_violation(sides: list) -> bool: +def no_inequality_violation(sides: list) -> bool: """ Let a, b, and c be sides of the triangle. Then all three of the following expressions must be true: @@ -42,8 +42,9 @@ def inequality_violation(sides: list) -> bool: return sides[0] + sides[1] >= sides[2] and sides[0] + sides[2] >= sides[1] and sides[2] + sides[1] >= sides[0] -def check_for_zeroes(sides: list) -> bool: - """No zeroes allowed.""" - if 0 in sides: - return False - return True +def all_sides_positive(sides: list) -> bool: + """ + No zeroes or negative numbers allowed. + All triangles should have 3 sides. + """ + return all(side > 0 for side in sides) and len(sides) == 3 From c76b668eb59746a9f518c46fcecff2f70eab1f65 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:37:04 -0700 Subject: [PATCH 66/75] Update triangle.py --- triangle/triangle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/triangle/triangle.py b/triangle/triangle.py index ead093f..6537e75 100644 --- a/triangle/triangle.py +++ b/triangle/triangle.py @@ -39,7 +39,9 @@ def no_inequality_violation(sides: list) -> bool: b + c ≥ a a + c ≥ b """ - return sides[0] + sides[1] >= sides[2] and sides[0] + sides[2] >= sides[1] and sides[2] + sides[1] >= sides[0] + return (sides[0] + sides[1] >= sides[2] and + sides[0] + sides[2] >= sides[1] and + sides[2] + sides[1] >= sides[0]) def all_sides_positive(sides: list) -> bool: From c932f460945ee64dbdab5853e4888453b05fb7e9 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 04:37:19 +0000 Subject: [PATCH 67/75] [Sync Iteration] python/triangle/3 --- solutions/python/triangle/3/triangle.py | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 solutions/python/triangle/3/triangle.py diff --git a/solutions/python/triangle/3/triangle.py b/solutions/python/triangle/3/triangle.py new file mode 100644 index 0000000..6537e75 --- /dev/null +++ b/solutions/python/triangle/3/triangle.py @@ -0,0 +1,52 @@ +"""Determine if a triangle is equilateral, isosceles, or scalene.""" + + +def equilateral(sides: list) -> bool: + """ + An equilateral triangle has all three sides the same length. + """ + if all_sides_positive(sides) and no_inequality_violation(sides): + return sides[0] == sides[1] == sides[2] + return False + + +def isosceles(sides: list) -> bool: + """ + An isosceles triangle has at least two sides the same length. + (It is sometimes specified as having exactly two sides the same length, + but for the purposes of this exercise we'll say at least two.) + """ + if all_sides_positive(sides) and no_inequality_violation(sides): + return sides[0] == sides[1] or sides[1] == sides[2] or sides[0] == sides[2] + return False + + +def scalene(sides: list) -> bool: + """ + A scalene triangle has all sides of different lengths. + """ + if all_sides_positive(sides) and no_inequality_violation(sides): + return sides[0] != sides[1] and sides[1] != sides[2] and sides[0] != sides[2] + return False + + +def no_inequality_violation(sides: list) -> bool: + """ + Let a, b, and c be sides of the triangle. + Then all three of the following expressions must be true: + + a + b ≥ c + b + c ≥ a + a + c ≥ b + """ + return (sides[0] + sides[1] >= sides[2] and + sides[0] + sides[2] >= sides[1] and + sides[2] + sides[1] >= sides[0]) + + +def all_sides_positive(sides: list) -> bool: + """ + No zeroes or negative numbers allowed. + All triangles should have 3 sides. + """ + return all(side > 0 for side in sides) and len(sides) == 3 From 86f9ddda8c1b2d2b34c2f781857226d2bbdbeca0 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:39:03 -0700 Subject: [PATCH 68/75] Update triangle_test.py --- triangle/triangle_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/triangle/triangle_test.py b/triangle/triangle_test.py index b76a82f..9312b90 100644 --- a/triangle/triangle_test.py +++ b/triangle/triangle_test.py @@ -1,3 +1,5 @@ +# pylint: disable=C0116, C0114, C0115 + # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json # File last updated on 2023-07-19 @@ -11,7 +13,7 @@ ) -# pylint: disable=C0116 +# pylint: disable=C0116, C0114, C0115 class EquilateralTriangleTest(unittest.TestCase): def test_all_sides_are_equal(self): self.assertIs(equilateral([2, 2, 2]), True) From b84570cdbb5e84325a6126b63e27ed48ece29de9 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 21:46:24 -0700 Subject: [PATCH 69/75] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7770891..860e2ec 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # [Exercism Python Track](https://exercism.io/my/tracks/python) +[![Pytest Workflow](https://github.com/ikostan/python/actions/workflows/pytest.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pytest.yml) +[![Pylint](https://github.com/ikostan/python/actions/workflows/pylint.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pylint.yml) +
-
From 8b3679c857950135b2cdf05fd0dd9a740c9f470b Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 22:18:20 -0700 Subject: [PATCH 70/75] Ruff --- .github/workflows/ruff.yml | 21 +++++++++++++ pyproject.toml | 60 +++++++++++++++++++++++++++++++++++++ requirements.txt | Bin 470 -> 496 bytes 3 files changed, 81 insertions(+) create mode 100644 .github/workflows/ruff.yml create mode 100644 pyproject.toml diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..f01a2a3 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,21 @@ +name: Ruff Lint and Format + +on: [push, pull_request] + +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install Ruff + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run Ruff lint + run: ruff check --output-format=github . + - name: Run Ruff format check + run: ruff format --check . \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..50718dd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,60 @@ +[tool.ruff] +# Exclude commonly ignored directories +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +# Line length and indentation settings, same as Black +line-length = 88 +indent-width = 4 + +# Set target version to Python 3.12 +target-version = "py312" + +[tool.ruff.lint] +# Enable Pyflakes (F) and a subset of pycodestyle (E) codes by default +select = ["E4", "E7", "E9", "F"] +ignore = [] + +# Allow fixes for all enabled rules when --fix is provided +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Enable formatter settings, similar to Black +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +# Auto-formatting of code examples in docstrings is currently disabled by default +docstring-code-format = false +docstring-code-line-length = "dynamic" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ad58882afce5f0a1f504e0fdc86c550ca687c885..44bbf70f2f84f6ba7c278ad0f37467689713853d 100644 GIT binary patch delta 37 mcmcb{{DFDH7DmY;hEj$!AhZQS0|q??Lk1%-X)t*`qcQ-w!w4<_ delta 16 Xcmeyse2sa-7RJe!80A=a8Mqh#H}C|} From a77d89126905e244a44805e202d4d7a4c62e4b6c Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 22:20:32 -0700 Subject: [PATCH 71/75] Update pyproject.toml --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 50718dd..36d7520 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,9 @@ exclude = [ "node_modules", "site-packages", "venv", + "tests/", + "*_test.py", + "test_*.py" ] # Line length and indentation settings, same as Black From 60880d231426f80126278541197c4cc79cb99eff Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 22:21:41 -0700 Subject: [PATCH 72/75] Update pyproject.toml --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 36d7520..8418471 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,8 @@ exclude = [ "venv", "tests/", "*_test.py", - "test_*.py" + "test_*.py", + "solutions/" ] # Line length and indentation settings, same as Black From a026afd0c56f118cbfc7c7fbdf252ad8c3ecd35d Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 22:26:35 -0700 Subject: [PATCH 73/75] Ruff format changes --- black-jack/black_jack.py | 10 +++++----- card-games/lists.py | 3 +-- chaitanas-colossal-coaster/list_methods.py | 23 ++++++++++------------ currency-exchange/exchange.py | 22 ++++++++------------- ghost-gobble-arcade-game/arcade_game.py | 6 +++--- grains/grains.py | 2 +- guidos-gorgeous-lasagna/lasagna.py | 7 +++---- hello-world/hello_world.py | 2 +- little-sisters-vocab/strings.py | 10 +++++----- making-the-grade/loops.py | 14 +++++++------ meltdown-mitigation/conditionals.py | 22 ++++++++++++--------- triangle/triangle.py | 8 +++++--- 12 files changed, 63 insertions(+), 66 deletions(-) diff --git a/black-jack/black_jack.py b/black-jack/black_jack.py index fb9dec8..4d9a3fe 100644 --- a/black-jack/black_jack.py +++ b/black-jack/black_jack.py @@ -17,10 +17,10 @@ def value_of_card(card) -> int: 2. 'A' (ace card) = 1 3. '2' - '10' = numerical value. """ - if card in 'JKQ': + if card in "JKQ": return 10 - if card == 'A': + if card == "A": return 1 return int(card) @@ -62,7 +62,7 @@ def value_of_ace(card_one, card_two) -> int: total: int = value_of_card(card_one) + value_of_card(card_two) # Hint: if we already have an ace in hand, then the value for # the upcoming ace would be 1. - if card_one == 'A' or card_two == 'A': + if card_one == "A" or card_two == "A": return 1 # The value of the hand with the ace needs to be as high as # possible without going over 21. @@ -89,10 +89,10 @@ def is_blackjack(card_one, card_two) -> bool: """ # If a player is dealt an ace (A) and a ten-card (10, K, Q, or J) # as their first two cards, then the player has a score of 21. - if card_one == 'A' and card_two in ('J', 'Q', 'K', '10'): + if card_one == "A" and card_two in ("J", "Q", "K", "10"): return True - if card_two == 'A' and card_one in ('J', 'Q', 'K', '10'): + if card_two == "A" and card_one in ("J", "Q", "K", "10"): return True return False diff --git a/card-games/lists.py b/card-games/lists.py index 55f76fc..ba00613 100644 --- a/card-games/lists.py +++ b/card-games/lists.py @@ -15,8 +15,7 @@ def get_rounds(number: int) -> list[int]: return [number, number + 1, number + 2] -def concatenate_rounds(rounds_1: list[int], - rounds_2: list[int]) -> list[int]: +def concatenate_rounds(rounds_1: list[int], rounds_2: list[int]) -> list[int]: """ Concatenate two lists of round numbers. diff --git a/chaitanas-colossal-coaster/list_methods.py b/chaitanas-colossal-coaster/list_methods.py index 029b503..4e24015 100644 --- a/chaitanas-colossal-coaster/list_methods.py +++ b/chaitanas-colossal-coaster/list_methods.py @@ -1,10 +1,12 @@ """Functions to manage and organize queues at Chaitana's roller coaster.""" -def add_me_to_the_queue(express_queue: list[str], - normal_queue: list[str], - ticket_type: int, - person_name: str) -> list[str]: +def add_me_to_the_queue( + express_queue: list[str], + normal_queue: list[str], + ticket_type: int, + person_name: str, +) -> list[str]: """ Add a person to the 'express' or 'normal' queue depending on the ticket number. @@ -22,8 +24,7 @@ def add_me_to_the_queue(express_queue: list[str], return normal_queue -def find_my_friend(queue: list[str], - friend_name: str) -> int: +def find_my_friend(queue: list[str], friend_name: str) -> int: """ Search the queue for a name and return their queue position (index). @@ -34,9 +35,7 @@ def find_my_friend(queue: list[str], return queue.index(friend_name) -def add_me_with_my_friends(queue: list[str], - index: int, - person_name: str) -> list[str]: +def add_me_with_my_friends(queue: list[str], index: int, person_name: str) -> list[str]: """ Insert the late arrival's name at a specific index of the queue. @@ -49,8 +48,7 @@ def add_me_with_my_friends(queue: list[str], return queue -def remove_the_mean_person(queue: list[str], - person_name: str) -> list[str]: +def remove_the_mean_person(queue: list[str], person_name: str) -> list[str]: """ Remove the mean person from the queue by the provided name. @@ -62,8 +60,7 @@ def remove_the_mean_person(queue: list[str], return queue -def how_many_namefellows(queue: list[str], - person_name: str) -> int: +def how_many_namefellows(queue: list[str], person_name: str) -> int: """ Count how many times the provided name appears in the queue. diff --git a/currency-exchange/exchange.py b/currency-exchange/exchange.py index ee23d07..25025a1 100644 --- a/currency-exchange/exchange.py +++ b/currency-exchange/exchange.py @@ -9,8 +9,7 @@ """ -def exchange_money(budget: float, - exchange_rate: float) -> float: +def exchange_money(budget: float, exchange_rate: float) -> float: """ Return the value of the exchanged currency. @@ -24,8 +23,7 @@ def exchange_money(budget: float, return budget / exchange_rate -def get_change(budget: float, - exchanging_value: float) -> float: +def get_change(budget: float, exchanging_value: float) -> float: """ Return the amount of money that "is left" from the budget. @@ -36,8 +34,7 @@ def get_change(budget: float, return budget - exchanging_value # pylint: disable=R0801 -def get_value_of_bills(denomination: float, - number_of_bills: float) -> float: +def get_value_of_bills(denomination: float, number_of_bills: float) -> float: """ Return only the total value of the bills (excluding fractional amounts) the booth would give back. @@ -52,8 +49,7 @@ def get_value_of_bills(denomination: float, return denomination * number_of_bills -def get_number_of_bills(amount: float, - denomination: int) -> int: +def get_number_of_bills(amount: float, denomination: int) -> int: """ Return the _number of currency bills_ that you can receive within the given _amount_. @@ -64,8 +60,7 @@ def get_number_of_bills(amount: float, return int(amount // denomination) -def get_leftover_of_bills(amount: float, - denomination: int) -> float: +def get_leftover_of_bills(amount: float, denomination: int) -> float: """ Return the _leftover amount_ that cannot be returned from your starting _amount_ given the denomination of bills. @@ -78,10 +73,9 @@ def get_leftover_of_bills(amount: float, # pylint: disable=R0801 -def exchangeable_value(budget: float, - exchange_rate: float, - spread: int, - denomination: int) -> int: +def exchangeable_value( + budget: float, exchange_rate: float, spread: int, denomination: int +) -> int: """ Return the maximum value of the new currency after calculating the *exchange rate* plus the *spread*. diff --git a/ghost-gobble-arcade-game/arcade_game.py b/ghost-gobble-arcade-game/arcade_game.py index eb5fe64..3cb4bc2 100644 --- a/ghost-gobble-arcade-game/arcade_game.py +++ b/ghost-gobble-arcade-game/arcade_game.py @@ -34,9 +34,9 @@ def lose(power_pellet_active: bool, touching_ghost: bool) -> bool: return touching_ghost and not power_pellet_active -def win(has_eaten_all_dots: bool, - power_pellet_active: bool, - touching_ghost: bool) -> bool: +def win( + has_eaten_all_dots: bool, power_pellet_active: bool, touching_ghost: bool +) -> bool: """ Trigger the victory event when all dots have been eaten. diff --git a/grains/grains.py b/grains/grains.py index 277079e..b86139f 100644 --- a/grains/grains.py +++ b/grains/grains.py @@ -31,4 +31,4 @@ def total() -> int: :rtype: int """ # return sum(square(sqr) for sqr in range(1, 65)) - return 2 ** 64 - 1 + return 2**64 - 1 diff --git a/guidos-gorgeous-lasagna/lasagna.py b/guidos-gorgeous-lasagna/lasagna.py index 577645a..99722f8 100644 --- a/guidos-gorgeous-lasagna/lasagna.py +++ b/guidos-gorgeous-lasagna/lasagna.py @@ -8,7 +8,6 @@ of a module and its functions and/or classes. """ - EXPECTED_BAKE_TIME: int = 40 PREPARATION_TIME: int = 2 @@ -45,8 +44,7 @@ def preparation_time_in_minutes(number_of_layers: int) -> int: return int(PREPARATION_TIME * number_of_layers) -def elapsed_time_in_minutes(number_of_layers: int, - elapsed_bake_time: int) -> int: +def elapsed_time_in_minutes(number_of_layers: int, elapsed_bake_time: int) -> int: """ Calculate elapsed time in minutes. @@ -62,4 +60,5 @@ def elapsed_time_in_minutes(number_of_layers: int, :rtype: int """ return preparation_time_in_minutes(number_of_layers=number_of_layers) + ( - EXPECTED_BAKE_TIME - bake_time_remaining(elapsed_bake_time)) + EXPECTED_BAKE_TIME - bake_time_remaining(elapsed_bake_time) + ) diff --git a/hello-world/hello_world.py b/hello-world/hello_world.py index 4d0815d..318a3bc 100644 --- a/hello-world/hello_world.py +++ b/hello-world/hello_world.py @@ -1,3 +1,3 @@ # pylint: disable=C0116, C0114 def hello(): - return 'Hello, World!' + return "Hello, World!" diff --git a/little-sisters-vocab/strings.py b/little-sisters-vocab/strings.py index f94c355..419c4a4 100644 --- a/little-sisters-vocab/strings.py +++ b/little-sisters-vocab/strings.py @@ -8,7 +8,7 @@ def add_prefix_un(word: str) -> str: :param word: str - containing the root word. :return: str - of root word prepended with 'un'. """ - return f'un{word}' + return f"un{word}" def make_word_groups(vocab_words: list[str]) -> str: @@ -27,7 +27,7 @@ def make_word_groups(vocab_words: list[str]) -> str: For example: list('en', 'close', 'joy', 'lighten'), produces the following string: 'en :: enclose :: enjoy :: enlighten'. """ - return f' :: {vocab_words[0]}'.join(vocab_words) + return f" :: {vocab_words[0]}".join(vocab_words) def remove_suffix_ness(word: str) -> str: @@ -39,7 +39,7 @@ def remove_suffix_ness(word: str) -> str: For example: "heaviness" becomes "heavy", but "sadness" becomes "sad". """ - return f'{word[:-5]}y' if word[-5] == 'i' else word[:-4] + return f"{word[:-5]}y" if word[-5] == "i" else word[:-4] def adjective_to_verb(sentence: str, index: int) -> str: @@ -53,5 +53,5 @@ def adjective_to_verb(sentence: str, index: int) -> str: For example, ("It got dark as the sun set.", 2) becomes "darken". """ words: list[str] = sentence.split() - word: str = words[index].strip('.,!?;:') - return f'{word}en' + word: str = words[index].strip(".,!?;:") + return f"{word}en" diff --git a/making-the-grade/loops.py b/making-the-grade/loops.py index 06668b7..3889505 100644 --- a/making-the-grade/loops.py +++ b/making-the-grade/loops.py @@ -21,8 +21,7 @@ def count_failed_students(student_scores: list) -> int: return len([score for score in student_scores if score <= 40.0]) # pylint: disable=R0801 -def above_threshold(student_scores: list, - threshold: int) -> list: +def above_threshold(student_scores: list, threshold: int) -> list: """ Filter out above threshold scores. @@ -55,8 +54,7 @@ def letter_grades(highest: int) -> list: # pylint: disable=R0801 -def student_ranking(student_scores: list, - student_names: list) -> list[str]: +def student_ranking(student_scores: list, student_names: list) -> list[str]: """ Organize the student's rank, name, and grade information in descending order. @@ -64,8 +62,12 @@ def student_ranking(student_scores: list, :param student_names: list - of string names by exam score in descending order. :return: list - of strings in format [". : "]. """ - return [f"{i}. {name}: {score}" for i, score, name in zip( - range(1, len(student_scores) + 1), student_scores, student_names)] + return [ + f"{i}. {name}: {score}" + for i, score, name in zip( + range(1, len(student_scores) + 1), student_scores, student_names + ) + ] # pylint: disable=R0801 diff --git a/meltdown-mitigation/conditionals.py b/meltdown-mitigation/conditionals.py index 7029096..e26fa94 100644 --- a/meltdown-mitigation/conditionals.py +++ b/meltdown-mitigation/conditionals.py @@ -15,7 +15,11 @@ def is_criticality_balanced(temperature, neutrons_emitted) -> bool: - The number of neutrons emitted per second is greater than 500. - The product of temperature and neutrons emitted per second is less than 500000. """ - return temperature < 800 and neutrons_emitted > 500 and (temperature * neutrons_emitted) < 500000 + return ( + temperature < 800 + and neutrons_emitted > 500 + and (temperature * neutrons_emitted) < 500000 + ) def reactor_efficiency(voltage, current, theoretical_max_power) -> str: @@ -39,18 +43,18 @@ def reactor_efficiency(voltage, current, theoretical_max_power) -> str: where generated power = voltage * current """ generated_power = voltage * current - efficiency = (generated_power/theoretical_max_power)*100 + efficiency = (generated_power / theoretical_max_power) * 100 if efficiency < 30: - return 'black' + return "black" if 30 <= efficiency < 60: - return 'red' + return "red" if 60 <= efficiency < 80: - return 'orange' + return "orange" - return 'green' + return "green" def fail_safe(temperature, neutrons_produced_per_second, threshold) -> str: @@ -70,9 +74,9 @@ def fail_safe(temperature, neutrons_produced_per_second, threshold) -> str: thr_percent = threshold / 100 if thr_percent - 10 <= current_state <= thr_percent + 10: - return 'NORMAL' + return "NORMAL" if current_state < thr_percent - 10: - return 'LOW' + return "LOW" - return 'DANGER' + return "DANGER" diff --git a/triangle/triangle.py b/triangle/triangle.py index 6537e75..eefee03 100644 --- a/triangle/triangle.py +++ b/triangle/triangle.py @@ -39,9 +39,11 @@ def no_inequality_violation(sides: list) -> bool: b + c ≥ a a + c ≥ b """ - return (sides[0] + sides[1] >= sides[2] and - sides[0] + sides[2] >= sides[1] and - sides[2] + sides[1] >= sides[0]) + return ( + sides[0] + sides[1] >= sides[2] + and sides[0] + sides[2] >= sides[1] + and sides[2] + sides[1] >= sides[0] + ) def all_sides_positive(sides: list) -> bool: From edebb649a44cc083af6ea2a36ed8311b1ddc925c Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 22:27:29 -0700 Subject: [PATCH 74/75] Update ruff.yml --- .github/workflows/ruff.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index f01a2a3..f493ef7 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -1,6 +1,10 @@ name: Ruff Lint and Format -on: [push, pull_request] +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] jobs: ruff: From 99ba2bc452e418745e73c89cb3a0ef4f78ab96c9 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 25 Aug 2025 22:33:20 -0700 Subject: [PATCH 75/75] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 860e2ec..078237e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Pytest Workflow](https://github.com/ikostan/python/actions/workflows/pytest.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pytest.yml) [![Pylint](https://github.com/ikostan/python/actions/workflows/pylint.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pylint.yml) +[![Ruff Lint and Format](https://github.com/ikostan/python/actions/workflows/ruff.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/ruff.yml)