# 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:

```var description: String {
get {
if pending == nil {
return descriptionAccumulator
} else {
return ...
}
}
}
private var descriptionAccumulator = "0"
```

Setting a new operator should also set the description accumulator. The format “%g” makes sure to trim trailing zeros for integer values:

```func setOperand(operand: Double) {
...
descriptionAccumulator = String(format:"%g", operand)
}
```

For constants just use its symbol for the description accumulator:

```func performOperation(symbol: String) {
...
case .Constant(let value):
...
descriptionAccumulator = symbol
...
}
```

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:

```private enum Operation {
...
case UnaryOperation((Double) -> Double, (String) -> String)
...
}
```

Now each unary operation can provide a suitable formatted description function:

```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 + ")!"}),
...
]
```

… and use this methods to set the description accumulator:

```func performOperation(symbol: String) {
...
case .UnaryOperation(let function, let descriptionFunction):
...
descriptionAccumulator = descriptionFunction(descriptionAccumulator)
...
}
```

Same with binary operation – only difference – we need two operators:

```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 }),
...
]
```

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:

```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)
...
}
```

Because the description should also show pending operation, adjust the description getter to do so:

```var description: String {
...
} else {
return pending!.descriptionFunction(pending!.descriptionOperand,
pending!.descriptionOperand != descriptionAccumulator ? descriptionAccumulator : "")
}
}
}
```

To get a correct descriptions we need to add the operator precedence – due to laziness only for binary operations:

```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),
...
]
```

A helper variable holds the precedence of the previous operation:

`private var currentPrecedence = Int.max`

When the precedence of a new operation is higher than the previous one, add brackets:

```func performOperation(symbol: String) {
...
case .BinaryOperation(let function, let descriptionFunction, let precedence):
...
if currentPrecedence < precedence {
descriptionAccumulator = "(" + descriptionAccumulator + ")"
}
currentPrecedence = precedence
...
}
```

Every time the description gets set and the there is no pending operation, reset the current precedence:

```private var descriptionAccumulator = "0" {
didSet {
if pending == nil {
currentPrecedence = Int.max
}
}
}
```

… and because we have now output of the description yet, test it using unit tests:

```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)
}
```

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

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

1. Ludovic Loridan says:

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. R. Carr says:

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. Avery says:

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.

1. Avery says:

Aha I have figured out why 🙂

2. Honestly, I have no idea (which happens with undocumented code after some time …)! If your version works, perfect!

4. Merl says:

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.