Created
April 20, 2012 04:09
-
-
Save mikebridge/2425955 to your computer and use it in GitHub Desktop.
Codelesson Clojure Assignment 7 & 8
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 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)))) |
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
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