Skip to content

Instantly share code, notes, and snippets.

@mikebridge
Created April 20, 2012 04:09
Show Gist options
  • Save mikebridge/2425955 to your computer and use it in GitHub Desktop.
Save mikebridge/2425955 to your computer and use it in GitHub Desktop.
Codelesson Clojure Assignment 7 & 8
(ns codelesson.assignment-8
(:require [clojure.string :as str])
(:import [org.joda.time Years DateTime])
(:import [java.text NumberFormat])
(:import [java.math BigDecimal])
(:import [codelesson.assignment8 JAccount JAccount$JAccountType]))
(def accounts (ref {}))
(def account-types (ref {}))
(defprotocol Account
"Defines a set of actions that can be performed on an account"
(account-type [this])
(set-account-type [this val])
(balance-dollars [this])
(set-balance-dollars [this val])
(date-created [this])
(set-date-created [this val])
(tx-count [this])
(set-tx-count [this val])
(get-map [this]
"new account")
(new-account-amt [this new-amt]
"update the account"))
(defrecord ClojureAccount [account-type balance-dollars date-created tx-count]
Account
(account-type [this] account-type)
(balance-dollars [this] balance-dollars)
(set-balance-dollars [this val]
;; why doesn't this work in clojure 1.4?
(assoc this :balance-dollars val))
(date-created [this] date-created)
(set-date-created [this val]
(assoc this :date-created val))
(tx-count [this] tx-count)
(set-tx-count [this amt] (assoc this :tx-count amt))
(get-map [this]
{:pre [(instance? DateTime date-created)]}
{:account-type account-type
:balance-dollars balance-dollars
:date-created date-created
:tx-count 0})
(new-account-amt [this new-amt]
"create a new account record with fn (+/-) applied"
(ClojureAccount. account-type new-amt date-created tx-count)))
;; Java Accounts
(def acct-mapping {:chequing JAccount$JAccountType/CHEQUING
:savings JAccount$JAccountType/SAVINGS
:money-market JAccount$JAccountType/MONEYMARKET})
(def reverse-acct-mapping
(apply hash-map (interleave (vals acct-mapping) (keys acct-mapping))))
(defn convert-to-jaccount-type [account-type]
(account-type acct-mapping))
(defn convert-from-jaccount-type [java-account-type]
(reverse-acct-mapping java-account-type ))
(declare create-jaccount)
(extend-type JAccount
Account
(account-type [this] (convert-from-jaccount-type (.getAccountType this)))
(set-account-type [this acct-type] (.setAccountType this (convert-to-jaccount-type acct-type)))
(balance-dollars [this] (.doubleValue (.getBalance this)))
(set-balance-dollars [this amt]
(create-jaccount (account-type this)
amt
(date-created this)
(tx-count this)))
;; (do (.setBalance this (BigDecimal. amt))
;; this))
(tx-count [this] (.getTxCount this))
(set-tx-count [this amt]
(create-jaccount (account-type this)
(balance-dollars this)
(date-created this)
amt))
(date-created [this] (.getDateCreated this))
(set-date-created [this date]
(create-jaccount (account-type this)
(balance-dollars this)
date
(tx-count this))))
(defn create-jaccount [account-type balance-dollars date-created tx-count]
(let [new-acct (JAccount.)]
(.setBalance new-acct (BigDecimal. balance-dollars))
(.setAccountType new-acct (convert-to-jaccount-type account-type))
(.setDateCreated new-acct date-created)
(.setTxCount new-acct tx-count)
new-acct))
;; end Java Accounts
(defn alter-account-amt [account-id fn amt]
"create a new account record with fn (+/-) applied"
{:pre [(fn? fn)
(number? amt)
(contains? @accounts account-id)]}
(let [current-balance (balance-dollars (@accounts account-id))]
(set-balance-dollars (@accounts account-id) (fn current-balance amt))))
(defn find-account [account-id]
(@accounts account-id))
(defn save-account [account-id new-account]
"replace account with new one"
(alter accounts assoc account-id new-account))
(defn save-new-account [id account]
{:pre [(contains? @account-types (account-type account))]}
(dosync (alter accounts assoc id account)))
;; utils
(defn account-logger [key ref old new]
;; (println (str "ACCOUNTS " key "\n WAS:" old "\n NOW:" new "END"))
)
(def uniq-id (ref 0))
(defn new-uniq-id []
(dosync (alter uniq-id inc)))
(defn years-since [date-time-1 date-time-2]
(.getYears (Years/yearsBetween date-time-1 date-time-2)))
(defn format-money [f]
{:pre [(number? f)]}
(.format (NumberFormat/getCurrencyInstance) f))
;; account definitions
(defn create-account-type [interest-rate overdraft-rate maximum-overdraft]
{:interest-rate interest-rate
:overdraft-rate overdraft-rate
:maximum-overdraft maximum-overdraft})
(defn reset-account-types []
(dosync (ref-set account-types {})))
(defn save-new-account-type [id account-type]
(dosync (alter account-types assoc id account-type)))
(add-watch accounts "accounts" account-logger)
;; overdraft available
(def original-overdraft 100000)
(def overall-overdraft (ref original-overdraft))
;; bank-total
(def original-bank 1000000)
(def overall-bank (ref original-bank))
(defn reset-accounts []
(dosync (ref-set accounts {})))
(defn lookup-account-type-field [account-id field]
{:post (number? %)}
"look up the corresponding field in this account's account type"
(let [account-type (account-type (@accounts account-id))]
((@account-types account-type) field)))
(defn inc-transaction-count [account-id]
"create a new account record with increased tx count"
{:pre [(contains? @accounts account-id)]}
(set-tx-count (@accounts account-id) (inc (tx-count (@accounts account-id)))))
(defn bonus-percentage [years rate balance]
{:pre [(number? years) (number? rate) (number? balance)]}
(cond
(> 0 balance) 0.0
(<= years 2) 0.0
(and (> years 2) (< years 5)) (if (> 500 balance)
(/ rate 32)
(/ rate 24))
(>= years 5) (if (> 500 balance)
(/ rate 16)
(/ rate 8))))
(defn apply-bonus [account-id]
"Apply appropriate bonus every 10 txes, if account is not in overdraft"
{:pre [(contains? @accounts account-id)] }
(if (= 0
(mod (tx-count (@accounts account-id)) 10))
(let [age-in-years (years-since (date-created (@accounts account-id)) (DateTime.))
current-balance (balance-dollars (@accounts account-id))
interest-rate (lookup-account-type-field account-id :interest-rate)
bonus-percent (bonus-percentage age-in-years interest-rate current-balance)
bonus (* bonus-percent current-balance)
]
(if (> current-balance 0)
(do
(save-account account-id (alter-account-amt account-id + bonus))
(alter overall-bank - bonus))))))
(defn apply-interest [account-id]
"Apply appropriate interest every 25 txes, if account is not in overdraft"
{:pre [(contains? @accounts account-id)] }
(if (= 0
(mod (tx-count (@accounts account-id)) 25))
(let [interest-rate (lookup-account-type-field account-id :interest-rate)
current-balance (balance-dollars (@accounts account-id))
interest (* interest-rate current-balance)
]
(if (> current-balance 0)
(do
(save-account account-id (alter-account-amt account-id + interest))
(alter overall-bank - interest))))))
(defn validate-overdrafts []
(= (format-money (- @overall-overdraft original-overdraft))
(format-money (reduce + (filter neg? (map balance-dollars (vals @accounts)))))))
(defn repay-overdrafts [account-id amt]
{:pre [(contains? @accounts account-id) (number? amt)] }
"repay the overdraft and return the remainder"
(let [outstanding-overdraft (max 0 (- (balance-dollars (@accounts account-id))))
to-repay (min amt outstanding-overdraft)
left-to-transfer (- amt to-repay)
]
(if (< 0 to-repay)
(alter overall-overdraft + to-repay)
)
left-to-transfer))
(defn take-from-overdraft [orig-balance new-balance]
"if we're asking for a negative balance, take it from the overdraft acct"
(if (< new-balance -1000)
(throw (Exception. "Exceeded maximum overdraft"))
)
(if (> new-balance 0)
0
(let [start (min orig-balance 0)
required (- start new-balance)]
(if (> 0 (- @overall-overdraft required))
(throw (Exception. "Bank declined overdraft")))
(alter overall-overdraft - required)
required)))
(defn withdraw [account-id amt]
(let [initial-amt (balance-dollars (@accounts account-id))]
(save-account account-id (alter-account-amt account-id - amt))
(save-account account-id (inc-transaction-count account-id))
(apply-interest account-id)
(apply-bonus account-id)
(take-from-overdraft initial-amt (balance-dollars (@accounts account-id)))
))
(defn deposit [account-id amt]
;; (let [amt-to-apply (repay-overdrafts account-id amt)]
(repay-overdrafts account-id amt)
(save-account account-id (alter-account-amt account-id + amt))
(save-account account-id (inc-transaction-count account-id))
(apply-interest account-id)
(apply-bonus account-id))
(defn apply-overdraft-penalty [account-id]
;; NOTE: You can apply a penalty on an overdrawn account, to
;; exceed the maximum overdrawn.
(do
(save-account account-id (alter-account-amt account-id - 10))
(alter overall-overdraft - 10)
(alter overall-bank + 10)
))
(defn transfer [from-account-id to-account-id amt]
{:pre [(contains? @accounts from-account-id)
(contains? @accounts to-account-id)]
:post [(validate-overdrafts)]}
(try
(dosync
(withdraw from-account-id amt)
(deposit to-account-id amt)
[(@accounts from-account-id) (@accounts to-account-id)]
)
(catch Exception e
(println "TX Failed" (.getMessage e))
;; I interpret the "in this mode" to mean
;; "when the overdraft request cannot be completed"
(dosync
(apply-overdraft-penalty from-account-id)))))
;; initialize accounts
(save-new-account-type :chequing (create-account-type 0.02 0.10 1000))
(save-new-account-type :savings (create-account-type 0.04 0.075 1000))
(save-new-account-type :money-market (create-account-type 0.06 0.05 1000))
(defn max-day-of-month [year month]
(.getMaximumValue (.dayOfMonth (DateTime. year month 1 12 0)))
)
(defn random-account-type []
(let [keys (keys @account-types)]
(nth keys (rand-int (count keys)))))
(defn random-date []
"some random date since Jan 1, seven years ago"
(def year-now (.get (.year (DateTime.))))
(def month (+ 1 (rand-int 12)))
(def year (- year-now (rand-int 7)))
(def day (+ 1 (rand-int (max-day-of-month year month))))
(def hour (rand-int 24))
(def minute (rand-int 60))
(DateTime. year month day hour minute)
)
(defn random-clojure-account []
;;(create-account (random-account-type) 500 (random-date)))
(ClojureAccount. (random-account-type) 500 (random-date) 0))
(defn random-java-account []
(create-jaccount (random-account-type) 500 (random-date) 0))
(defn random-accounts-clj [n]
(dotimes [x n]
(save-new-account (new-uniq-id) (random-clojure-account))))
(defn random-accounts-java [n]
(dotimes [x n]
(save-new-account (new-uniq-id) (random-java-account))))
(defn select-random-amt []
(+ 100 (* 10 (rand-int (/ (- 500 100) 10)))))
(defn select-random-account [accounts]
(let [key-list (keys accounts)]
(nth key-list (rand-int (count key-list)))))
(defn exec-random-transaction []
(let [from-account-id (select-random-account @accounts)
to-account-id (select-random-account (dissoc @accounts from-account-id))]
(transfer from-account-id to-account-id (select-random-amt))))
(defn random-transactions [n]
(dotimes [x n]
(exec-random-transaction)))
(defn format-acct [account-id]
(str "acct #" account-id
" balance: " (format-money (balance-dollars (@accounts account-id)))
" tx-count: " (tx-count (@accounts account-id))))
(defn format-result [accounts overdraft bank]
(let [key-list (keys accounts)]
(str "Bank balance: " (format-money bank) "\n"
"Overdraft balance: " (format-money overdraft) "\n"
(str/join "\n" (map format-acct key-list)))))
(defn main[]
(do
(random-accounts-clj 5)
(random-accounts-java 5)
(random-transactions 100)
(println (format-result @accounts @overall-overdraft @overall-bank))))
(defn check-result []
;; hardcoded values
(let [orig-accts-total (* (+ 500) 20)
final-accts-total (reduce + (map balance-dollars (vals @accounts)))
total-overdraft (reduce + (filter neg? (map balance-dollars (vals @accounts))))]
(println "overall balance delta" (format-money (- final-accts-total orig-accts-total)))
(println "... should match bank balance delta" (format-money (- original-bank @overall-bank)))
(println "overdraft delta" (format-money (- original-overdraft @overall-overdraft)))
(println "... should match total overdraft" (format-money total-overdraft))))
package codelesson.assignment8;
import java.math.BigDecimal;
import org.joda.time.DateTime;
public class JAccount {
public enum JAccountType { CHEQUING, SAVINGS, MONEYMARKET };
private DateTime _dateCreated;
private String _accountId;
private BigDecimal _balance;
private JAccountType _accountType;
private int _txCount = 0;
public BigDecimal getBalance() {
return _balance;
}
public void setBalance(BigDecimal val) {
_balance = val;
}
public String getAccountId() {
return _accountId;
}
public void setAccountId(String val) {
_accountId = val;
}
public int getTxCount() {
return _txCount;
}
public void setTxCount(int val) {
_txCount = val;
}
public DateTime getDateCreated() {
return _dateCreated;
}
public void setDateCreated(DateTime val) {
_dateCreated = val;
}
public JAccountType getAccountType() {
return _accountType;
}
public void setAccountType (JAccountType val) {
_accountType = val;
}
public String Greet () {
return "Hello From Java";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment