 # paulfioravanti's solution

## to Book Store in the Ruby Track

Published at Jun 03 2019 · 0 comments
Instructions
Test suite
Solution

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 Ruby resources page.

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.

Run the tests from the exercise directory using the following command:

``````ruby book_store_test.rb
``````

To include color from the command line:

``````ruby -r minitest/pride book_store_test.rb
``````

## Source

Inspired by the harry potter kata from Cyber-Dojo. 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.

### book_store_test.rb

``````require 'minitest/autorun'
require_relative 'book_store'

# Common test data version: 1.4.0 33c6b60
class BookStoreTest < Minitest::Test
def test_only_a_single_book
# skip
end

def test_two_of_the_same_book
skip
end

skip
end

def test_two_different_books
skip
end

def test_three_different_books
skip
end

def test_four_different_books
skip
basket = [1, 2, 3, 4]
end

def test_five_different_books
skip
basket = [1, 2, 3, 4, 5]
end

def test_two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three
skip
basket = [1, 1, 2, 2, 3, 3, 4, 5]
end

def test_two_groups_of_four_is_cheaper_than_groups_of_five_and_three
skip
basket = [1, 1, 2, 3, 4, 4, 5, 5]
end

def test_group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three
skip
basket = [1, 1, 2, 2, 3, 4]
end

def test_two_each_of_first_4_books_and_1_copy_each_of_rest
skip
basket = [1, 1, 2, 2, 3, 3, 4, 4, 5]
end

def test_two_copies_of_each_book
skip
basket = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
end

def test_three_copies_of_first_book_and_2_each_of_remaining
skip
basket = [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
basket = [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
basket = [1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5]
end
end``````
``````module BookStore
BASE_PRICE = 8.0
private_constant :BASE_PRICE
BUNDLE_TYPES = (1..5).freeze
private_constant :BUNDLE_TYPES
DISCOUNT_TYPES = [0.0, 0.05, 0.1, 0.2, 0.25].freeze
private_constant :DISCOUNT_TYPES
BUNDLE_PRICE = lambda do |(bundle, discount)|
[bundle, BASE_PRICE * bundle * (1 - discount)]
end
BUNDLE_PRICES =
BUNDLE_TYPES
.zip(DISCOUNT_TYPES)
.map(&BUNDLE_PRICE)
.to_h
private_constant :BUNDLE_PRICES
NO_COST = 0
private_constant :NO_COST

module_function

.then(&method(:generate_initial_book_tally))
.then(&method(:price_books))
end

def price_books(book_tally)
book_tally = remove_zero_tallies(book_tally)
return 0 if book_tally.empty?

(1..book_tally.length)
.each
.with_object(book_tally)
.map(&method(:price_bundle))
.min
end
private_class_method :price_books

.group_by(&:itself)
.values
.map(&:length)
end
private_class_method :generate_initial_book_tally

def remove_zero_tallies(book_tally)
book_tally
.sort
.drop_while(&:zero?)
.reverse
end
private_class_method :remove_zero_tallies

def price_bundle(bundle, book_tally)
tail = book_tally.drop(bundle)

price_books(next_bundle_tally) + BUNDLE_PRICES[bundle]
end
private_class_method :price_bundle
end``````