Skip to content

Commit 482c655

Browse files
[#764] New practice exercise book-store (#778)
Co-authored-by: Angelika Tyborska <angelikatyborska@fastmail.com>
1 parent 98f8168 commit 482c655

File tree

10 files changed

+396
-0
lines changed

10 files changed

+396
-0
lines changed

config.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2490,6 +2490,27 @@
24902490
"strings"
24912491
],
24922492
"difficulty": 5
2493+
},
2494+
{
2495+
"slug": "book-store",
2496+
"name": "Book Store",
2497+
"uuid": "d6d9687c-8bf0-482e-8309-4f3d706ba753",
2498+
"prerequisites": [
2499+
"lists",
2500+
"pattern-matching",
2501+
"guards",
2502+
"multiple-clause-functions",
2503+
"enum",
2504+
"list-comprehensions",
2505+
"maps",
2506+
"pipe-operator",
2507+
"ranges",
2508+
"recursion"
2509+
],
2510+
"practices": [
2511+
"list-comprehensions"
2512+
],
2513+
"difficulty": 8
24932514
}
24942515
],
24952516
"foregone": [
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Description
2+
3+
To try and encourage more sales of different books from a popular 5 book
4+
series, a bookshop has decided to offer discounts on multiple book purchases.
5+
6+
One copy of any of the five books costs $8.
7+
8+
If, however, you buy two different books, you get a 5%
9+
discount on those two books.
10+
11+
If you buy 3 different books, you get a 10% discount.
12+
13+
If you buy 4 different books, you get a 20% discount.
14+
15+
If you buy all 5, you get a 25% discount.
16+
17+
Note: that if you buy four books, of which 3 are
18+
different titles, you get a 10% discount on the 3 that
19+
form part of a set, but the fourth book still costs $8.
20+
21+
Your mission is to write a piece of code to calculate the
22+
price of any conceivable shopping basket (containing only
23+
books of the same series), giving as big a discount as
24+
possible.
25+
26+
For example, how much does this basket of books cost?
27+
28+
- 2 copies of the first book
29+
- 2 copies of the second book
30+
- 2 copies of the third book
31+
- 1 copy of the fourth book
32+
- 1 copy of the fifth book
33+
34+
One way of grouping these 8 books is:
35+
36+
- 1 group of 5 --> 25% discount (1st,2nd,3rd,4th,5th)
37+
- +1 group of 3 --> 10% discount (1st,2nd,3rd)
38+
39+
This would give a total of:
40+
41+
- 5 books at a 25% discount
42+
- +3 books at a 10% discount
43+
44+
Resulting in:
45+
46+
- 5 x (8 - 2.00) == 5 x 6.00 == $30.00
47+
- +3 x (8 - 0.80) == 3 x 7.20 == $21.60
48+
49+
For a total of $51.60
50+
51+
However, a different way to group these 8 books is:
52+
53+
- 1 group of 4 books --> 20% discount (1st,2nd,3rd,4th)
54+
- +1 group of 4 books --> 20% discount (1st,2nd,3rd,5th)
55+
56+
This would give a total of:
57+
58+
- 4 books at a 20% discount
59+
- +4 books at a 20% discount
60+
61+
Resulting in:
62+
63+
- 4 x (8 - 1.60) == 4 x 6.40 == $25.60
64+
- +4 x (8 - 1.60) == 4 x 6.40 == $25.60
65+
66+
For a total of $51.20
67+
68+
And $51.20 is the price with the biggest discount.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"authors": ["jiegillet"],
3+
"contributors": [],
4+
"files": {
5+
"example": [
6+
".meta/example.ex"
7+
],
8+
"solution": [
9+
"lib/book_store.ex"
10+
],
11+
"test": [
12+
"test/book_store_test.exs"
13+
]
14+
},
15+
"blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.",
16+
"source": "Inspired by the harry potter kata from Cyber-Dojo.",
17+
"source_url": "http://cyber-dojo.org"
18+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
defmodule BookStore do
2+
use Agent
3+
@type book :: integer
4+
5+
@base_price 800
6+
@discount %{
7+
1 => @base_price,
8+
2 => div(@base_price * 95, 100),
9+
3 => div(@base_price * 90, 100),
10+
4 => div(@base_price * 80, 100),
11+
5 => div(@base_price * 75, 100)
12+
}
13+
14+
@doc """
15+
Calculate lowest price (in cents) for a shopping basket containing books.
16+
"""
17+
@spec total(basket :: [book]) :: integer
18+
def total(basket) do
19+
# We use an Agent to memoize the discounts
20+
{:ok, _pid} = Agent.start_link(fn -> %{} end, name: :discount)
21+
22+
basket
23+
|> frequencies
24+
|> Enum.sort()
25+
|> discounts()
26+
|> Enum.min()
27+
end
28+
29+
def frequencies(list) do
30+
list
31+
|> Enum.map(&%{&1 => 1})
32+
|> Enum.reduce(%{}, &Map.merge(&1, &2, fn _item, a, b -> a + b end))
33+
|> Map.values()
34+
end
35+
36+
def discounts([]), do: [0]
37+
def discounts([n]), do: [n * @base_price]
38+
39+
def discounts(frequencies) do
40+
cached = Agent.get(:discount, &Map.get(&1, frequencies))
41+
42+
if cached do
43+
cached
44+
else
45+
discount =
46+
for num_books <- 2..5,
47+
{picked, left} <- pick_n_distinct_from(num_books, frequencies),
48+
remove_picked = picked |> Enum.map(&(&1 - 1)) |> Enum.reject(&(&1 == 0)),
49+
costs <- discounts(Enum.sort(remove_picked ++ left)),
50+
do: costs + num_books * @discount[num_books]
51+
52+
Agent.update(:discount, &Map.put(&1, frequencies, discount))
53+
discount
54+
end
55+
end
56+
57+
def pick_n_distinct_from(n, list) when length(list) < n, do: %{}
58+
def pick_n_distinct_from(0, list), do: %{[] => list}
59+
def pick_n_distinct_from(n, list) when length(list) == n, do: %{list => []}
60+
61+
def pick_n_distinct_from(n, list) do
62+
for pick <- Enum.uniq(list),
63+
{picked, left} <- pick_n_distinct_from(n - 1, list -- [pick]),
64+
into: %{},
65+
do: {Enum.sort([pick | picked]), left}
66+
end
67+
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
[17146bd5-2e80-4557-ab4c-05632b6b0d01]
12+
description = "Only a single book"
13+
14+
[cc2de9ac-ff2a-4efd-b7c7-bfe0f43271ce]
15+
description = "Two of the same book"
16+
17+
[5a86eac0-45d2-46aa-bbf0-266b94393a1a]
18+
description = "Empty basket"
19+
20+
[158bd19a-3db4-4468-ae85-e0638a688990]
21+
description = "Two different books"
22+
23+
[f3833f6b-9332-4a1f-ad98-6c3f8e30e163]
24+
description = "Three different books"
25+
26+
[1951a1db-2fb6-4cd1-a69a-f691b6dd30a2]
27+
description = "Four different books"
28+
29+
[d70f6682-3019-4c3f-aede-83c6a8c647a3]
30+
description = "Five different books"
31+
32+
[78cacb57-911a-45f1-be52-2a5bd428c634]
33+
description = "Two groups of four is cheaper than group of five plus group of three"
34+
35+
[f808b5a4-e01f-4c0d-881f-f7b90d9739da]
36+
description = "Two groups of four is cheaper than groups of five and three"
37+
38+
[fe96401c-5268-4be2-9d9e-19b76478007c]
39+
description = "Group of four plus group of two is cheaper than two groups of three"
40+
41+
[68ea9b78-10ad-420e-a766-836a501d3633]
42+
description = "Two each of first 4 books and 1 copy each of rest"
43+
44+
[c0a779d5-a40c-47ae-9828-a340e936b866]
45+
description = "Two copies of each book"
46+
47+
[18fd86fe-08f1-4b68-969b-392b8af20513]
48+
description = "Three copies of first book and 2 each of remaining"
49+
50+
[0b19a24d-e4cf-4ec8-9db2-8899a41af0da]
51+
description = "Three each of first 2 books and 2 each of remaining books"
52+
53+
[bb376344-4fb2-49ab-ab85-e38d8354a58d]
54+
description = "Four groups of four are cheaper than two groups each of five and three"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
defmodule BookStore do
2+
@typedoc "A book is represented by its number in the 5-book series"
3+
@type book :: 1 | 2 | 3 | 4 | 5
4+
5+
@doc """
6+
Calculate lowest price (in cents) for a shopping basket containing books.
7+
"""
8+
@spec total(basket :: [book]) :: integer
9+
def total(basket) do
10+
end
11+
end

exercises/practice/book-store/mix.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule BookStore.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :book_store,
7+
version: "0.1.0",
8+
# elixir: "~> 1.8",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps()
11+
]
12+
end
13+
14+
# Run "mix help compile.app" to learn about applications.
15+
def application do
16+
[
17+
extra_applications: [:logger]
18+
]
19+
end
20+
21+
# Run "mix help deps" to learn about dependencies.
22+
defp deps do
23+
[
24+
# {:dep_from_hexpm, "~> 0.3.0"},
25+
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
26+
]
27+
end
28+
end

0 commit comments

Comments
 (0)