Skip to content

Instantly share code, notes, and snippets.

@mrryanjohnston
Created June 14, 2018 21:42
Show Gist options
  • Save mrryanjohnston/7cc250b8e808ea274fbcdde5547f7cf5 to your computer and use it in GitHub Desktop.
Save mrryanjohnston/7cc250b8e808ea274fbcdde5547f7cf5 to your computer and use it in GitHub Desktop.
Change Maker

Change Maker

Description

Makes change for configured currencies.

Usage

# Initialize your changemaker
# Defaults to USD
ChangeMaker.tap do |c|
  # get denominations of 100 cents
  c.fewest_number_of_coins(100)
end

# To change to another currency:
ChangeMaker.new(currency: :eur).tap do |c|
  # or...
  c.currency = :usd
end
class ChangeMaker
Currencies = {
usd: [
25,
10,
5,
1
],
eur: [
200,
100,
50,
20,
10,
5,
2,
1
]
}.freeze
attr_accessor :currency
def initialize(currency: :usd)
self.currency = currency
end
def currency=(value)
raise CurrencyInvalidError, "Currency #{value} not currency supported" if Currencies[value].nil?
@currency = value
end
def currency_map
Currencies[currency]
end
def fewest_number_of_coins(cents)
output = {}
remainder = cents
currency_map.each do |denomination|
output[denomination] = remainder / denomination
remainder = remainder % denomination
end
output
end
class CurrencyInvalidError < StandardError; end
end
require 'rspec'
require_relative 'change_maker'
RSpec.describe ChangeMaker do
context 'currency' do
it 'supports :usd and :eur' do
expect(described_class::Currencies.has_key? :usd).to be true
expect(described_class::Currencies.has_key? :eur).to be true
end
it 'maps set of denominations to curreny names' do
actual = described_class.new(currency: :eur).currency_map
expected = described_class::Currencies[:eur]
expect(actual).to eq(expected)
end
it 'raises an error when invalid currency set' do
expect { described_class.new(currency: :invalid) } .to raise_error described_class::CurrencyInvalidError
end
end
context 'with default usd' do
let (:change_maker) { described_class.new }
it 'can make change for 1 dollar' do
change_maker.tap do |c|
c.fewest_number_of_coins(100).tap do |f|
expect(f[25]).to eq 4
expect(f[10]).to eq 0
expect(f[5]).to eq 0
expect(f[1]).to eq 0
end
end
end
it 'can make change for 1 dollar and 21' do
change_maker.tap do |c|
c.fewest_number_of_coins(121).tap do |f|
expect(f[25]).to eq 4
expect(f[10]).to eq 2
expect(f[5]).to eq 0
expect(f[1]).to eq 1
end
end
end
end
context 'with eur' do
let (:change_maker) { described_class.new(currency: :eur) }
it 'can make change for 1000 euro cents' do
change_maker.tap do |c|
c.fewest_number_of_coins(1000).tap do |f|
expect(f[200]).to eq 5
expect(f[100]).to eq 0
expect(f[50]).to eq 0
expect(f[20]).to eq 0
expect(f[10]).to eq 0
expect(f[5]).to eq 0
expect(f[2]).to eq 0
expect(f[1]).to eq 0
end
end
end
it 'can make change for 121 euro cents' do
change_maker.tap do |c|
c.fewest_number_of_coins(1213).tap do |f|
expect(f[200]).to eq 6
expect(f[100]).to eq 0
expect(f[50]).to eq 0
expect(f[20]).to eq 0
expect(f[10]).to eq 1
expect(f[5]).to eq 0
expect(f[2]).to eq 1
expect(f[1]).to eq 1
end
end
end
end
end
@mrryanjohnston
Copy link
Author

Revised fewest_number_of_coins using reduce (and, of course, tap):

  def fewest_number_of_coins(cents)
    {}.tap do |output|
      currency_map.reduce(cents) do |remainder, denomination|
        output[denomination] = remainder / denomination
        remainder % denomination
      end
    end
  end

@mrryanjohnston
Copy link
Author

This is more lines, but it allows for the wallet object and serialization to be decoupled.

  attr_accessor :currency
  attr_accessor :wallet

  def initialize(currency: :usd, wallet: [])
    self.currency = currency
    self.wallet = wallet
  end
#...
  def put_cents_in_wallet(cents)
    currency_map.reduce(cents) do |remainder, denomination|
      wallet << remainder / denomination
      remainder % denomination
    end
    self
  end

  def wallet_as_hash
    wallet.each_with_index.reduce({}) do |hash, (amount_of_coins, index)|
      hash.merge({ currency_map[index] => amount_of_coins })
    end
  end

  def wallet_as_string
    wallet.each_with_index.reduce("Current wallet:\n") do |string, (amount_of_coins, index)|
      string << "#{currency_map[index]}: #{amount_of_coins}\n"
    end
  end

Tests need to be updated for the above to pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment