mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-30 16:01:14 -04:00
feat(curriculum): add discount calculator workshop (#62644)
Co-authored-by: Dario <105294544+Dario-DC@users.noreply.github.com> Co-authored-by: Ilenia <26656284+ilenia-magoni@users.noreply.github.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -4661,9 +4661,12 @@
|
||||
"title": "Understanding Abstraction",
|
||||
"intro": ["Learn about Understanding Abstraction in these lessons."]
|
||||
},
|
||||
"workshop-placeholder-oop-3": {
|
||||
"title": "Placeholder - Waiting for title",
|
||||
"intro": [""]
|
||||
"workshop-discount-calculator": {
|
||||
"title": "Build a Discount Calculator",
|
||||
"intro": [
|
||||
"Build a Discount Calculator",
|
||||
"In this workshop you will build a flexible discount pricing calculator through abstract base classes, allowing multiple discount algorithms to be applied interchangeably without modifying the core logic."
|
||||
]
|
||||
},
|
||||
"lab-player-interface": {
|
||||
"title": "Build a Player Interface",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the Build a Discount Calculator
|
||||
block: workshop-discount-calculator
|
||||
superBlock: full-stack-developer
|
||||
---
|
||||
|
||||
## Introduction to the Build a Discount Calculator
|
||||
|
||||
In this workshop you will build a flexible discount pricing calculator through abstract base classes, allowing multiple discount algorithms to be applied interchangeably without modifying the core logic.
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: 68e524c94e2e4ab7b4bfc7e2
|
||||
title: Step 1
|
||||
challengeType: 20
|
||||
dashedName: step-1
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this workshop, you are going to build a discount calculator that can apply different discount strategies to products. The system will determine the best price for a customer based on multiple discount rules.
|
||||
|
||||
Start by defining a class named `Product`. Give your class an `__init__` method with the following parameters: `self`, `name`, and `price`. Inside the `__init__` method, assign `name` and `price` parameters to instance attributes with the same name.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a class named `Product`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_class("Product")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `Product` class should have an `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("Product").has_function("__init__")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `__init__` method should have the following parameters: `self`, `name`, and `price`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_args("self, name, price")`)
|
||||
})
|
||||
```
|
||||
|
||||
You should assign `name` to `self.name` inside your `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_stmt("self.name = name")`)
|
||||
})
|
||||
```
|
||||
|
||||
You should assign `price` to `self.price` inside your `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_stmt("self.price = price")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: 68e529226b0defdff26c4a89
|
||||
title: Step 3
|
||||
challengeType: 20
|
||||
dashedName: step-3
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Right now, if you create an instance of `Product` and print it, you'll see a default object representation that isn't very informative. For example, it might look something like `<__main__.Product object at 0x7f9b8c2d1d30>`.
|
||||
|
||||
To convert the default object representation into a readable string, create a `__str__` method in your `Product` class that returns a string with the format `name - $price` (where `name` and `price` should be replaced with the values of the corresponding attributes).
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `Product` class should have a `__str__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("Product").has_function("__str__")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `__str__` method should return a string in the format `name - $price`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
test_product = Product("Test Item", 25.5)
|
||||
assert str(test_product) == "Test Item - $25.5"
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float):
|
||||
self.name = name
|
||||
self.price = price
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
id: 68e529226b0defdff26c4a8a
|
||||
title: Step 6
|
||||
challengeType: 20
|
||||
dashedName: step-6
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now, create an instance of `Product` with the name `Wireless Mouse` and a price of `50.0`. Assign it to a variable named `product`. Then, print `product` to the console to see what it looks like.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should have a variable named `product`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_variable("product")`)
|
||||
})
|
||||
```
|
||||
|
||||
You should assign an instance of `Product` to the `product` variable.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert isinstance(product, Product)`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `Product` instance should have the name `Wireless Mouse` and price `50.0`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert product.name == 'Wireless Mouse'
|
||||
assert product.price == 50.0
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
You should print `product` to the console.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_call("print(product)")`)
|
||||
})
|
||||
```
|
||||
|
||||
When you print a `Product` with name `'Wireless Mouse'` and price `50.0`, it should display `Wireless Mouse - $50.0`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
import io
|
||||
import sys
|
||||
captured_output = io.StringIO()
|
||||
sys.stdout = captured_output
|
||||
|
||||
test_product = Product('Wireless Mouse', 50.0)
|
||||
print(test_product)
|
||||
|
||||
sys.stdout = sys.__stdout__
|
||||
output = captured_output.getvalue()
|
||||
|
||||
assert "Wireless Mouse - $50.0" in output
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id: 68e529226b0defdff26c4a8b
|
||||
title: Step 7
|
||||
challengeType: 20
|
||||
dashedName: step-7
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now you'll start building the discount system interface. Python provides a way to define abstract classes that serve as blueprints for other classes. This is done using the `ABC` class (Abstract Base Class) of the `abc` module.
|
||||
|
||||
At the top of your code, add the following import statement:
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
```
|
||||
|
||||
This imports the `ABC` class and the `abstractmethod` decorator, which you'll use to define methods that must be implemented by any class that inherits from your abstract class.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should have the mentioned import statement at the top of your code.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_imports()[0].is_equivalent("from abc import ABC, abstractmethod")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58981
|
||||
title: Step 8
|
||||
challengeType: 20
|
||||
dashedName: step-8
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create a class named `DiscountStrategy` that inherits from `ABC`. This will be an abstract base class that defines the interface for all discount strategies.
|
||||
|
||||
For now, just use `pass` in the class body to create an empty class.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a class named `DiscountStrategy`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_class("DiscountStrategy")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `DiscountStrategy` class should inherit from `ABC`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("DiscountStrategy").inherits_from("ABC")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58982
|
||||
title: Step 9
|
||||
challengeType: 20
|
||||
dashedName: step-9
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
As discussed in the previous lesson, abstract methods are methods that must be implemented by any class that inherits from the abstract base class. They serve as a contract, ensuring that all discount strategies will have certain required methods.
|
||||
|
||||
Inside your `DiscountStrategy` class, define a method named `is_applicable`. Use the `@abstractmethod` decorator above the method definition. The method should have the following parameters: `self`, `product`, and `user_tier`. Use the `pass` statement in the method body.
|
||||
|
||||
Add type hints to make the method signature clear: `product` should be of type `Product`, `user_tier` should be of type `str`.
|
||||
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `DiscountStrategy` class should have a method named `is_applicable`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").has_function("is_applicable")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should have the `@abstractmethod` decorator.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").find_function("is_applicable").has_decorators("abstractmethod")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should have parameters `self`, `product: Product`, and `user_tier: str`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").find_function("is_applicable").has_args("self,product:Product,user_tier:str")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
--fcc-editable-region--
|
||||
class DiscountStrategy(ABC):
|
||||
pass
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
```
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58983
|
||||
title: Step 11
|
||||
challengeType: 20
|
||||
dashedName: step-11
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now add a second abstract method named `apply_discount` to your `DiscountStrategy` class. Use the `@abstractmethod` decorator and give it parameters `self` and `product`.
|
||||
|
||||
Add type hints: `product` should be of type `Product`, and the method should return a `float`. Use `pass` in the method body.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `DiscountStrategy` class should have a method named `apply_discount`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").has_function("apply_discount")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have the `@abstractmethod` decorator.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").find_function("apply_discount").has_decorators("abstractmethod")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have parameters `self` and `product: Product`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").find_function("apply_discount").has_args("self, product:Product")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have a return type hint of `-> float`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("DiscountStrategy").find_function("apply_discount").has_returns("float")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
```
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58984
|
||||
title: Step 12
|
||||
challengeType: 20
|
||||
dashedName: step-12
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now it's time to create a concrete implementation of your abstract class. Create a class named `PercentageDiscount` that inherits from `DiscountStrategy`.
|
||||
|
||||
Give it an `__init__` method that takes `self` and `percent` as parameters. Give a type hint of `int` to `percent` and set the return type of the method to `None`. Inside the `__init__` method, store `percent` as an instance attribute.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a class named `PercentageDiscount`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_class("PercentageDiscount")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `PercentageDiscount` class should inherit from `DiscountStrategy`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("PercentageDiscount").inherits_from("DiscountStrategy")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `PercentageDiscount` class should have an `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").has_function("__init__")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `__init__` method should have parameters `self` and `percent: int`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("__init__").has_args("self, percent: int")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `__init__` method should have a return type hint of `None`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("__init__").has_returns("None")`)
|
||||
})
|
||||
```
|
||||
|
||||
You should assign `percent` to `self.percent` inside your `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("PercentageDiscount").find_function("__init__").has_stmt("self.percent = percent")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
```
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58985
|
||||
title: Step 13
|
||||
challengeType: 20
|
||||
dashedName: step-13
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Since `PercentageDiscount` inherits from an abstract class, you must implement all abstract methods. Start by implementing the `is_applicable` method. The `is_applicable` method should take `self`, `product: Product`, and `user_tier: str` as parameters. It should have a return type hint of `bool`.
|
||||
|
||||
This method should determine whether a percentage discount is valid. Return `True` if `self.percent` is less than or equal to `70`, otherwise return `False`. This prevents unrealistic discounts like 90% off.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `PercentageDiscount` class should have an `is_applicable` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").has_function("is_applicable")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should have parameters `self`, `product: Product`, and `user_tier: str`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("is_applicable").has_args("self,product:Product,user_tier:str")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should return `True` when `self.percent` is less than or equal to `70`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
# Add stub method to make class instantiable for testing
|
||||
class TestPercentageDiscount(PercentageDiscount):
|
||||
def apply_discount(self, product):
|
||||
return 0
|
||||
|
||||
test_product = Product("Test", 100.0)
|
||||
discount1 = TestPercentageDiscount(50)
|
||||
discount2 = TestPercentageDiscount(70)
|
||||
assert discount1.is_applicable(test_product, "regular") == True
|
||||
assert discount2.is_applicable(test_product, "regular") == True
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should return `False` when `self.percent` is greater than `70`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
# Add stub method to make class instantiable for testing
|
||||
class TestPercentageDiscount(PercentageDiscount):
|
||||
def apply_discount(self, product):
|
||||
return 0
|
||||
|
||||
test_product = Product("Test", 100.0)
|
||||
discount = TestPercentageDiscount(75)
|
||||
assert discount.is_applicable(test_product, "regular") == False
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should have a return type hint of `bool`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("is_applicable").has_returns("bool")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58986
|
||||
title: Step 14
|
||||
challengeType: 20
|
||||
dashedName: step-14
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now implement the `apply_discount` method in your `PercentageDiscount`.
|
||||
|
||||
This method should calculate and return the discounted price.
|
||||
|
||||
The formula is: `product.price * (1 - self.percent / 100)`
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `PercentageDiscount` class should have an `apply_discount` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").has_function("apply_discount")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have parameters `self` and `product: Product`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("apply_discount").has_args("self, product: Product")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have a return type hint of `float`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("apply_discount").has_returns("float")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should calculate the discounted price using the formula `product.price * (1 - self.percent / 100)`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
test_product = Product("Test", 100.0)
|
||||
discount = PercentageDiscount(20)
|
||||
result = discount.apply_discount(test_product)
|
||||
assert result == 80.0
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58987
|
||||
title: Step 16
|
||||
challengeType: 20
|
||||
dashedName: step-16
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now you'll create another discount strategy. Create a class named `FixedAmountDiscount` that inherits from `DiscountStrategy`.
|
||||
|
||||
Give it an `__init__` method that takes `self` and `amount` as parameters. Give `amount` parameter a type hint of `int` and a return type hint of `None` for the method. Inside the `__init__` method, store `amount` as an instance attribute.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a class named `FixedAmountDiscount`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_class("FixedAmountDiscount")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `FixedAmountDiscount` class should inherit from `DiscountStrategy`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").inherits_from("DiscountStrategy")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `FixedAmountDiscount` class should have an `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").has_function("__init__")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `__init__` method should have parameters `self` and `amount: int`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("__init__").has_args("self, amount: int")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `__init__` method should have a return type of `None`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("__init__").has_returns("None")`)
|
||||
})
|
||||
```
|
||||
|
||||
You should assign `amount` to `self.amount` inside your `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("FixedAmountDiscount").find_function("__init__").has_stmt("self.amount = amount")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58988
|
||||
title: Step 17
|
||||
challengeType: 20
|
||||
dashedName: step-17
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Implement the `is_applicable` method for your `FixedAmountDiscount` class. A fixed amount discount should only be applied if it makes sense economically.
|
||||
|
||||
Return `True` if `product.price * 0.9` is greater than `self.amount`, otherwise return `False`. This ensures the discount doesn't exceed 10% of the original price, which would be unrealistic.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `FixedAmountDiscount` class should have an `is_applicable` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").has_function("is_applicable")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should have parameters `self`, `product: Product`, and `user_tier: str`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("is_applicable").has_args("self, product:Product, user_tier:str")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should have a return type hint of `-> bool`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("is_applicable").has_returns("bool")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should return `True` when `product.price * 0.9` is greater than `self.amount`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
# Add stub method to make class instantiable for testing
|
||||
class TestFixedAmountDiscount(FixedAmountDiscount):
|
||||
def apply_discount(self, product):
|
||||
return 0
|
||||
|
||||
test_product = Product("Test", 100.0)
|
||||
discount = TestFixedAmountDiscount(50)
|
||||
assert discount.is_applicable(test_product, "regular") == True
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should return `False` when `product.price * 0.9` is less than or equal to `self.amount`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
# Add stub method to make class instantiable for testing
|
||||
class TestFixedAmountDiscount(FixedAmountDiscount):
|
||||
def apply_discount(self, product):
|
||||
return 0
|
||||
|
||||
test_product = Product("Test", 100.0)
|
||||
discount = TestFixedAmountDiscount(95)
|
||||
assert discount.is_applicable(test_product, "regular") == False
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f58989
|
||||
title: Step 18
|
||||
challengeType: 20
|
||||
dashedName: step-18
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now implement the `apply_discount` method for `FixedAmountDiscount`. This method should subtract the fixed amount from the product price and return the new price.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `FixedAmountDiscount` class should have an `apply_discount` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").has_function("apply_discount")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have parameters `self` and `product: Product`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("apply_discount").has_args("self, product: Product")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have a return type hint of `-> float`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("apply_discount").has_returns("float")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should return `product.price - self.amount`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
test_product = Product("Test", 100.0)
|
||||
discount = FixedAmountDiscount(15)
|
||||
result = discount.apply_discount(test_product)
|
||||
assert result == 85.0
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
id: 68e5293bd00d2fe134f5898a
|
||||
title: Step 19
|
||||
challengeType: 20
|
||||
dashedName: step-19
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Let's test your new discount strategy. After the existing code, create an instance of `FixedAmountDiscount` with an amount of `5` and assign it to a variable named `fixed_discount`. Then apply this discount to the product and print the result.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a variable named `fixed_discount` with a `FixedAmountDiscount` instance.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).has_variable("fixed_discount")
|
||||
assert isinstance(fixed_discount, FixedAmountDiscount)
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `FixedAmountDiscount` instance should be initialized with amount `5`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert fixed_discount.amount == 5`)
|
||||
})
|
||||
```
|
||||
|
||||
You should call `fixed_discount.apply_discount(product)` and print the result.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).has_call("print(fixed_discount.apply_discount(product))")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec5309d
|
||||
title: Step 20
|
||||
challengeType: 20
|
||||
dashedName: step-20
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now let's create a third discount strategy for premium users. Create a class named `PremiumUserDiscount` that inherits from `DiscountStrategy`.
|
||||
|
||||
Unlike the previous strategies, this one doesn't need an `__init__` method because it doesn't store any configuration. It will apply a fixed 20% discount to all premium users.
|
||||
|
||||
For now, just create the empty class that inherits from `DiscountStrategy` using `pass`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a class named `PremiumUserDiscount`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_class("PremiumUserDiscount")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `PremiumUserDiscount` class should inherit from `DiscountStrategy`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").inherits_from("DiscountStrategy")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,133 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec5309e
|
||||
title: Step 21
|
||||
challengeType: 20
|
||||
dashedName: step-21
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Implement the `is_applicable` method for `PremiumUserDiscount`. This method should check if the user is a premium member.
|
||||
|
||||
Return `True` if `user_tier.lower()` equals `'premium'`, otherwise return `False`. Using `.lower()` ensures the check is case-insensitive.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `PremiumUserDiscount` class should have an `is_applicable` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").has_function("is_applicable")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should have parameters `self`, `product: Product`, and `user_tier: str`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").find_function("is_applicable").has_args("self, product:Product, user_tier:str")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should have a return type hint of `-> bool`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").find_function("is_applicable").has_returns("bool")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should return `True` when `user_tier.lower()` equals `'premium'`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
# Add stub method to make class instantiable for testing
|
||||
class TestPremiumUserDiscount(PremiumUserDiscount):
|
||||
def apply_discount(self, product):
|
||||
return 0
|
||||
|
||||
test_product = Product("Test", 100.0)
|
||||
discount = TestPremiumUserDiscount()
|
||||
assert discount.is_applicable(test_product, "Premium") == True
|
||||
assert discount.is_applicable(test_product, "premium") == True
|
||||
assert discount.is_applicable(test_product, "PREMIUM") == True
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `is_applicable` method should return `False` when `user_tier` is not `'premium'`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
# Add stub method to make class instantiable for testing
|
||||
class TestPremiumUserDiscount(PremiumUserDiscount):
|
||||
def apply_discount(self, product):
|
||||
return 0
|
||||
|
||||
test_product = Product("Test", 100.0)
|
||||
discount = TestPremiumUserDiscount()
|
||||
assert discount.is_applicable(test_product, "regular") == False
|
||||
assert discount.is_applicable(test_product, "standard") == False
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
--fcc-editable-region--
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
pass
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec5309f
|
||||
title: Step 22
|
||||
challengeType: 20
|
||||
dashedName: step-22
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now implement the `apply_discount` method for `PremiumUserDiscount`. This should apply a 20% discount to premium users.
|
||||
|
||||
Return `product.price * 0.8` (which represents 80% of the original price, i.e., 20% off).
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `PremiumUserDiscount` class should have an `apply_discount` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").has_function("apply_discount")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have parameters `self` and `product: Product`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").find_function("apply_discount").has_args("self, product: Product")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should have a return type hint of `-> float`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").find_function("apply_discount").has_returns("float")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should return `product.price * 0.8`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
test_product = Product("Test", 100.0)
|
||||
discount = PremiumUserDiscount()
|
||||
result = discount.apply_discount(test_product)
|
||||
assert result == 80.0
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `apply_discount` method should apply a 20% discount (80% of original price).
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
test_product = Product("Test", 50.0)
|
||||
discount = PremiumUserDiscount()
|
||||
result = discount.apply_discount(test_product)
|
||||
assert result == 40.0
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec530a0
|
||||
title: Step 23
|
||||
challengeType: 20
|
||||
dashedName: step-23
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now you need a way to manage multiple discount strategies and find the best price. At the top of your file, after the existing import, add an import for the `List` type from the `typing` module:
|
||||
|
||||
```py
|
||||
from typing import List
|
||||
```
|
||||
|
||||
This allows you to specify that a function parameter is a list containing specific types of objects.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should import `List` from the `typing` module as mentioned in the instructions after the existing imports.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_imports()[1].is_equivalent("from typing import List")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec530a1
|
||||
title: Step 24
|
||||
challengeType: 20
|
||||
dashedName: step-24
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create a class named `DiscountEngine`. This class will manage all discount strategies and calculate the best price for a product.
|
||||
|
||||
Give it an `__init__` method that takes `self` and `strategies` as parameters, where `strategies` has a type hint of `List[DiscountStrategy]`. The return type of the method should be `None`. Inside the `__init__` method, store `strategies` as an instance attribute.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a class named `DiscountEngine`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_class("DiscountEngine")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `DiscountEngine` class should have an `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").has_function("__init__")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `__init__` method should have parameters `self` and `strategies: List[DiscountStrategy]`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("__init__").has_args("self, strategies: List[DiscountStrategy]")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `__init__` method should have a return type of `None`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("__init__").has_returns("None")`)
|
||||
})
|
||||
```
|
||||
|
||||
You should assign `strategies` to `self.strategies` inside your `__init__` method.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("DiscountEngine").find_function("__init__").has_stmt("self.strategies = strategies")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec530a2
|
||||
title: Step 25
|
||||
challengeType: 20
|
||||
dashedName: step-25
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now create a method named `calculate_best_price` in your `DiscountEngine` class. This method should take `self`, `product`, and `user_tier` as parameters, and return a `float`.
|
||||
|
||||
For now, just use `pass` in the method body. You'll implement the logic in the next steps.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `DiscountEngine` class should have a method named `calculate_best_price`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").has_function("calculate_best_price")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `calculate_best_price` method should have parameters `self`, `product: Product`, and `user_tier: str`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").has_args("self, product:Product, user_tier:str")`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `calculate_best_price` method should have a return type hint of `-> float`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").has_returns("float")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec530a3
|
||||
title: Step 26
|
||||
challengeType: 20
|
||||
dashedName: step-26
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Inside the `calculate_best_price` method, create a list named `prices` containing `product.price`. This ensures that if no discounts apply, the customer pays the full price.
|
||||
|
||||
# --hints--
|
||||
|
||||
Inside `calculate_best_price`, you should create a list named `prices` containing `product.price`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").has_stmt("prices = [product.price]")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
|
||||
--fcc-editable-region--
|
||||
def calculate_best_price(self, product: Product, user_tier: str) -> float:
|
||||
pass
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec530a4
|
||||
title: Step 27
|
||||
challengeType: 20
|
||||
dashedName: step-27
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now create a `for` loop that iterates over `self.strategies`. For each `strategy`, check if it's applicable using the `is_applicable` method. If it is, calculate the discounted price using `apply_discount` and store it in a variable named `discounted`. Then, append the discounted price to the `prices` list.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `for` loop that iterates over `self.strategies`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").find_for_loops()[0].find_for_iter().is_equivalent("self.strategies")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Inside the loop, your `if` statement should be `if strategy.is_applicable(product, user_tier):`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").find_for_loops()[0].find_ifs()[0].find_conditions()[0].is_equivalent("strategy.is_applicable(product, user_tier)")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
If a strategy is applicable, you should call `apply_discount` and append the result to `prices`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").find_for_loops()[0].find_ifs()[0].find_body().is_ordered("discounted = strategy.apply_discount(product)","prices.append(discounted)")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
|
||||
--fcc-editable-region--
|
||||
def calculate_best_price(self, product: Product, user_tier: str) -> float:
|
||||
prices = [product.price]
|
||||
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec530a5
|
||||
title: Step 28
|
||||
challengeType: 20
|
||||
dashedName: step-28
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Finally, return the minimum value from the `prices` list using the `min()` function. This will give you the best (lowest) price after considering all applicable discounts.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `calculate_best_price` method should return the minimum value from the `prices` list using the `min` function.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").find_return().is_equivalent("return min(prices)")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
|
||||
def calculate_best_price(self, product: Product, user_tier: str) -> float:
|
||||
prices = [product.price]
|
||||
|
||||
for strategy in self.strategies:
|
||||
if strategy.is_applicable(product, user_tier):
|
||||
discounted = strategy.apply_discount(product)
|
||||
prices.append(discounted)
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
```
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec530a6
|
||||
title: Step 30
|
||||
challengeType: 20
|
||||
dashedName: step-30
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now let's test the complete discount system! Add an `if` statement checking if `__name__ == '__main__'` and move `product = Product('Wireless Mouse', 50.0)` inside it.
|
||||
|
||||
After that, inside the `if` block, create a variable `user_tier` and set it to the string `Premium`. Then create a variable `strategies` set to a list of strategies containing `PercentageDiscount(10)`, `FixedAmountDiscount(5)`, and `PremiumUserDiscount()`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should have an `if` statement checking if `__name__ == '__main__'`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_ifs()[0].find_conditions()[0].is_equivalent('__name__ == "__main__"')`)
|
||||
})
|
||||
```
|
||||
|
||||
You should move `product = Product('Wireless Mouse', 50.0)` inside the `if` block.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt("product = Product('Wireless Mouse', 50.0)")`)
|
||||
})
|
||||
```
|
||||
|
||||
You should set `user_tier` to the string `Premium` within the `if` block.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt('user_tier = "Premium"')`)
|
||||
})
|
||||
```
|
||||
|
||||
You should create a list named `strategies` containing `PercentageDiscount(10)`, `FixedAmountDiscount(5)`, and `PremiumUserDiscount()`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt('strategies = [PercentageDiscount(10), FixedAmountDiscount(5), PremiumUserDiscount()]')`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
|
||||
def calculate_best_price(self, product: Product, user_tier: str) -> float:
|
||||
prices = [product.price]
|
||||
|
||||
for strategy in self.strategies:
|
||||
if strategy.is_applicable(product, user_tier):
|
||||
discounted = strategy.apply_discount(product)
|
||||
prices.append(discounted)
|
||||
|
||||
return min(prices)
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,193 @@
|
||||
---
|
||||
id: 68e52994bc7ea2e3dec530a7
|
||||
title: Step 32
|
||||
challengeType: 20
|
||||
dashedName: step-32
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
To complete the workshop, add a final print statement to display the best price with two decimal places using an f-string and this format: `Best price for [product.name] for [user_tier] user: $[best_price]` where `[product.name]`, `[user_tier]`, and `[best_price]` should be replaced with the actual variable values.
|
||||
|
||||
An example output could be: `Best price for Wireless Buds for Premium user: $93.00`. Note the two decimal places for the price.
|
||||
|
||||
With that, the discount calculator workshop is complete!
|
||||
|
||||
# --hints--
|
||||
|
||||
Your output should match the format: `Best price for {product.name} for {user_tier} user: ${best_price:.2f}`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
import io
|
||||
import sys
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = buffer = io.StringIO()
|
||||
|
||||
exec(_code)
|
||||
|
||||
output = buffer.getvalue()
|
||||
sys.stdout = old_stdout
|
||||
|
||||
assert "Best price for Wireless Mouse for Premium user: $40.00" in output
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
|
||||
def calculate_best_price(self, product: Product, user_tier: str) -> float:
|
||||
prices = [product.price]
|
||||
|
||||
for strategy in self.strategies:
|
||||
if strategy.is_applicable(product, user_tier):
|
||||
discounted = strategy.apply_discount(product)
|
||||
prices.append(discounted)
|
||||
|
||||
return min(prices)
|
||||
|
||||
if __name__ == '__main__':
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
user_tier = 'Premium'
|
||||
|
||||
strategies = [
|
||||
PercentageDiscount(10),
|
||||
FixedAmountDiscount(5),
|
||||
PremiumUserDiscount()
|
||||
]
|
||||
|
||||
engine = DiscountEngine(strategies)
|
||||
best_price = engine.calculate_best_price(product, user_tier)
|
||||
--fcc-editable-region--
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price*0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8 # 20% off for premium users
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
|
||||
def calculate_best_price(self, product: Product, user_tier: str) -> float:
|
||||
prices = [product.price]
|
||||
for strategy in self.strategies:
|
||||
if strategy.is_applicable(product, user_tier):
|
||||
discounted = strategy.apply_discount(product)
|
||||
prices.append(discounted)
|
||||
return min(prices)
|
||||
|
||||
if __name__ == '__main__':
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
user_tier = 'Premium'
|
||||
strategies = [
|
||||
PercentageDiscount(10),
|
||||
FixedAmountDiscount(5),
|
||||
PremiumUserDiscount()
|
||||
]
|
||||
engine = DiscountEngine(strategies)
|
||||
best_price = engine.calculate_best_price(product, user_tier)
|
||||
print(f'Best price for {product.name} for {user_tier} user: ${best_price:.2f}')
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
id: 68e79d91daf8fb4808f93c47
|
||||
title: Step 15
|
||||
challengeType: 20
|
||||
dashedName: step-15
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
After implementing the `apply_discount` method, test your discount strategy. Create an instance of `PercentageDiscount` with `10` percent and assign it to a variable named `discount`. Then apply the discount using `discount.apply_discount(product)`, and print the result to see the discount in action.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a variable named `discount` with a `PercentageDiscount` instance.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).has_variable("discount")
|
||||
assert isinstance(discount, PercentageDiscount)
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
Your `PercentageDiscount` instance should be initialized with `10` percent.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert discount.percent == 10`)
|
||||
})
|
||||
```
|
||||
|
||||
You should call `discount.apply_discount(product)` and print the result.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).has_call("print(discount.apply_discount(product))")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
id: 68e7a24b2482bd5fa88774fa
|
||||
title: Step 29
|
||||
challengeType: 20
|
||||
dashedName: step-29
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Now, remove the following lines from the bottom of your code:
|
||||
|
||||
- `print(product)`
|
||||
- `discount = PercentageDiscount(10)`
|
||||
- `print(discount.apply_discount(product))`
|
||||
- `fixed_discount = FixedAmountDiscount(5)`
|
||||
- `print(fixed_discount.apply_discount(product))`
|
||||
|
||||
# --hints--
|
||||
|
||||
You should not have the line `print(product)` in your code.
|
||||
|
||||
```js
|
||||
({ test: () => assert.isFalse(runPython(`_Node(_code).has_call('print(product)')`
|
||||
))
|
||||
})
|
||||
```
|
||||
|
||||
You should not have the line `discount = PercentageDiscount(10)` in your code.
|
||||
|
||||
```js
|
||||
({ test: () => assert.isFalse(runPython(`_Node(_code).has_stmt('discount = PercentageDiscount(10)')`
|
||||
))
|
||||
})
|
||||
```
|
||||
|
||||
You should not have the line `print(discount.apply_discount(product))` in your code.
|
||||
|
||||
```js
|
||||
({ test: () => assert.isFalse(runPython(`_Node(_code).has_call('print(discount.apply_discount(product))')`
|
||||
))
|
||||
})
|
||||
```
|
||||
|
||||
You should not have the line `fixed_discount = FixedAmountDiscount(5)` in your code.
|
||||
|
||||
```js
|
||||
|
||||
({ test: () => assert.isFalse(runPython(`_Node(_code).has_stmt('fixed_discount = FixedAmountDiscount(5)')`
|
||||
))
|
||||
})
|
||||
```
|
||||
|
||||
You should not have the line `print(fixed_discount.apply_discount(product))` in your code.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => assert.isFalse(runPython(`_Node(_code).has_call('print(fixed_discount.apply_discount(product))')`
|
||||
))
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
|
||||
def calculate_best_price(self, product: Product, user_tier: str) -> float:
|
||||
prices = [product.price]
|
||||
|
||||
for strategy in self.strategies:
|
||||
if strategy.is_applicable(product, user_tier):
|
||||
discounted = strategy.apply_discount(product)
|
||||
prices.append(discounted)
|
||||
return min(prices)
|
||||
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
|
||||
--fcc-editable-region--
|
||||
print(product)
|
||||
|
||||
discount = PercentageDiscount(10)
|
||||
print(discount.apply_discount(product))
|
||||
|
||||
fixed_discount = FixedAmountDiscount(5)
|
||||
print(fixed_discount.apply_discount(product))
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: 68e9207ddcba053c5cb24413
|
||||
title: Step 2
|
||||
challengeType: 20
|
||||
dashedName: step-2
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In Python, type hints are optional signals that tell other developers what the data type of a variable or function is expected to be. Here is an example that adds the type hint `str` to the `name` parameter.
|
||||
|
||||
```py
|
||||
def greet(name: str):
|
||||
return 'Hello, ' + name
|
||||
```
|
||||
|
||||
For the `__init__` method parameters, update the `name` parameter to have the type hint `str` and the `price` parameter to have the type hint `float`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should add the type hint `str` to the `name` parameter and the type hint `float` to the `price` parameter in the `__init__` method of the `Product` class.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_args("self, name: str, price: float")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
--fcc-editable-region--
|
||||
class Product:
|
||||
def __init__(self, name, price):
|
||||
self.name = name
|
||||
self.price = price
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: 68ef53ef276acfa40c83cb12
|
||||
title: Step 10
|
||||
challengeType: 20
|
||||
dashedName: step-10
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Add a return type hint of `bool` to the `is_applicable` method so that it clearly indicates it returns a `bool`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `is_applicable` method should have a return type hint of `-> bool`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`
|
||||
assert _Node(_code).find_class("DiscountStrategy").find_function("is_applicable").has_returns("bool")
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
--fcc-editable-region--
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str):
|
||||
pass
|
||||
--fcc-editable-region--
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
print(product)
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
id: 68efb04d5ea87cf267192ed4
|
||||
title: Step 31
|
||||
challengeType: 20
|
||||
dashedName: step-31
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Create a `DiscountEngine` instance and store it in the variable `engine`. After that, calculate the best price using `calculate_best_price` method and store it in a variable named `best_price`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should create a `DiscountEngine` instance with the `strategies` list.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt('engine = DiscountEngine(strategies)')`)
|
||||
})
|
||||
```
|
||||
|
||||
You should call `engine.calculate_best_price(product, user_tier)` and store the result in `best_price`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt('best_price = engine.calculate_best_price(product, user_tier)')
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.name} - ${self.price}'
|
||||
|
||||
class DiscountStrategy(ABC):
|
||||
@abstractmethod
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
pass
|
||||
|
||||
class PercentageDiscount(DiscountStrategy):
|
||||
def __init__(self, percent: int) -> None:
|
||||
self.percent = percent
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return self.percent <= 70
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * (1 - self.percent / 100)
|
||||
|
||||
class FixedAmountDiscount(DiscountStrategy):
|
||||
def __init__(self, amount: int) -> None:
|
||||
self.amount = amount
|
||||
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return product.price * 0.9 > self.amount
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price - self.amount
|
||||
|
||||
class PremiumUserDiscount(DiscountStrategy):
|
||||
def is_applicable(self, product: Product, user_tier: str) -> bool:
|
||||
return user_tier.lower() == 'premium'
|
||||
|
||||
def apply_discount(self, product: Product) -> float:
|
||||
return product.price * 0.8
|
||||
|
||||
class DiscountEngine:
|
||||
def __init__(self, strategies: List[DiscountStrategy]) -> None:
|
||||
self.strategies = strategies
|
||||
|
||||
def calculate_best_price(self, product: Product, user_tier: str) -> float:
|
||||
prices = [product.price]
|
||||
|
||||
for strategy in self.strategies:
|
||||
if strategy.is_applicable(product, user_tier):
|
||||
discounted = strategy.apply_discount(product)
|
||||
prices.append(discounted)
|
||||
|
||||
return min(prices)
|
||||
--fcc-editable-region--
|
||||
if __name__ == '__main__':
|
||||
product = Product('Wireless Mouse', 50.0)
|
||||
user_tier = 'Premium'
|
||||
|
||||
strategies = [
|
||||
PercentageDiscount(10),
|
||||
FixedAmountDiscount(5),
|
||||
PremiumUserDiscount()
|
||||
]
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
id: 68f1196f0fedc6f6ecc9aba6
|
||||
title: Step 4
|
||||
challengeType: 20
|
||||
dashedName: step-4
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In Python, a return type hint indicates the expected return type of a function or method. You do this by adding `-> return_type` after the parameter list in the method definition.
|
||||
|
||||
Here is an example of a method with both parameter and return type hints whose return type is `bool`:
|
||||
|
||||
```py
|
||||
def example_method(self, value: int) -> bool:
|
||||
pass
|
||||
```
|
||||
|
||||
Other return type hints you might use include `str`, `None`, `float` and more.
|
||||
|
||||
In the existing `__init__` method, add a return type hint of `None` since constructors do not return a value.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `__init__` method should have a return type hint of `None`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_returns("None")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
--fcc-editable-region--
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float):
|
||||
self.name = name
|
||||
self.price = price
|
||||
--fcc-editable-region--
|
||||
def __str__(self):
|
||||
return f'{self.name} - ${self.price}'
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: 68f119ad905b29ff59d858c8
|
||||
title: Step 5
|
||||
challengeType: 20
|
||||
dashedName: step-5
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Just like the previous step, in your `__str__` method, add a return type hint of `str` since this method returns a string representation of the object.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `__str__` method should have a return type hint of `str`.
|
||||
|
||||
```js
|
||||
({
|
||||
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__str__").has_returns("str")`)
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```py
|
||||
class Product:
|
||||
def __init__(self, name: str, price: float) -> None:
|
||||
self.name = name
|
||||
self.price = price
|
||||
--fcc-editable-region--
|
||||
def __str__(self):
|
||||
return f'{self.name} - ${self.price}'
|
||||
--fcc-editable-region--
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "Build a Discount Calculator",
|
||||
"isUpcomingChange": true,
|
||||
"dashedName": "workshop-discount-calculator",
|
||||
"helpCategory": "Python",
|
||||
"blockLayout": "challenge-grid",
|
||||
"challengeOrder": [
|
||||
{ "id": "68e524c94e2e4ab7b4bfc7e2", "title": "Step 1" },
|
||||
{ "id": "68e9207ddcba053c5cb24413", "title": "Step 2" },
|
||||
{ "id": "68e529226b0defdff26c4a89", "title": "Step 3" },
|
||||
{ "id": "68f1196f0fedc6f6ecc9aba6", "title": "Step 4" },
|
||||
{ "id": "68f119ad905b29ff59d858c8", "title": "Step 5" },
|
||||
{ "id": "68e529226b0defdff26c4a8a", "title": "Step 6" },
|
||||
{ "id": "68e529226b0defdff26c4a8b", "title": "Step 7" },
|
||||
{ "id": "68e5293bd00d2fe134f58981", "title": "Step 8" },
|
||||
{ "id": "68e5293bd00d2fe134f58982", "title": "Step 9" },
|
||||
{ "id": "68ef53ef276acfa40c83cb12", "title": "Step 10" },
|
||||
{ "id": "68e5293bd00d2fe134f58983", "title": "Step 11" },
|
||||
{ "id": "68e5293bd00d2fe134f58984", "title": "Step 12" },
|
||||
{ "id": "68e5293bd00d2fe134f58985", "title": "Step 13" },
|
||||
{ "id": "68e5293bd00d2fe134f58986", "title": "Step 14" },
|
||||
{ "id": "68e79d91daf8fb4808f93c47", "title": "Step 15" },
|
||||
{ "id": "68e5293bd00d2fe134f58987", "title": "Step 16" },
|
||||
{ "id": "68e5293bd00d2fe134f58988", "title": "Step 17" },
|
||||
{ "id": "68e5293bd00d2fe134f58989", "title": "Step 18" },
|
||||
{ "id": "68e5293bd00d2fe134f5898a", "title": "Step 19" },
|
||||
{ "id": "68e52994bc7ea2e3dec5309d", "title": "Step 20" },
|
||||
{ "id": "68e52994bc7ea2e3dec5309e", "title": "Step 21" },
|
||||
{ "id": "68e52994bc7ea2e3dec5309f", "title": "Step 22" },
|
||||
{ "id": "68e52994bc7ea2e3dec530a0", "title": "Step 23" },
|
||||
{ "id": "68e52994bc7ea2e3dec530a1", "title": "Step 24" },
|
||||
{ "id": "68e52994bc7ea2e3dec530a2", "title": "Step 25" },
|
||||
{ "id": "68e52994bc7ea2e3dec530a3", "title": "Step 26" },
|
||||
{ "id": "68e52994bc7ea2e3dec530a4", "title": "Step 27" },
|
||||
{ "id": "68e52994bc7ea2e3dec530a5", "title": "Step 28" },
|
||||
{ "id": "68e7a24b2482bd5fa88774fa", "title": "Step 29" },
|
||||
{ "id": "68e52994bc7ea2e3dec530a6", "title": "Step 30" },
|
||||
{ "id": "68efb04d5ea87cf267192ed4", "title": "Step 31" },
|
||||
{ "id": "68e52994bc7ea2e3dec530a7", "title": "Step 32" }
|
||||
],
|
||||
"blockLabel": "workshop",
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true
|
||||
}
|
||||
@@ -746,6 +746,7 @@
|
||||
"workshop-media-catalogue",
|
||||
"lab-polygon-area-calculator",
|
||||
"lecture-understanding-abstraction",
|
||||
"workshop-discount-calculator",
|
||||
"lab-player-interface",
|
||||
"review-object-oriented-programming",
|
||||
"quiz-object-oriented-programming"
|
||||
|
||||
Reference in New Issue
Block a user