Created
January 14, 2020 20:24
-
-
Save jazzytomato/b68f662a234277a4861f53c7a60d35ae to your computer and use it in GitHub Desktop.
Making change in Ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Instructions: run with rspec coin_change.rb | |
# | |
# | |
# Making Change | |
# Given a number "x" and a sorted array of coins "coinset", write a function | |
# that returns the amounts for each coin in the coinset that sums up to X or | |
# indicate an error if there is no way to make change for that x with the given | |
# coinset. For example, with x=7 and a coinset of [1,5,10,25], a valid answer | |
# would be {1: 7} or {1: 2, 5: 1}. With x = 3 and a coinset of [2,4] it should | |
# indicate an error. Bonus points for optimality. | |
# Use the following examples to test it out | |
# A. x = 6 coinset = [1,5,10,25] | |
# B. x = 6, coinset = [3,4] | |
# C. x = 6, coinset = [1,3,4] | |
# D. x = 6, coinset = [5,7] | |
NoChangeGiven = Class.new(StandardError) | |
def optimal_coin_change(coinset, x) | |
coinset.reverse! | |
cache = Hash.new do |h, current_amount| | |
h[current_amount] = if current_amount < coinset.min.to_i | |
[] | |
elsif coinset.include?(current_amount) | |
[current_amount] | |
else | |
coinset.reject { |coin| coin > current_amount } | |
.map { |coin| [coin] + h[current_amount - coin] } # recurse by looking up in the hash | |
.select { |coins| coins.sum == current_amount } | |
.min_by(&:size) || [] # keep the smallest number of coins | |
end | |
end | |
raise NoChangeGiven if cache[x].empty? | |
cache[x].reduce(Hash.new(0)) { |a, b| a.merge(b => a[b] + 1) } # count/group by | |
end | |
describe 'Getting optimal coin change' do | |
subject { optimal_coin_change(coinset, amount) } | |
context 'When no change available' do | |
let(:coinset) { [100] } | |
let(:amount) { 1 } | |
it 'should raise an error' do | |
expect { subject }.to raise_error(NoChangeGiven) | |
end | |
end | |
context 'When no coins available' do | |
let(:coinset) { [] } | |
let(:amount) { 1_000 } | |
it 'should raise an error' do | |
expect { subject }.to raise_error(NoChangeGiven) | |
end | |
end | |
context 'When amount is 0' do | |
let(:coinset) { [1, 2, 3, 4] } | |
let(:amount) { 0 } | |
it 'should raise an error' do | |
expect { subject }.to raise_error(NoChangeGiven) | |
end | |
end | |
context 'When no possible change' do | |
let(:coinset) { [5, 7] } | |
let(:amount) { 6 } | |
it 'should raise an error' do | |
expect { subject }.to raise_error(NoChangeGiven) | |
end | |
end | |
context 'Valid cases with optimal solutions' do | |
context do | |
let(:amount) { 6 } | |
let(:coinset) { [1, 5, 10, 25] } | |
it { expect(subject).to eq(5 => 1, 1 => 1) } | |
end | |
context do | |
let(:amount) { 6 } | |
let(:coinset) { [3, 4] } | |
it { expect(subject).to eq(3 => 2) } | |
end | |
context do | |
let(:amount) { 6 } | |
let(:coinset) { [1, 3, 4] } | |
it { expect(subject).to eq(3 => 2) } | |
end | |
context do | |
let(:amount) { 47 } | |
let(:coinset) { [1, 5, 10, 20, 50, 100] } | |
it { expect(subject).to eq(20 => 2, 5 => 1, 1 => 2) } | |
end | |
context do | |
let(:amount) { 247 } | |
let(:coinset) { [1, 5, 10, 20, 50, 100] } | |
it { expect(subject).to eq(100 => 2, 20 => 2, 5 => 1, 1 => 2) } | |
end | |
context do | |
let(:amount) { 359 } | |
let(:coinset) { [1, 5, 10, 20, 50, 100] } | |
it { expect(subject).to eq(100 => 3, 50 => 1, 5 => 1, 1 => 4) } | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment