cs193p – Assignment #1 Task #5

Please note, this blog entry is from a previous course. You might want to check out the current one.

Add a String property to your CalculatorBrain called description which returns a description of the sequence of operands and operations that led to the value returned by result. “=“ should never appear in this description, nor should “…”.

Let’s add a public string property called description, and compute it – like the result property – from another new private string property – the description accumulator. When no operation is pending, we can just return the accumulator otherwise we will have to do a little bit more:
[swift]
var description: String {
get {
if pending == nil {
return descriptionAccumulator
} else {
return …
}
}
}
private var descriptionAccumulator = "0"
[/swift]

Setting a new operator should also set the description accumulator. The format “%g” makes sure to trim trailing zeros for integer values:
[swift]
func setOperand(operand: Double) {

descriptionAccumulator = String(format:"%g", operand)
}
[/swift]

For constants just use its symbol for the description accumulator:
[swift]
func performOperation(symbol: String) {

case .Constant(let value):

descriptionAccumulator = symbol

}
[/swift]

Unary operations might need a different description depending on the kind of operation, thus we expand its declaration by a new function using a string (the current operand) as parameter and returning a string with the description:
[swift]
private enum Operation {

case UnaryOperation((Double) -> Double, (String) -> String)

}
[/swift]

Now each unary operation can provide a suitable formatted description function:
[swift]
private var operations: Dictionary<String,Operation> = [

"±" : Operation.UnaryOperation({ -$0 }, { "-(" + $0 + ")"}),
"√" : Operation.UnaryOperation(sqrt, { "√(" + $0 + ")"}),
"x²" : Operation.UnaryOperation({ pow($0, 2) }, { "(" + $0 + ")²"}),
"x³" : Operation.UnaryOperation({ pow($0, 3) }, { "(" + $0 + ")³"}),
"x⁻¹" : Operation.UnaryOperation({ 1 / $0 }, { "(" + $0 + ")⁻¹"}),
"sin" : Operation.UnaryOperation(sin, { "sin(" + $0 + ")"}),
"cos" : Operation.UnaryOperation(cos, { "cos(" + $0 + ")"}),
"tan" : Operation.UnaryOperation(tan, { "tan(" + $0 + ")"}),
"sinh" : Operation.UnaryOperation(sinh, { "sinh(" + $0 + ")"}),
"cosh" : Operation.UnaryOperation(cosh, { "cosh(" + $0 + ")"}),
"tanh" : Operation.UnaryOperation(tanh, { "tanh(" + $0 + ")"}),
"ln" : Operation.UnaryOperation(log, { "ln(" + $0 + ")"}),
"log" : Operation.UnaryOperation(log10, { "log(" + $0 + ")"}),
"eˣ" : Operation.UnaryOperation(exp, { "e^(" + $0 + ")"}),
"10ˣ" : Operation.UnaryOperation({ pow(10, $0) }, { "10^(" + $0 + ")"}),
"x!" : Operation.UnaryOperation(factorial, { "(" + $0 + ")!"}),

]
[/swift]

… and use this methods to set the description accumulator:
[swift]
func performOperation(symbol: String) {

case .UnaryOperation(let function, let descriptionFunction):

descriptionAccumulator = descriptionFunction(descriptionAccumulator)

}
[/swift]

Same with binary operation – only difference – we need two operators:
[swift]
private enum Operation {

case BinaryOperation((Double, Double) -> Double, (String, String) -> String)

}

private var operations: Dictionary<String,Operation> = [

"×" : Operation.BinaryOperation(*, { $0 + " × " + $1 }),
"÷" : Operation.BinaryOperation(/, { $0 + " ÷ " + $1 }),
"+" : Operation.BinaryOperation(+, { $0 + " + " + $1 }),
"-" : Operation.BinaryOperation(-, { $0 + " – " + $1 }),
"xʸ" : Operation.BinaryOperation(pow, { $0 + " ^ " + $1 }),

]
[/swift]

In addition store the description information (the function and the current operand) in the pending-information structure, to use it when both operands are known:
[swift]
private struct PendingBinaryOperationInfo {

var descriptionFunction: (String, String) -> String
var descriptionOperand: String
}

func performOperation(symbol: String) {

case .BinaryOperation(let function, let descriptionFunction):

pending = PendingBinaryOperationInfo(binaryFunction: function, firstOperand: accumulator,
descriptionFunction: descriptionFunction, descriptionOperand: descriptionAccumulator)

}

private func executePendingBinaryOperation() {

descriptionAccumulator = pending!.descriptionFunction(pending!.descriptionOperand, descriptionAccumulator)

}
[/swift]

Because the description should also show pending operation, adjust the description getter to do so:
[swift]
var description: String {

} else {
return pending!.descriptionFunction(pending!.descriptionOperand,
pending!.descriptionOperand != descriptionAccumulator ? descriptionAccumulator : "")
}
}
}
[/swift]

To get a correct descriptions we need to add the operator precedence – due to laziness only for binary operations:
[swift]
private enum Operation {

case BinaryOperation((Double, Double) -> Double, (String, String) -> String, Int)

}

private var operations: Dictionary<String,Operation> = [

"×" : Operation.BinaryOperation(*, { $0 + " × " + $1 }, 1),
"÷" : Operation.BinaryOperation(/, { $0 + " ÷ " + $1 }, 1),
"+" : Operation.BinaryOperation(+, { $0 + " + " + $1 }, 0),
"-" : Operation.BinaryOperation(-, { $0 + " – " + $1 }, 0),
"xʸ" : Operation.BinaryOperation(pow, { $0 + " ^ " + $1 }, 2),

]
[/swift]

A helper variable holds the precedence of the previous operation:
[swift]private var currentPrecedence = Int.max[/swift]

When the precedence of a new operation is higher than the previous one, add brackets:
[swift]
func performOperation(symbol: String) {

case .BinaryOperation(let function, let descriptionFunction, let precedence):

if currentPrecedence < precedence {
descriptionAccumulator = "(" + descriptionAccumulator + ")"
}
currentPrecedence = precedence

}
[/swift]

Every time the description gets set and the there is no pending operation, reset the current precedence:
[swift]
private var descriptionAccumulator = "0" {
didSet {
if pending == nil {
currentPrecedence = Int.max
}
}
}
[/swift]

… and because we have now output of the description yet, test it using unit tests:
[swift]
func testDescription() {
// a. touching 7 + would show “7 + …” (with 7 still in the display)
let brain = CalculatorBrain()
brain.setOperand(7)
brain.performOperation("+")
XCTAssertEqual(brain.description, "7 + ")
XCTAssertEqual(brain.result, 7.0)

// b. 7 + 9 would show “7 + …” (9 in the display)
//brain.setOperand(9) // entered but not pushed to model
XCTAssertEqual(brain.description, "7 + ")
XCTAssertEqual(brain.result, 7.0)

// c. 7 + 9 = would show “7 + 9 =” (16 in the display)
brain.setOperand(9)
brain.performOperation("=")
XCTAssertEqual(brain.description, "7 + 9")
XCTAssertEqual(brain.result, 16.0)

// d. 7 + 9 = √ would show “√(7 + 9) =” (4 in the display)
brain.performOperation("√")
XCTAssertEqual(brain.description, "√(7 + 9)")
XCTAssertEqual(brain.result, 4.0)

// e. 7 + 9 √ would show “7 + √(9) …” (3 in the display)
brain.setOperand(7)
brain.performOperation("+")
brain.setOperand(9)
brain.performOperation("√")
XCTAssertEqual(brain.description, "7 + √(9)")
XCTAssertEqual(brain.result, 3.0)

// f. 7 + 9 √ = would show “7 + √(9) =“ (10 in the display)
brain.performOperation("=")
XCTAssertEqual(brain.description, "7 + √(9)")
XCTAssertEqual(brain.result, 10.0)

// g. 7 + 9 = + 6 + 3 = would show “7 + 9 + 6 + 3 =” (25 in the display)
brain.setOperand(7)
brain.performOperation("+")
brain.setOperand(9)
brain.performOperation("=")
brain.performOperation("+")
brain.setOperand(6)
brain.performOperation("+")
brain.setOperand(3)
brain.performOperation("=")
XCTAssertEqual(brain.description, "7 + 9 + 6 + 3")
XCTAssertEqual(brain.result, 25.0)

// h. 7 + 9 = √ 6 + 3 = would show “6 + 3 =” (9 in the display)
brain.setOperand(7)
brain.performOperation("+")
brain.setOperand(9)
brain.performOperation("=")
brain.performOperation("√")
brain.setOperand(6)
brain.performOperation("+")
brain.setOperand(3)
brain.performOperation("=")
XCTAssertEqual(brain.description, "6 + 3")
XCTAssertEqual(brain.result, 9.0)

// i. 5 + 6 = 7 3 would show “5 + 6 =” (73 in the display)
brain.setOperand(5)
brain.performOperation("+")
brain.setOperand(6)
brain.performOperation("=")
//brain.setOperand(73) // entered but not pushed to model
XCTAssertEqual(brain.description, "5 + 6")
XCTAssertEqual(brain.result, 11.0)

// j. 7 + = would show “7 + 7 =” (14 in the display)
brain.setOperand(7)
brain.performOperation("+")
brain.performOperation("=")
XCTAssertEqual(brain.description, "7 + 7")
XCTAssertEqual(brain.result, 14.0)

// k. 4 × π = would show “4 × π =“ (12.5663706143592 in the display)
brain.setOperand(4)
brain.performOperation("×")
brain.performOperation("π")
brain.performOperation("=")
XCTAssertEqual(brain.description, "4 × π")
XCTAssertTrue(abs(brain.result – 12.5663706143592) < 0.001)

// m. 4 + 5 × 3 = could also show “(4 + 5) × 3 =” if you prefer (27 in the display)
brain.setOperand(4)
brain.performOperation("+")
brain.setOperand(5)
brain.performOperation("×")
brain.setOperand(3)
brain.performOperation("=")
XCTAssertEqual(brain.description, "(4 + 5) × 3")
XCTAssertEqual(brain.result, 27.0)
}
[/swift]

The complete code for task #5 is available on GitHub.

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

7 thoughts on “cs193p – Assignment #1 Task #5”

  1. Thank you so much for this, I was stuck !
    It’s actually brilliant, I wonder why I didn’t think of mimicking the accumulator’s behaviour for description…

  2. Thanks for this. I just wanted to confirm I wasn’t crazy as this task was about 20x more work than all the other tasks combined. I feel like there should have been some hint that this task is essentially the entire assignment. Thanks for the guidance!

  3. Hi, thank you so much for the walkthrough! I have a question about the description variable.

    Why did you return pending!.descriptionFunction(pending!.descriptionOperand, pending!.descriptionOperand != descriptionAccumulator ? descriptionAccumulator : “”), instead of just pending!.descriptionFunction(pending!.descriptionOperand, “”)? My thoughts is that if pending is not nil, the second operand for the binary function will always be empty.

  4. Thank you very much, great solution!
    However I believe, we need to add this line of code

    pending!.descriptionOperand = descriptionAccumulator

    inside of this condition

    if currentPrecedence < precedence {
    descriptionAccumulator = "(" + descriptionAccumulator + ")"
    // should be inserted here
    }

    so, then everything should be fine and all conditions will be met.

Leave a Reply

Your email address will not be published. Required fields are marked *