#!/usr/bin/env python
# encoding: utf-8
"""
CookieJar.py
Created by Sebastian Werner on 2012-01-14.
"""
import sys
import os
import math
#import numpy
import random
class CookieJar:
"""
The cookie jar - an implementation of the homework for pythonbootcamp 2012.01
All calculations regarding values are done as integer to avoid floating point fails :)
The type of coins is hardcoded in the names dictionary - feel free to update this.
"""
# we use reverse keys - makes calculation and constroction of data types easy
# the current US coins
names = {10 : "dimes", 5 : "nickels", 1 : "pennies", 25 : "quarters"}
# valid US coins - silvers wont be used in current implementation
#names = {100 : "silvers", 50 : "halfs", 10 : "dimes", 5 : "nickels", 1 : "pennies", 25 : "quarters"}
# euro coins
#names = {50 : "halbe", 20: "zwanziger", 10 : "zehner", 5 : "fünfer", 2 : "zweier", 1 : "cents"}
def __init__(self, transactions_per_day=5, deplete_jar_quarters=False,washing_mode=False,print_summary_every_year=False,print_summary_of_every_transaction=False):
# contents has the integer number of coins per sorted¡
self.jar = dict([(k,0) for k in self.names.keys()])
# how many days it has been filled
self.age = 0
self.transactions_per_day = transactions_per_day
self.debug_transaction = print_summary_of_every_transaction
self.debug_year = print_summary_every_year
self.washing_mode = washing_mode
self.deplete_jar_quarters = deplete_jar_quarters
pass
def empty(self):
""" Reset the object to initial state - flush the jar and reset its age"""
self.jar = dict([(k,0) for k in self.names.keys()])
self.age = 0
def calc_change(self,transaction):
"""Determine the change for a transaction - return a dictionary of [quarters, dimes, nickels, pennies]"""
change = dict([(k,0) for k in self.names.keys()])
# calculate change in cents
transaction = transaction % 100
if transaction == 0:
# we dont get change back
return change
for coin in sorted(change.keys(), reverse=True):
# find an optimum combination of coins for the change
change[coin] = transaction // coin # the key is integer division
transaction -= change[coin]*coin
return change
def use_pocket_money(self,transaction,pocket_money):
"""Use money in pocket to pay the small change (<1$) for the transaction - if pocket money sufficient"
idea of this aproach:
- start trying to pay with the largest coin
- in case pocket has less then required coins of that type, still use them
- return remaining amount to pay
not covered in this solution is:
- mininum number of coin goal (if available, spend 2 dimes and a nickel instead of a quarter)
"""
if (self.debug_transaction):
print "pay transaction: %i" % transaction
cents_to_pay = transaction % 100
for coin in sorted(pocket_money.keys(),reverse=True):
# determine the amounts of required coins - integer division!
we_need = cents_to_pay // coin
if(we_need <= pocket_money[coin]):
# use all the coins to pay
pocket_money[coin] -= we_need
# reduce the remaining amount to pay
cents_to_pay -= we_need * coin
else:
# use the available coins to pay
# remark: this means you can end up with a remaining amount of coins to pay
# in this case you get change back afterwards
cents_to_pay -= pocket_money[coin] * coin
# all coins used
pocket_money[coin] = 0
return cents_to_pay
def another_day(self):
"""Perform the number of transactions_per_day of purchases - uses & resets the pocket_money. cost per transaction is a round(random()*50,2)."""
pocket = dict([(k,0) for k in self.names.keys()])
for i in range(self.transactions_per_day):
# transaction value hardcoded to a max of 50$
cost = int(random.random()*5000);
# uah, magic - calculate new change we get, using the pocket money on the cost of transaction
new_change = self.calc_change(self.use_pocket_money(cost,pocket))
# add new coins to the pocket - ugly
for k in sorted(pocket.keys(), reverse=True):
pocket[k] += new_change[k]
if (self.debug_transaction):
print "transaction %i done " % i
print "pocket is %s @ %.2f$" % (self.coins(pocket), self.value(pocket))
# wash coin case one:
# Just use Quarters in pocket - dont touch the ones in Jar
if((self.washing_mode) and not (self.deplete_jar_quarters)):
if self.age % 7 == 0:
# Thank god it's Friday!
pocket[25] = max(0, pocket[25]-8)
# end of day - so fill the jar!
for k in sorted(pocket.keys(), reverse=True):
self.jar[k] += pocket[k]
# wash coin case two:
# Use Quarters from Jar for washing - these include the pocket+jar ones
if((self.washing_mode) and (self.deplete_jar_quarters)):
if self.age % 7 == 0:
# Friday again
self.jar[25] = max(0, self.jar[25]-8)
# yearly summary?
if ((self.debug_year) and (self.age+1 == 365)):
print str(self)
# Day is over - so increase age
self.age += 1
def value(self,inventory):
"""Returns value of change in cents. Defaults using the jar."""
return sum([k*v for k,v in inventory.iteritems()])
def coins(self,inventory):
"""Returns number of coins and nicely prints them"""
s = ""
for k in sorted(inventory.keys(), reverse=True):
s += "%i %s, " % (inventory[k],self.names[k])
# return the list - but chop of the ", " at the end.
return s[:-2]
def __str__(self):
"""Print the contents of the jar - age, value and coins in it"""
s = "age %i : " % self.age
s += "%.2f$ (%s)" % ((float(self.value(self.jar))/100.0),self.coins(self.jar))
return s
def run_question_a():
"""What is the average total amount of change accumulated each year (assume X=5)? What is the 1-sigma scatter about this quantity?"""
print "Question a)"
# how many years shall we run and average?
n = 50
jar = CookieJar()
collected_change = []
import numpy
for i in xrange(n):
jar.empty()
for j in xrange(365):
jar.another_day()
collected_change.append(jar.value(jar.jar))
# progress bar!
if i % 10 == 0:
sys.stdout.write('.')
sys.stdout.flush()
print '',n, 'experiments finished'
print "Mean was %.2f. Standard deviation was $%.2f." % (numpy.mean(collected_change)/100.0, (numpy.std(collected_change)/100.0))
print
def run_question_b():
"""What coin (quarter, dime, nickel, penny) are you most likely to accumulate over time? Second most likely? Does it depend on X?"""
print "Question b)"
# how many years shall we run and average?
n = 10
# in which range of purchases?
min_purchases = 1
max_purchases = 10
print "Purch./day | Most acc. | 2nd most. acc"
print "-----------+-----------+--------------"
for purchases_per_day in xrange(min_purchases,max_purchases+1):
# create the Jar that is set for the amount of transactions
jar = CookieJar(transactions_per_day=purchases_per_day)
# another storage structure - actually we should have specified THIS as the object... damn.
top_list = dict([(k,0) for k in jar.names.keys()])
for i in xrange(n):
# for performance reasons: empty the jar is FASTER then recreating a new object :)
jar.empty()
for j in xrange(365):
jar.another_day()
# record coins collected in that specific year
for k in sorted(jar.jar.keys(), reverse=True):
top_list[k] += jar.jar[k]
# now sorting voodoo
max1, max2 = (None, 0), (None, 0)
for k,v in top_list.iteritems():
if v > max2[1]:
if v > max1[1]:
max2 = max1
max1 = k,v
else:
max2 = k,v
print "% 10d | % 9s | % 13s" % (purchases_per_day,jar.names[max1[0]],jar.names[max2[0]])
print
def run_question_c():
"""Let’s say you need 8 quarters per week to do laundry. How many quarters do you have at the end of the year? (if you do not have enough quarters at the end of each week, use only what you have)."""
print "Question c)"
# how many years shall we run and average?
n = 100
# lets compare three cases
jar = CookieJar()
if not jar.jar.has_key(25):
print "Current set of coins does not have quarters...Don't know what to do!"
print "PANIC!"
return
num_quarters = []
for i in xrange(n):
jar.empty()
for j in xrange(365):
jar.another_day()
num_quarters.append(jar.jar[25])
print "Average quarters per year (no washing): %.2f" % (sum(num_quarters) / len(num_quarters))
# now lets wash... BUT just use quarters from the pocket to pay
jar.washing_mode = True
# reset the counting array
num_quarters = []
for i in xrange(n):
jar.empty()
for j in xrange(365):
jar.another_day()
num_quarters.append(jar.jar[25])
print "Average quarters per year (washing): %.2f" % (sum(num_quarters) / len(num_quarters))
# now wash AND use quarters from the jar
jar.deplete_jar_quarters=True
# and... dont forget to empty the array
num_quarters = []
for i in xrange(n):
jar.empty()
for j in xrange(365):
jar.another_day()
num_quarters.append(jar.jar[25])
print "Average quarters per year (washing, also use jar-quarters): %.2f" % (sum(num_quarters) / len(num_quarters))
print
# always look on the bright side of life!
if __name__ == "__main__":
run_question_a()
run_question_b()
run_question_c()