Python: book_store
This commit is contained in:
parent
8190c11851
commit
f5e841e110
4 changed files with 232 additions and 0 deletions
1
python/book-store/.exercism/metadata.json
Normal file
1
python/book-store/.exercism/metadata.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"track":"python","exercise":"book-store","id":"cef3e9ecc90a4fcc9e92b878f280657a","url":"https://exercism.io/my/solutions/cef3e9ecc90a4fcc9e92b878f280657a","handle":"DmitryKokorin","is_requester":true,"auto_approve":false}
|
||||
115
python/book-store/README.md
Normal file
115
python/book-store/README.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# Book Store
|
||||
|
||||
To try and encourage more sales of different books from a popular 5 book
|
||||
series, a bookshop has decided to offer discounts on multiple book purchases.
|
||||
|
||||
One copy of any of the five books costs $8.
|
||||
|
||||
If, however, you buy two different books, you get a 5%
|
||||
discount on those two books.
|
||||
|
||||
If you buy 3 different books, you get a 10% discount.
|
||||
|
||||
If you buy 4 different books, you get a 20% discount.
|
||||
|
||||
If you buy all 5, you get a 25% discount.
|
||||
|
||||
Note: that if you buy four books, of which 3 are
|
||||
different titles, you get a 10% discount on the 3 that
|
||||
form part of a set, but the fourth book still costs $8.
|
||||
|
||||
Your mission is to write a piece of code to calculate the
|
||||
price of any conceivable shopping basket (containing only
|
||||
books of the same series), giving as big a discount as
|
||||
possible.
|
||||
|
||||
For example, how much does this basket of books cost?
|
||||
|
||||
- 2 copies of the first book
|
||||
- 2 copies of the second book
|
||||
- 2 copies of the third book
|
||||
- 1 copy of the fourth book
|
||||
- 1 copy of the fifth book
|
||||
|
||||
One way of grouping these 8 books is:
|
||||
|
||||
- 1 group of 5 --> 25% discount (1st,2nd,3rd,4th,5th)
|
||||
- +1 group of 3 --> 10% discount (1st,2nd,3rd)
|
||||
|
||||
This would give a total of:
|
||||
|
||||
- 5 books at a 25% discount
|
||||
- +3 books at a 10% discount
|
||||
|
||||
Resulting in:
|
||||
|
||||
- 5 x (8 - 2.00) == 5 x 6.00 == $30.00
|
||||
- +3 x (8 - 0.80) == 3 x 7.20 == $21.60
|
||||
|
||||
For a total of $51.60
|
||||
|
||||
However, a different way to group these 8 books is:
|
||||
|
||||
- 1 group of 4 books --> 20% discount (1st,2nd,3rd,4th)
|
||||
- +1 group of 4 books --> 20% discount (1st,2nd,3rd,5th)
|
||||
|
||||
This would give a total of:
|
||||
|
||||
- 4 books at a 20% discount
|
||||
- +4 books at a 20% discount
|
||||
|
||||
Resulting in:
|
||||
|
||||
- 4 x (8 - 1.60) == 4 x 6.40 == $25.60
|
||||
- +4 x (8 - 1.60) == 4 x 6.40 == $25.60
|
||||
|
||||
For a total of $51.20
|
||||
|
||||
And $51.20 is the price with the biggest discount.
|
||||
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run `pytest book_store_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module:
|
||||
`python -m pytest book_store_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/book-store` directory.
|
||||
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Inspired by the harry potter kata from Cyber-Dojo. [http://cyber-dojo.org](http://cyber-dojo.org)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
33
python/book-store/book_store.py
Normal file
33
python/book-store/book_store.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
from collections import Counter
|
||||
from itertools import combinations
|
||||
|
||||
DISCOUNTS = [0, 0, 5, 10, 20, 25]
|
||||
BOOK_COST = 8
|
||||
|
||||
|
||||
def get_discount(counter):
|
||||
|
||||
xs = sorted([i for i in counter if i != 0], reverse=True)
|
||||
|
||||
if not xs:
|
||||
return 0
|
||||
|
||||
discount = 0
|
||||
|
||||
for group_size in range(1, len(xs) + 1):
|
||||
for xs1 in combinations(xs, len(xs)):
|
||||
xs1 = [item - 1 if index < group_size else item
|
||||
for index, item in enumerate(xs1)]
|
||||
curr_discount = (
|
||||
DISCOUNTS[group_size]*group_size + get_discount(xs1)
|
||||
)
|
||||
if curr_discount > discount:
|
||||
discount = curr_discount
|
||||
|
||||
return discount
|
||||
|
||||
|
||||
def total(basket):
|
||||
|
||||
return BOOK_COST*(len(basket)*100
|
||||
- get_discount(Counter(basket).values()))
|
||||
83
python/book-store/book_store_test.py
Normal file
83
python/book-store/book_store_test.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import unittest
|
||||
|
||||
from book_store import total
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json`
|
||||
|
||||
|
||||
class BookStoreTest(unittest.TestCase):
|
||||
def test_only_a_single_book(self):
|
||||
basket = [1]
|
||||
self.assertEqual(total(basket), 800)
|
||||
|
||||
def test_two_of_the_same_book(self):
|
||||
basket = [2, 2]
|
||||
self.assertEqual(total(basket), 1600)
|
||||
|
||||
def test_empty_basket(self):
|
||||
basket = []
|
||||
self.assertEqual(total(basket), 0)
|
||||
|
||||
def test_two_different_books(self):
|
||||
basket = [1, 2]
|
||||
self.assertEqual(total(basket), 1520)
|
||||
|
||||
def test_three_different_books(self):
|
||||
basket = [1, 2, 3]
|
||||
self.assertEqual(total(basket), 2160)
|
||||
|
||||
def test_four_different_books(self):
|
||||
basket = [1, 2, 3, 4]
|
||||
self.assertEqual(total(basket), 2560)
|
||||
|
||||
def test_five_different_books(self):
|
||||
basket = [1, 2, 3, 4, 5]
|
||||
self.assertEqual(total(basket), 3000)
|
||||
|
||||
def test_two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three(self):
|
||||
basket = [1, 1, 2, 2, 3, 3, 4, 5]
|
||||
self.assertEqual(total(basket), 5120)
|
||||
|
||||
def test_two_groups_of_four_is_cheaper_than_groups_of_five_and_three(self):
|
||||
basket = [1, 1, 2, 3, 4, 4, 5, 5]
|
||||
self.assertEqual(total(basket), 5120)
|
||||
|
||||
def test_group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three(self):
|
||||
basket = [1, 1, 2, 2, 3, 4]
|
||||
self.assertEqual(total(basket), 4080)
|
||||
|
||||
def test_two_each_of_first_4_books_and_1_copy_each_of_rest(self):
|
||||
basket = [1, 1, 2, 2, 3, 3, 4, 4, 5]
|
||||
self.assertEqual(total(basket), 5560)
|
||||
|
||||
def test_two_copies_of_each_book(self):
|
||||
basket = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
|
||||
self.assertEqual(total(basket), 6000)
|
||||
|
||||
def test_three_copies_of_first_book_and_2_each_of_remaining(self):
|
||||
basket = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1]
|
||||
self.assertEqual(total(basket), 6800)
|
||||
|
||||
def test_three_each_of_first_2_books_and_2_each_of_remaining_books(self):
|
||||
basket = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2]
|
||||
self.assertEqual(total(basket), 7520)
|
||||
|
||||
def test_four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three(
|
||||
self
|
||||
):
|
||||
basket = [1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5]
|
||||
self.assertEqual(total(basket), 10240)
|
||||
|
||||
# Additional tests for this track
|
||||
|
||||
def test_two_groups_of_four_and_a_group_of_five(self):
|
||||
basket = [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5]
|
||||
self.assertEqual(total(basket), 8120)
|
||||
|
||||
def test_shuffled_book_order(self):
|
||||
basket = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]
|
||||
self.assertEqual(total(basket), 8120)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue