Skip to content

Instantly share code, notes, and snippets.

@terrbear
Created May 28, 2015 18:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save terrbear/19731d95d0bc6481d9fd to your computer and use it in GitHub Desktop.
Save terrbear/19731d95d0bc6481d9fd to your computer and use it in GitHub Desktop.
#tunables!
VERBOSE = false
MAX_DEBT = 15_000
MIN_DEBT = 500
MAX_APR = 30
DEBT_MONEY = 1500
SIMULATION_ATTEMPTS = 1000
class Debt
attr_accessor :name, :amount, :apr, :min_payment
def initialize(name, amount, apr, min_payment)
self.name = name
self.amount = amount
self.apr = apr
self.min_payment = min_payment
end
def pay!(amount)
self.amount -= amount
if self.amount < 0
overpaid = self.amount.abs
self.amount = 0
return overpaid
end
return 0
end
def to_s
self.name.to_s
end
def compound!
return if self.paid?
self.amount += self.interest
end
def interest
interest = self.amount
daily_rate = self.apr.to_f / (365 * 100)
30.times do
interest += (daily_rate * interest)
end
interest - self.amount
end
def to_t
[self.name.capitalize.rjust(10),
self.amount.to_i.to_s.rjust(10),
self.apr.to_i.to_s.rjust(10),
self.min_payment.to_i.to_s.rjust(10)
].join("|")
end
def paid?
self.amount <= 0
end
def dup
Debt.new(name, amount, apr, min_payment)
end
end
class Wallet
attr_accessor :debts
def initialize(strategy)
@strategy = strategy
self.debts = []
end
def dup
Wallet.new(@strategy).tap do |w|
w.debts = self.debts.map{|d| d.dup}
end
end
def unpaid_debts
debts.reject{|d| d.paid?}
end
def pay_min_payments(leftover_debts, amount)
return amount if leftover_debts.empty?
debt = nil
Strategy.without_memory{debt = @strategy.choose(leftover_debts, amount)}
next_set = leftover_debts - [debt]
if amount > debt.min_payment
amount += debt.pay!(debt.min_payment)
amount -= debt.min_payment
else
puts "Warning, can't pay min payment to #{debt}" if VERBOSE
end
return pay_min_payments(next_set, amount)
end
def pay!(amount)
debts.each{|d| d.compound!}
amount = pay_min_payments(unpaid_debts, amount)
while amount > 0
debt = @strategy.choose(debts, amount)
puts "paying #{amount.to_i} to #{debt}" if VERBOSE
break if debt.nil?
amount = debt.pay!(amount)
end
end
def to_s
([["Name".center(10),
"Amount".center(10),
"APR".center(10),
"Min Payment".center(10)].join("|")] +
self.debts.map{|d| d.to_t}).join("\n")
end
def empty?
self.debts.select{|d| d.amount > 0}.empty?
end
end
class Strategy
class << self
@memory = true
def memory
@memory
end
def without_memory(&block)
@memory = false
yield
@memory = true
end
end
end
class SmallestAmount < Strategy
def choose(debts, payment)
debts.sort{|a,b| a.amount <=> b.amount}.reject{|d| d.paid?}.first
end
end
class HighestInterest < Strategy
def choose(debts, payment)
debts.sort{|a,b| b.apr <=> a.apr}.reject{|d| d.paid?}.first
end
end
class LowestInterest < Strategy
def choose(debts, payment)
debts.sort{|a,b| a.apr <=> b.apr}.reject{|d| d.paid?}.first
end
end
class WhackAMole < Strategy
def choose(debts, payment)
debts.sort{|a, b| b.amount <=> a.amount}.reject{|d| d.paid?}.first
end
end
class HighestCard < Strategy
@highest = nil
def choose(debts, payment)
if Strategy.memory
if @highest.nil? || @highest.paid?
@highest = choose_without_memory(debts)
end
return @highest
else
return choose_without_memory(debts)
end
end
def choose_without_memory(debts)
debts.sort{|a, b| b.amount <=> a.amount}.reject{|d| d.paid?}.first
end
end
class Sprinter < HighestInterest
def months
3
end
def choose(debts, payment)
debts.select{|d| d.amount < (payment * self.months) && !d.paid?}.sort{|a, b| a.amount <=> b.amount}.first || super(debts, payment)
end
end
class Sprinter6 < Sprinter
def months
6
end
end
class Sprinter12 < Sprinter
def months
12
end
end
class Sprinter1 < Sprinter
def months
1
end
end
def new_wallet(strategy)
wallet = Wallet.new(strategy)
wallet.debts = $debts
wallet.dup
end
def payoff(wallet)
count = 0
loop do
break if wallet.empty?
wallet.pay!(DEBT_MONEY)
count += 1
if count % 10 == 0 && VERBOSE
banner " After #{count} "
puts wallet
end
if count > 100_000
puts "Never gonna finish"
exit 1
end
end
return count
end
def test_strategy(strategy)
payoff new_wallet(strategy)
end
def banner(str = '')
puts "*" * 80
puts str.upcase.center(80, "*")
puts "*" * 80
end
def create_debts!
$debts = []
count = rand(7) + 2
count.times do |i|
balance = rand(MAX_DEBT) + MIN_DEBT
apr = rand(MAX_APR)
min_payment = balance * 0.01
$debts << Debt.new("Debt#{i}", balance, apr, min_payment)
end
end
scoreboard = {
"Smallest Amount" => {wins: 0, losses: 0},
"Highest Interest" => {wins: 0, losses: 0},
"Lowest Interest" => {wins: 0, losses: 0},
"Whack A Mole" => {wins: 0, losses: 0},
"Highest Card" => {wins: 0, losses: 0},
"Sprinter" => {wins: 0, losses: 0},
"Sprinter 6" => {wins: 0, losses: 0},
"Sprinter 12" => {wins: 0, losses: 0},
"Sprinter 1" => {wins: 0, losses: 0},
}
SIMULATION_ATTEMPTS.times do |time|
begin
create_debts!
strategies = {}
strategies["Smallest Amount"] = test_strategy SmallestAmount.new
strategies["Highest Interest"] = test_strategy HighestInterest.new
strategies["Lowest Interest"] = test_strategy LowestInterest.new
strategies["Whack A Mole"] = test_strategy WhackAMole.new
strategies["Highest Card"] = test_strategy HighestCard.new
strategies["Sprinter"] = test_strategy Sprinter.new
strategies["Sprinter 12"] = test_strategy Sprinter12.new
strategies["Sprinter 6"] = test_strategy Sprinter6.new
strategies["Sprinter 1"] = test_strategy Sprinter1.new
sorted = strategies.sort{|a, b| a[1] <=> b[1]}
winner = sorted.first[1]
loser = sorted.last[1]
sorted.select{|strat| strat[1] == winner}.each{|winners| scoreboard[winners.first][:wins] += 1}
if winner != loser
sorted.select{|strat| strat[1] != winner}.each{|losers| scoreboard[losers.first][:losses] += 1}
end
rescue
next
end
end
banner " scoreboard "
puts [
"Name".center(20),
"Wins".center(10),
"Losses".center(10),
"Pct".center(5)
].join(" | ")
out = scoreboard.keys.map do |k|
vals = scoreboard[k]
[k.center(20),
vals[:wins].to_s.rjust(10),
vals[:losses].to_s.rjust(10),
("%.2f" % (vals[:wins].to_f / (vals[:wins] + vals[:losses]))).rjust(5)].join(" | ")
end.join("\n")
puts out
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment