Created
February 20, 2012 01:06
-
-
Save nodename/1866910 to your computer and use it in GitHub Desktop.
Coding Exercise #2: Fantasy Banking
This file contains 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
(ns account | |
(:import [java.util Date] | |
[java.lang Math]) | |
(:use [util])) | |
(def account-type [:checking :savings :money-market]) | |
(def interest-rate (zipmap account-type [0.02 0.04 0.06])) | |
(def overdraft-charge (zipmap account-type [0.10 0.075 0.05])) | |
(def initial-balance 500) | |
(def interest-interval 25) | |
(def bonus-interval 10) | |
(def today (Date.)) | |
;; TODO fix these if today is Feb 29! | |
(def two-years-ago (Date. (- (.getYear today) 2) (.getMonth today) (.getDate today))) | |
(def five-years-ago (Date. (- (.getYear today) 5) (.getMonth today) (.getDate today))) | |
(def ten-years-ago (Date. (- (.getYear today) 10) (.getMonth today) (.getDate today))) | |
(def two-years (- (.getTime today) (.getTime two-years-ago))) | |
(def five-years (- (.getTime today) (.getTime five-years-ago))) | |
(def ten-years (- (.getTime today) (.getTime ten-years-ago))) | |
(defn select-random-past-date | |
[] | |
(Date. (+ (.getTime ten-years-ago) (long (Math/floor (rand ten-years)))))) | |
(defn create-random-account | |
[] | |
{:type (select-random-element account-type) | |
:incept-date (select-random-past-date) | |
:transaction-history []}) | |
(defn balance | |
[account] | |
(let [transactions (.size (:transaction-history account))] | |
(if (zero? transactions) | |
initial-balance | |
(:balance (last (:transaction-history account)))))) | |
;; we assume that interest is applied to the entire current balance | |
(defn interest-amount | |
[account] | |
(let [rate (interest-rate (:type account)) | |
balance (balance account)] | |
(* balance rate))) | |
(defn- balance-increase | |
[account] | |
(let [balance (balance account) | |
history (:transaction-history account) | |
prior-index (- (.size history) bonus-interval) | |
prior-balance (:balance (history prior-index))] | |
(- balance prior-balance))) | |
(defn- age | |
[account] | |
(- (.getTime today) (.getTime (:incept-date account)))) | |
(defn- age-category [account] | |
(let [ages [::under-two-years ::two-to-five-years ::over-five-years] | |
age (age account)] | |
(cond (< age two-years) ::under-two-years | |
(> age five-years) ::over-five-years | |
:else ::two-to-five-years))) | |
(defn- balance-increase-category [account] | |
(let [increases [::none ::under-five-hundred ::five-hundred-or-over] | |
increase (balance-increase account)] | |
(cond (< increase 0) ::none | |
(>= increase 500) ::five-hundred-or-over | |
:else ::under-five-hundred))) | |
(defn- bonus-category [account] | |
[(age-category account) (balance-increase-category account)]) | |
(defmulti bonus-rate bonus-category) | |
(defmethod bonus-rate [::two-to-five-years ::under-five-hundred] [account] | |
(/ (interest-rate (:type account)) 32)) | |
(defmethod bonus-rate [::two-to-five-years ::five-hundred-or-over] [account] | |
(/ (interest-rate (:type account)) 24)) | |
(defmethod bonus-rate [::over-five-years ::under-five-hundred] [account] | |
(/ (interest-rate (:type account)) 16)) | |
(defmethod bonus-rate [::over-five-years ::five-hundred-or-over] [account] | |
(/ (interest-rate (:type account)) 8)) | |
(defmethod bonus-rate :default [account] | |
0) | |
;; calling balance-increase again? there must be a more efficient way | |
(defn bonus-amount | |
[account] | |
(* (bonus-rate account) (balance-increase account))) |
This file contains 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
(ns bank | |
(:use [account] :reload-all)) | |
(defn add-random-account | |
[bank] | |
{:accounts (conj (:accounts bank) (create-random-account)) :overdraft-fund (:overdraft-fund bank)}) | |
;; assuming this interpretation of the overdraft conditions: | |
(defn overdraft-allowed | |
[bank account transaction] | |
(cond (< (- (balance account) (:amount transaction)) -1000) false | |
(zero? (:overdraft-fund bank)) false | |
:else true)) | |
(defn apply-simple-transaction | |
[bank account transaction] | |
(let [updated-transaction (assoc transaction :balance (+ (balance account) (:amount transaction))) | |
updated-account (assoc account | |
:transaction-history (conj (:transaction-history account) updated-transaction))] | |
(assoc-in bank [:accounts (.indexOf (bank :accounts) account)] updated-account))) | |
(defn apply-overdraft | |
[bank account transaction] | |
(let [overdraft-amount (- (:amount transaction) (balance account)) | |
charge (* overdraft-amount (overdraft-charge (:type account))) | |
updated-transaction (assoc transaction :balance (- (+ overdraft-amount charge))) | |
updated-account (assoc account | |
:transaction-history (conj (:transaction-history account) updated-transaction)) | |
updated-overdraft-fund (- (:overdraft-fund bank) overdraft-amount)] | |
(assoc-in bank [:accounts (.indexOf (bank :accounts) account)] updated-account) | |
(assoc bank :overdraft-fund updated-overdraft-fund))) | |
(def failed-overdraft-charge 10) | |
(defn apply-failed-overdraft | |
[bank account transaction] | |
(let [updated-transaction (assoc transaction :balance (- (balance account) failed-overdraft-charge)) | |
updated-account (assoc account | |
:transaction-history (conj (:transaction-history account) updated-transaction))] | |
(assoc-in bank [:accounts (.indexOf (bank :accounts) account)] updated-account))) | |
;; current balance of account is defined by the balance field of the last transaction. | |
;; apply-bonus and apply-interest are not considered transactions themselves. | |
(defn apply-extras | |
[bank account amount] | |
(let [transaction (last (:transaction-history account)) | |
updated-transaction (assoc transaction :balance (+ (:balance transaction) amount)) | |
updated-account (assoc-in account [:transaction-history (.indexOf (account :transaction-history) transaction)] updated-transaction)] | |
(assoc-in bank [:accounts (.indexOf (bank :accounts) account)] updated-account))) | |
(defn apply-interest | |
[bank account] | |
(apply-extras bank account (interest-amount account))) | |
(defn apply-bonus | |
[bank account] | |
(apply-extras bank account (bonus-amount account))) | |
(defn extra-applies? | |
[transaction-number interval] | |
(cond | |
(zero? transaction-number) false | |
(zero? (mod transaction-number interval)) true | |
:else false)) | |
(defn apply-transaction | |
[bank account transaction] | |
(let [account-index (.indexOf (bank :accounts) account) | |
bank (cond (>= (:amount transaction) 0) (apply-simple-transaction bank account transaction) | |
(>= (- (balance account) (:amount transaction)) 0) (apply-simple-transaction bank account transaction) | |
(true? (overdraft-allowed bank account transaction)) (apply-overdraft bank account transaction)) | |
transaction-number (.size (:transaction-history account)) | |
updated-account ((bank :accounts) account-index) | |
bank (if (extra-applies? transaction-number bonus-interval) (apply-bonus bank updated-account) bank) | |
updated-account ((bank :accounts) account-index) | |
bank (if (extra-applies? transaction-number interest-interval) (apply-interest bank updated-account) bank)] | |
bank)) |
This file contains 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
(ns fantasy-banking | |
(:use [account] | |
[transaction] | |
[bank] | |
[util] :reload-all)) | |
(defn add-n-accounts | |
[bank n] | |
(loop [account-index 0 | |
bank bank] | |
(if (= n account-index) | |
bank | |
(recur (inc account-index) (add-random-account bank))))) | |
(defn apply-n-transactions | |
[bank n] | |
(loop [transaction-index 0 | |
bank bank] | |
(if (= n transaction-index) | |
bank | |
(let [accounts (bank :accounts) | |
account (select-random-element accounts) | |
transaction (create-random-transaction)] | |
(recur (inc transaction-index) (apply-transaction bank account transaction)))))) | |
(def N 10) | |
(def the-bank {:accounts [] :overdraft-fund 100000}) | |
(defn run-simulation | |
[bank] | |
(let [bank (add-n-accounts bank N) | |
bank (apply-n-transactions bank (* 100 N))] | |
bank)) | |
(defn show-balances | |
[bank] | |
(println "Account balances:") | |
(loop [account-index 0 | |
_ nil] | |
(if (= account-index N) | |
bank | |
(recur (inc account-index) (println (round-to-two-decimal-places (balance ((bank :accounts) account-index)))))))) | |
(defn show-overdraft-fund-balance | |
[bank] | |
(println "Overdraft fund balance:") | |
(println (round-to-two-decimal-places (bank :overdraft-fund)))) | |
(show-overdraft-fund-balance (show-balances (run-simulation the-bank))) |
This file contains 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
(ns transaction | |
(:use [util])) | |
(defn rand-transaction-amount | |
[] | |
(+ 100 (* 10 (rand-int 40)))) | |
(defn create-random-transaction | |
[] | |
{:amount (* (select-random-element [-1 1]) (rand-transaction-amount))}) |
This file contains 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
(ns util) | |
(defn select-random-element | |
[v] | |
(v (rand-int (count v)))) | |
(defn round-to-two-decimal-places | |
[n] | |
(format "%6.2f" n)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment