Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,18 @@
"strings",
"logic"
]
},
{
"uuid": "0ec96460-08be-49a0-973a-4336f21b763c",
"slug": "book-store",
"core": false,
"unlocked_by": null,
"difficulty": 6,
"topics": [
"loops",
"arrays",
"logic"
]
}
],
"foregone": [
Expand Down
9 changes: 9 additions & 0 deletions exercises/book-store/.meta/generator/book_store_case.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'generator/exercise_case'

class BookStoreCase < Generator::ExerciseCase

def workload
assert_equal { "BookStore.calculate_price(#{basket})" }
end

end
1 change: 1 addition & 0 deletions exercises/book-store/.meta/solutions/.version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0
47 changes: 47 additions & 0 deletions exercises/book-store/.meta/solutions/book_store.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module BookKeeping
VERSION = 0
end

class BookStore

GROUP_DISCOUNTS = [0, 0.05, 0.1, 0.2, 0.25]
INDIVIDUAL_PRICE = 8

def self.calculate_price(basket)
groups = []
remaining_books = basket.dup

# Make as many groups of 4 as you can
while (largest_group = remaining_books.uniq).length > 3
group_of_four = largest_group[0..3]
group_of_four.each {|book| remaining_books.delete_at(remaining_books.index(book)) }
groups << group_of_four
end

# Bump as many of them to groups of 5 as you can
groups.each do |group|
fifth_book = (remaining_books - group).first
next unless fifth_book

group << fifth_book
remaining_books.delete_at(remaining_books.index(fifth_book))
end

# Make the largest groups you can with the remaining books
while (new_group = remaining_books.uniq).any?
new_group.each {|book| remaining_books.delete_at(remaining_books.index(book)) }
groups << new_group
end

groups.map {|group| group_price(group.length) }.inject(0,:+)
end

private

def self.group_price(group_size)
discount = GROUP_DISCOUNTS[group_size - 1]

group_size * INDIVIDUAL_PRICE * (1 - discount)
end

end
100 changes: 100 additions & 0 deletions exercises/book-store/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 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.

* * * *

For installation and learning resources, refer to the
[exercism help page](http://exercism.io/languages/ruby).

For running the tests provided, you will need the Minitest gem. Open a
terminal window and run the following command to install minitest:

gem install minitest

If you would like color output, you can `require 'minitest/pride'` in
the test file, or note the alternative instruction, below, for running
the test file.

In order to run the test, you can run the test file from the exercise
directory. For example, if the test suite is called
`hello_world_test.rb`, you can run the following command:

ruby hello_world_test.rb

To include color from the command line:

ruby -r minitest/pride hello_world_test.rb


## 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.
97 changes: 97 additions & 0 deletions exercises/book-store/book_store_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require 'minitest/autorun'
require_relative 'book_store'

# Common test data version: 1.1.0 a636903
class BookStoreTest < Minitest::Test
def test_only_a_single_book
# skip
assert_equal 8.0, BookStore.calculate_price([1])
end

def test_two_of_the_same_book
skip
assert_equal 16.0, BookStore.calculate_price([2, 2])
end

def test_empty_basket
skip
assert_equal 0.0, BookStore.calculate_price([])
end

def test_two_different_books
skip
assert_equal 15.2, BookStore.calculate_price([1, 2])
end

def test_three_different_books
skip
assert_equal 21.6, BookStore.calculate_price([1, 2, 3])
end

def test_four_different_books
skip
assert_equal 25.6, BookStore.calculate_price([1, 2, 3, 4])
end

def test_five_different_books
skip
assert_equal 30.0, BookStore.calculate_price([1, 2, 3, 4, 5])
end

def test_two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three
skip
assert_equal 51.2, BookStore.calculate_price([1, 1, 2, 2, 3, 3, 4, 5])
end

def test_group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three
skip
assert_equal 40.8, BookStore.calculate_price([1, 1, 2, 2, 3, 4])
end

def test_two_each_of_first_4_books_and_1_copy_each_of_rest
skip
assert_equal 55.6, BookStore.calculate_price([1, 1, 2, 2, 3, 3, 4, 4, 5])
end

def test_two_copies_of_each_book
skip
assert_equal 60.0, BookStore.calculate_price([1, 1, 2, 2, 3, 3, 4, 4, 5, 5])
end

def test_three_copies_of_first_book_and_2_each_of_remaining
skip
assert_equal 68.0, BookStore.calculate_price([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1])
end

def test_three_each_of_first_2_books_and_2_each_of_remaining_books
skip
assert_equal 75.2, BookStore.calculate_price([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2])
end

def test_four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three
skip
assert_equal 102.4, BookStore.calculate_price([1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5])
end

# Problems in exercism evolve over time, as we find better ways to ask
# questions.
# The version number refers to the version of the problem you solved,
# not your solution.
#
# Define a constant named VERSION inside of the top level BookKeeping
# module, which may be placed near the end of your file.
#
# In your file, it will look like this:
#
# module BookKeeping
# VERSION = 1 # Where the version number matches the one in the test.
# end
#
# If you are curious, read more about constants on RubyDoc:
# http://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/constants.html

def test_bookkeeping
skip
assert_equal 0, BookKeeping::VERSION
end
end