Created
November 29, 2016 01:54
-
-
Save trilliwon/eb0a86c1a275cc8a458db8d33945a6da to your computer and use it in GitHub Desktop.
money class with TDD
This file contains hidden or 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
public struct HashTable<Key: Hashable, Value> { | |
fileprivate typealias Element = (key: Key, value: Value) | |
fileprivate typealias Bucket = [Element] | |
fileprivate var buckets: [Bucket] | |
fileprivate(set) var count = 0 | |
public init(capacity: Int) { | |
assert(capacity > 0) | |
buckets = .init(repeating: [], count: capacity) | |
} | |
public var isEmpty: Bool { | |
return count == 0 | |
} | |
fileprivate func indexForKey(_ key: Key) -> Int { | |
return abs(key.hashValue) % buckets.count | |
} | |
} | |
// MARK: - Basic operations | |
extension HashTable { | |
public subscript(key: Key) -> Value? { | |
get { | |
return valueForKey(key) | |
} | |
set { | |
if let value = newValue { | |
_ = updateValue(value, forKey: key) | |
} else { | |
_ = removeValueForKey(key) | |
} | |
} | |
} | |
public func valueForKey(_ key: Key) -> Value? { | |
let index = indexForKey(key) | |
for element in buckets[index] { | |
if element.key == key { | |
return element.value | |
} | |
} | |
return nil // key not in hash table | |
} | |
public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { | |
let index = indexForKey(key) | |
// Do we already have this key in the bucket? | |
for (i, element) in buckets[index].enumerated() { | |
if element.key == key { | |
let oldValue = element.value | |
buckets[index][i].value = value | |
return oldValue | |
} | |
} | |
// This key isn't in the bucket yet; add it to the chain. | |
buckets[index].append((key: key, value: value)) | |
count += 1 | |
return nil | |
} | |
public mutating func removeValueForKey(_ key: Key) -> Value? { | |
let index = indexForKey(key) | |
// Find the element in the bucket's chain and remove it. | |
for (i, element) in buckets[index].enumerated() { | |
if element.key == key { | |
buckets[index].remove(at: i) | |
count -= 1 | |
return element.value | |
} | |
} | |
return nil // key not in hash table | |
} | |
public mutating func removeAll() { | |
buckets = .init(repeating: [], count: buckets.count) | |
count = 0 | |
} | |
} | |
// MARK: - Helper methods for inspecting the hash table | |
extension HashTable { | |
public var keys: [Key] { | |
var a = [Key]() | |
for bucket in buckets { | |
for element in bucket { | |
a.append(element.key) | |
} | |
} | |
return a | |
} | |
public var values: [Value] { | |
var a = [Value]() | |
for bucket in buckets { | |
for element in bucket { | |
a.append(element.value) | |
} | |
} | |
return a | |
} | |
} | |
// MARK: - For debugging | |
extension HashTable: CustomStringConvertible { | |
public var description: String { | |
return buckets.flatMap { b in b.map { e in "\(e.key) = \(e.value)" } }.joined(separator: ", ") | |
} | |
} | |
extension HashTable: CustomDebugStringConvertible { | |
public var debugDescription: String { | |
var s = "" | |
for (i, bucket) in buckets.enumerated() { | |
s += "bucket \(i): " + bucket.map { e in "\(e.key) = \(e.value)" }.joined(separator: ", ") + "\n" | |
} | |
return s | |
} | |
} |
This file contains hidden or 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
import Foundation | |
protocol Expression { | |
func reduce(_ bank: Bank, to: String) -> Money | |
func plus(_ addend: Expression) -> Expression | |
func times(_ multiplier: Int) -> Expression | |
} | |
class Money: Equatable, Expression { | |
internal var amount: Int | |
internal var currencyString: String | |
init(amount: Int, currencyString: String) { | |
self.amount = amount | |
self.currencyString = currencyString | |
} | |
static func dollar(_ amount: Int) -> Money { | |
return Money(amount: amount, currencyString: "USD") | |
} | |
static func frenc(_ amount: Int) -> Money { | |
return Money(amount: amount, currencyString: "CHF") | |
} | |
func currency() -> String { | |
return currencyString | |
} | |
func times(_ multiplier: Int) -> Expression { | |
return Money(amount: amount * multiplier, currencyString: currencyString) | |
} | |
func plus(_ addend: Expression) -> Expression { | |
return Sum(augend: self, addend: addend) | |
} | |
func reduce(_ bank: Bank, to: String) -> Money { | |
let rate = bank.rate(currency(), to: to) | |
return Money(amount: amount / rate, currencyString: to) | |
} | |
} | |
func ==(lhs: Money, rhs: Money) -> Bool { | |
if lhs.amount != rhs.amount { | |
return false | |
} | |
if lhs.currencyString != rhs.currencyString { | |
return false | |
} | |
return true | |
} | |
struct Pair: Equatable, Hashable { | |
fileprivate let from: String | |
fileprivate let to: String | |
init(from: String, to: String) { | |
self.from = from | |
self.to = to | |
} | |
var hashValue: Int = 0 | |
} | |
func ==(lhs: Pair, rhs: Pair) -> Bool { | |
if lhs.from != rhs.from { | |
return false | |
} | |
if lhs.to != rhs.to { | |
return false | |
} | |
return true | |
} | |
class Bank { | |
var rates = HashTable<Pair, Int>(capacity: 10) | |
func reduce(_ source: Expression, to: String) -> Money { | |
return source.reduce(self, to: to) | |
} | |
func rate(_ from: String, to: String) -> Int { | |
if from == to { return 1 } | |
let rate: Int = rates.valueForKey(Pair(from: from, to: to))! | |
return rate | |
} | |
func addRate(_ from: String, to: String, rate: Int) { | |
rates[Pair(from: from, to: to)] = rate | |
} | |
} | |
struct Sum: Expression { | |
let augend: Expression | |
let addend: Expression | |
func reduce(_ bank: Bank, to: String) -> Money { | |
let amount = augend.reduce(bank, to: to).amount + addend.reduce(bank, to: to).amount | |
return Money(amount: amount, currencyString: to) | |
} | |
func plus(_ addend: Expression) -> Expression { | |
return Sum(augend: self, addend: addend) | |
} | |
func times(_ multiplier: Int) -> Expression { | |
return Sum(augend: addend.times(multiplier), addend: addend.times(multiplier)) | |
} | |
} | |
This file contains hidden or 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
import XCTest | |
@testable import TDDByExample | |
class MoneyTests: XCTestCase { | |
override func setUp() { | |
super.setUp() | |
} | |
override func tearDown() { | |
super.tearDown() | |
} | |
func test_Multiplication() { | |
let five: Money = Money.dollar(5) | |
let ten = five.times(2) as! Money | |
let fifteen = five.times(3) as! Money | |
XCTAssertEqual(Money.dollar(10), ten) | |
XCTAssertEqual(Money.dollar(15), fifteen) | |
} | |
func test_Currency() { | |
XCTAssertEqual("USD", Money.dollar(1).currency()) | |
XCTAssertEqual("CHF", Money.frenc(1).currency()) | |
} | |
func test_Equality() { | |
XCTAssertTrue((Money.dollar(5) == Money.dollar(5))) | |
XCTAssertFalse(Money.dollar(5) == Money.dollar(6)) | |
XCTAssertFalse(Money.dollar(5) == Money.frenc(5)) | |
} | |
func test_SimpleAddition() { | |
let five = Money.dollar(5) | |
let sum: Expression = five.plus(five) | |
let bank = Bank() | |
let reduced = bank.reduce(sum, to: "USD") | |
XCTAssertEqual(Money.dollar(10), reduced) | |
} | |
func test_PlusReturnsSum() { | |
let five = Money.dollar(5) | |
let result = five.plus(five) | |
let sum: Sum = result as! Sum | |
let augend = sum.augend as! Money | |
let addend = sum.addend as! Money | |
XCTAssertEqual(five, augend) | |
XCTAssertEqual(five, addend) | |
} | |
func test_ReduceSum() { | |
let sum = Sum(augend: Money.dollar(3), addend: Money.dollar(4)) | |
let bank = Bank() | |
let result = bank.reduce(sum, to: "USD") | |
XCTAssertEqual(Money.dollar(7), result) | |
} | |
func test_ReduceMoney() { | |
let bank = Bank() | |
let result = bank.reduce(Money.dollar(1), to: "USD") | |
XCTAssertEqual(Money.dollar(1), result) | |
} | |
func test_ReduceMoneyDifferentCurrency() { | |
let bank = Bank() | |
bank.addRate("CHF", to: "USD", rate: 2) | |
let result = bank.reduce(Money.frenc(2), to: "USD") | |
XCTAssertEqual(Money.dollar(1), result) | |
} | |
func test_IdentityRate() { | |
XCTAssertEqual(1, Bank().rate("USD", to: "USD")) | |
} | |
func test_MixedAddtion() { | |
let fiveBucks: Expression = Money.dollar(5) | |
let tenFrances: Expression = Money.frenc(10) | |
let bank = Bank() | |
bank.addRate("CHF", to: "USD", rate: 2) | |
let result: Money = bank.reduce(fiveBucks.plus(tenFrances), to: "USD") | |
XCTAssertEqual(Money.dollar(10), result) | |
} | |
func test_SumPlusMoney() { | |
let fiveBucks: Expression = Money.dollar(5) | |
let tenFrances: Expression = Money.frenc(10) | |
let bank = Bank() | |
bank.addRate("CHF", to: "USD", rate: 2) | |
let sum: Expression = Sum(augend: fiveBucks, addend: tenFrances).plus(fiveBucks) | |
let result: Money = bank.reduce(sum, to: "USD") | |
XCTAssertEqual(Money.dollar(15), result) | |
} | |
func test_SumTimes() { | |
let fiveBucks: Expression = Money.dollar(5) | |
let tenFrances: Expression = Money.frenc(10) | |
let bank = Bank() | |
bank.addRate("CHF", to: "USD", rate: 2) | |
let sum: Expression = Sum(augend: fiveBucks, addend: tenFrances).times(2) | |
let result: Money = bank.reduce(sum, to: "USD") | |
XCTAssertEqual(Money.dollar(20), result) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment