I wrote a set of python classes for solving the change making problem, in various forms.
Given the coin denominations and values:
- Find all possible ways of making change.
- Find a best way of making change (minimize number of coins).
- Determine whether it is possible to make change.
This version of the project on github.
Main file: change.py
# An implementation solving the change making problem and related problems.
# See http://en.wikipedia.org/wiki/Change-making_problem
from itertools import takewhile
def abstractMethod(): raise NotImplementedError('Abstract method')
class CoinChanger(object):
def __init__(self, denominations):
CoinChanger.verify_denominations(denominations)
self.denominations = tuple(sorted(denominations))
self.cache = dict()
@staticmethod
def verify_denominations(denominations):
assert len(set(denominations)) == len(denominations)
assert all(type(d) == int for d in denominations)
assert all(d > 0 for d in denominations)
def change_for(self, amount):
if amount not in self.cache:
self.cache[amount] = self._value_to_store_for_amount(self._change_for(amount))
for change in self.cache[amount]:
yield change
def _change_for(self, amount):
if amount == 0:
yield self._get_value_for_zero_change()
return
for i, d in self.denominations_less_than_or_equal_to(amount):
remaining_amount = amount - d
for change in self.change_for(remaining_amount):
yield self._get_change_for_denomination(change, d, i)
def denominations_less_than_or_equal_to(self, amount):
'''Yields (index, denomination)'''
return takewhile(lambda (i, d): d <= amount, enumerate(self.denominations))
def _get_value_for_zero_change(self):
abstractMethod()
def _get_change_for_denomination(self, change, denomination, denomination_index):
abstractMethod()
def _value_to_store_for_amount(self, value):
abstractMethod()
class AllPossibilitiesCoinChanger(CoinChanger):
'''Given a set of denominations, yields all possible ways of making change for a given value.'''
def __init__(self, denominations):
super(AllPossibilitiesCoinChanger, self).__init__(denominations)
def _get_value_for_zero_change(self):
return tuple([0] * len(self.denominations))
def _get_change_for_denomination(self, change, denomination, denomination_index):
new_change = list(change)
new_change[denomination_index] += 1
return tuple(new_change)
def _value_to_store_for_amount(self, value):
return list(value)
class MinimumCoinChanger(CoinChanger):
'''Given a set of denominations, a best way (minimize the number of coins) of making change for a given value.'''
def __init__(self, denominations):
super(MinimumCoinChanger, self).__init__(denominations)
def _get_value_for_zero_change(self):
return tuple([0] * len(self.denominations))
def _get_change_for_denomination(self, change, denomination, denomination_index):
new_change = list(change)
new_change[denomination_index] += 1
return tuple(new_change)
def _value_to_store_for_amount(self, value):
try:
return [ min(value, key=sum) ]
except ValueError:
return []
class BooleanCoinChanger(CoinChanger):
'''Given a set of denominations, determines whether it is possible to make change for a given value'''
def __init__(self, denominations):
super(BooleanCoinChanger, self).__init__(denominations)
def _get_value_for_zero_change(self):
return True
def _get_change_for_denomination(self, change, denomination, denomination_index):
assert change == True
return change
def _value_to_store_for_amount(self, value):
for v in value:
return [ True ]
else:
return []
Test file: change_test.py
from change import AllPossibilitiesCoinChanger, MinimumCoinChanger, BooleanCoinChanger
import unittest
class AllPossibilitiesCoinChangerTest(unittest.TestCase):
def test_change_for_zero(self):
changer = AllPossibilitiesCoinChanger((1,3))
self.assertChangeForAmountIs(changer, 0, (0,0))
def test_change_for_one(self):
changer = AllPossibilitiesCoinChanger((1,3))
self.assertChangeForAmountIs(changer, 1, (1,0))
def test_change_for_multiple(self):
changer = AllPossibilitiesCoinChanger((1,3))
self.assertChangeForAmountIs(changer, 3, (3,0), (0,1))
def test_change_for_many(self):
changer = AllPossibilitiesCoinChanger((1,3,4))
self.assertChangeForAmountIs(changer, 6, (6,0,0), (3,1,0), (2,0,1), (0,2,0))
def test_impossible_change(self):
changer = AllPossibilitiesCoinChanger((3,5))
self.assertChangeForAmountIs(changer, 4)
self.assertChangeForAmountIs(changer, 2)
def assertChangeForAmountIs(self, changer, amount, *values):
actual_values = set(changer.change_for(amount))
unordered_values = set(values)
self.assertEquals(unordered_values, actual_values)
self.assertEquals(len(values), len(unordered_values))
class MinimumCoinChangerTest(unittest.TestCase):
def test_change_for_zero(self):
changer = MinimumCoinChanger((1,3))
self.assertChangeForAmountIs(changer, 0, (0,0))
def test_change_for_one(self):
changer = MinimumCoinChanger((1,3))
self.assertChangeForAmountIs(changer, 1, (1,0))
def test_change_for_multiple(self):
changer = MinimumCoinChanger((1,3))
self.assertChangeForAmountIs(changer, 3, (0,1))
def test_change_for_many(self):
changer = MinimumCoinChanger((1,3,4))
self.assertChangeForAmountIs(changer, 6, (0,2,0))
def test_impossible_change(self):
changer = MinimumCoinChanger((3,5))
self.assertNotChangeable(changer, 4)
self.assertNotChangeable(changer, 2)
def assertChangeForAmountIs(self, changer, amount, change):
actual_change = list(changer.change_for(amount))
self.assertEquals([change], actual_change)
def assertNotChangeable(self, changer, amount):
self.assertEquals([], list(changer.change_for(amount)))
class BooleanCoinChangerTest(unittest.TestCase):
def test_change_for_zero(self):
changer = BooleanCoinChanger((1,3))
self.assertChangeable(changer, 0)
def test_change_for_one(self):
changer = BooleanCoinChanger((1,3))
self.assertChangeable(changer, 1)
def test_change_for_multiple(self):
changer = BooleanCoinChanger((1,3))
self.assertChangeable(changer, 3)
def test_change_for_many(self):
changer = BooleanCoinChanger((1,3,4))
self.assertChangeable(changer, 6)
def test_impossible_change(self):
changer = BooleanCoinChanger((3,5))
self.assertNotChangeable(changer, 4)
self.assertNotChangeable(changer, 2)
def assertChangeForAmountIs(self, changer, amount, expected_change):
actual_change = list(changer.change_for(amount))
self.assertEquals(expected_change, actual_change)
def assertChangeable(self, changer, amount):
self.assertChangeForAmountIs(changer, amount, [True])
def assertNotChangeable(self, changer, amount):
self.assertChangeForAmountIs(changer, amount, [])