Skip to content

Instantly share code, notes, and snippets.

@nodename
Created February 20, 2012 01:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nodename/1866910 to your computer and use it in GitHub Desktop.
Save nodename/1866910 to your computer and use it in GitHub Desktop.
Coding Exercise #2: Fantasy Banking
(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)))
(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))
(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)))
(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))})
(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