cs193p – Project #2 Assignment #2 Task #7

von Cal.on (Eigenes Werk) [GFDL (http://www.gnu.org/copyleft/fdl.html) oder CC BY-SA 4.0-3.0-2.5-2.0-1.0 (http://creativecommons.org/licenses/by-sa/4.0-3.0-2.5-2.0-1.0)], via Wikimedia Commons

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

Implement a new read-only (get only, no set) var to CalculatorBrain to describe the contents of the brain as a String …

var description: String
  1. Unary operations should be shown using “function” notation. For example, the input 10 cos would display in the description as cos(10).
  2. Binary operations should be shown using “infix” notation. For example, the input 3↲5 – should display as 3-5. Be sure to get the order correct!
  3. All other stack contents (e.g. operands, variables, constants like π, etc.) should be displayed unadorned. For example, 23.5 ⇒ 23.5, π ⇒ π (not 3.1415!), the variable x ⇒ x (not its value!), etc.
  4. Any combination of stack elements should be properly displayed. Examples:
    10√3+ ⇒ √(10)+3
    3↲5+√ ⇒ √(3+5)
    3↲5↲4++ ⇒ 3+(5+4) or (for Extra Credit) 3+5+4
    3↲5√+√6÷ ⇒ √(3+√(5))÷6
  5. If there are any missing operands, substitute a ? for them, e.g. 3↲+ ⇒ ?+3.
  6. If there are multiple complete expressions on the stack, separate them by commas: for example, 3↲5+√πcos ⇒ √(3+5),cos(π). The expressions should be in historical order with the oldest at the beginning of the string and the most recently pushed/performed at the end.
  7. Your description must properly convey the mathematical expression. For example, 3↲5↲4+* must not output 3*5+4 — it must be 3*(5+4). In other words, you will need to sometimes add parentheses around binary operations. Having said that, try to minimize parentheses as much as you can (as long as the output is mathematically correct). See Extra Credit if you want to really do this well.


While there is a stack left which has not been described yet, call a helper method which will try to decipher the remaining stack. If there are multiple independent parts of the stack separate their descriptions using a comma:

    var description: String {
        get {
            var (result, ops) = ("", opStack)
            do {
                var current: String?
                (current, ops) = description(ops)
                result = result == "" ? current! : "\(current!), \(result)"
            } while ops.count > 0
            return result
        }
    }

The helper method starts with the last element of the stack and works it recursively. For operands we return the formated value (“%g” removes trailing zeros). For nullary operations – in our case π – return the symbol. For unary operations return the symbol with the operands in brackets. For binary operations return the operands separated by the symbol. In addition add brackets to the first operand if its remaining stack consists of only one or two elements. For a variable return its symbol. If the stack does not compute, return a question mark.

    private func description(ops: [Op]) -> (result: String?, remainingOps: [Op]) {
        if !ops.isEmpty {
            var remainingOps = ops
            let op = remainingOps.removeLast()
            switch op {
            case .Operand(let operand):
                return (String(format: "%g", operand) , remainingOps)
            case .NullaryOperation(let symbol, _):
                return (symbol, remainingOps);
            case .UnaryOperation(let symbol, _):
                let operandEvaluation = description(remainingOps)
                if let operand = operandEvaluation.result {
                    return ("\(symbol)(\(operand))", operandEvaluation.remainingOps)
                }
            case .BinaryOperation(let symbol, _):
                let op1Evaluation = description(remainingOps)
                if var operand1 = op1Evaluation.result {
                    if remainingOps.count - op1Evaluation.remainingOps.count > 2 {
                        operand1 = "(\(operand1))"
                    }
                    let op2Evaluation = description(op1Evaluation.remainingOps)
                    if let operand2 = op2Evaluation.result {
                        return ("\(operand2) \(symbol) \(operand1)", op2Evaluation.remainingOps)
                    }
                }
            case .Variable(let symbol):
                return (symbol, remainingOps)
            }
        }
        return ("?", ops)
    }

To test the new method create some test cases:

    func testDescription() {
        // cos(10)
        var brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(10)!, 10)
        XCTAssertTrue(brain.performOperation("cos")! - -0.839 < 0.1)
        XCTAssertEqual(brain.description, "cos(10)")
        
        // 3 - 5
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(3)!, 3)
        XCTAssertEqual(brain.pushOperand(5)!, 5)
        XCTAssertEqual(brain.performOperation("−")!, -2)
        XCTAssertEqual(brain.description, "3 − 5")

        // 23.5
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(23.5)!, 23.5)
        XCTAssertEqual(brain.description, "23.5")

        // π
        brain = CalculatorBrain()
        XCTAssertEqual(brain.performOperation("π")!, M_PI)
        XCTAssertEqual(brain.description, "π")

        // x
        brain = CalculatorBrain()
        XCTAssertNil(brain.pushOperand("x"))
        XCTAssertEqual(brain.description, "x")
        
        // √(10) + 3
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(10)!, 10)
        XCTAssertTrue(brain.performOperation("√")! - 3.162 < 0.1)
        XCTAssertEqual(brain.pushOperand(3)!, 3)
        XCTAssertTrue(brain.performOperation("+")! - 6.162 < 0.1)
        XCTAssertEqual(brain.description, "√(10) + 3")
        
        // √(3 + 5)
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(3)!, 3)
        XCTAssertEqual(brain.pushOperand(5)!, 5)
        XCTAssertEqual(brain.performOperation("+")!, 8)
        XCTAssertTrue(brain.performOperation("√")! - 2.828 < 0.1)
        XCTAssertEqual(brain.description, "√(3 + 5)")
        
        // 3 + (5 + 4)
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(3)!, 3)
        XCTAssertEqual(brain.pushOperand(5)!, 5)
        XCTAssertEqual(brain.pushOperand(4)!, 4)
        XCTAssertEqual(brain.performOperation("+")!, 9)
        XCTAssertEqual(brain.performOperation("+")!, 12)
        XCTAssertEqual(brain.description, "3 + (5 + 4)")
        
        // √(3 + √(5)) ÷ 6
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(3)!, 3)
        XCTAssertEqual(brain.pushOperand(5)!, 5)
        XCTAssertTrue(brain.performOperation("√")! - 2.236 < 0.1)
        XCTAssertTrue(brain.performOperation("+")! - 5.236 < 0.1)
        XCTAssertTrue(brain.performOperation("√")! - 2.288 < 0.1)
        XCTAssertEqual(brain.pushOperand(6)!, 6)
        XCTAssertTrue(brain.performOperation("÷")! - 0.381 < 0.1)
        XCTAssertEqual(brain.description, "√(3 + √(5)) ÷ 6")
        
        // ? + 3
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(3)!, 3)
        XCTAssertNil(brain.performOperation("+"))
        XCTAssertEqual(brain.description, "? + 3")
        
        // √(3 + 5), cos(π)
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(3)!, 3)
        XCTAssertEqual(brain.pushOperand(5)!, 5)
        XCTAssertEqual(brain.performOperation("+")!, 8)
        XCTAssertTrue(brain.performOperation("√")! - 2.828 < 0.1)
        XCTAssertEqual(brain.performOperation("π")!, M_PI)
        XCTAssertEqual(brain.performOperation("cos")!, -1)
        XCTAssertEqual(brain.description, "√(3 + 5), cos(π)")
        
        // 3 * (5 + 4)
        brain = CalculatorBrain()
        XCTAssertEqual(brain.pushOperand(3)!, 3)
        XCTAssertEqual(brain.pushOperand(5)!, 5)
        XCTAssertEqual(brain.pushOperand(4)!, 4)
        XCTAssertEqual(brain.performOperation("+")!, 9)
        XCTAssertEqual(brain.performOperation("×")!, 27)
        XCTAssertEqual(brain.description, "3 × (5 + 4)")
    }

The complete code for the task #7 is available on GitHub.

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

8 thoughts on “cs193p – Project #2 Assignment #2 Task #7”

  1. Hi
    Thank you for all your work
    Amazing

    I would like to learn and be so good like you in closures…
    do{
    var current: String?
    (current, ops) = description(ops)
    result = result == “” ? current! : “\(current!), \(result)”
    }while ops.count > 0

    Wow
    How to do that?

    Regarding task..
    I prefer your previous code in that there are to many parenthesis for me…
    If there is an issue in changes in enum…
    I tried something myself – could you please look on it?
    What is your opinion?

    class CalculatorBrain {
    …..

    var variableValues = [String: Double]()
    …..
    func pushDigit(operand: Double)->Double?{
    opStack.append(Op.Operator(operand))
    digitArray.append(operand)
    ……
    private func description(ops:[Op])->(result: String?, remainingOps: [Op]){
    ……..
    case .BinaryOperation(let symbol, _):
    ……
    if (symbol == “+” || symbol == “−”) &&
    digitArray.count > 2{
    return (“(\(op2)\(symbol)\(op1))”, op2Description.remainingOps)
    }else{
    return (“\(op2)\(symbol)\(op1)”, op2Description.remainingOps)
    }

    Thank you

  2. It is not working….

    I tried to add

    func performOperation(symbol: String)->Double?{
    if symbol == “×” || symbol == “÷” || symbol == “+” || symbol == “−” && digitArray.count > 0{
    digitArray.removeLast()
    println(digitArray)
    }

    and to change
    if (symbol == “+” || symbol == “−”) &&
    digitArray.count > 2{
    to
    if (symbol == “+” || symbol == “−”) &&
    digitArray.count > 1{

    but it is eating away all parenthesis….

    How to do that…
    Thanks

  3. Nothing is working
    I try many thing…
    I don’t understand why you change your solution?
    Your previous code was perfect that is so bad.
    Why have you changed it?
    Are there any limitation in the task conditions?
    Why?

    Thank you

    1. Sorry, I do not understand which previous code are you referring to? From task #6 to task #7 I did not change any code only added some?

      Sorry, but I wrote that code three months ago, if you are referring to “previous” code it’s quite difficult for me to know what exactly you mean.

      Could you please upload your complete code to e.g. github … code in the comments is garbled quite often …

  4. private enum Op: Printable{
    case Operand(Double)
    case UnaryOperation(String, Double ->Double)
    case BinaryOperation(String, Int, (Double, Double) ->Double)

    var description: String{
    get {
    switch self{
    case .Operand(let operand):
    return “\(operand)”
    case .UnaryOperation(let symbol, _):
    return symbol
    case .BinaryOperation(let symbol,_,_):
    return symbol
    }
    }
    }
    var precedence: Int {
    get {
    switch self {
    case .BinaryOperation(_, let precedence, _):
    return precedence
    default:
    return Int.max
    }
    }
    }
    }

    init(){
    func learnOps(op: Op){
    knownOps[op.description] = op
    }
    knownOps[“×”] = Op.BinaryOperation(“×”,2, *)
    knownOps[“÷”] = Op.BinaryOperation(“÷”,2) {$1 / $0}
    knownOps[“+”] = Op.BinaryOperation(“+”,1, +)
    knownOps[“−”] = Op.BinaryOperation(“−”,1) {$1 – $0}
    knownOps[“√”] = Op.UnaryOperation(“√”, sqrt)
    learnOps(Op.UnaryOperation(“±”){-$0})
    }
    ….
    var description: String?{
    get{
    var (result, ops) = (“”, opStack)
    do{
    var current: String?
    (current, ops, _ ) = description(ops)
    result = result == “” ? current! : “\(current!), \(result)”
    }while ops.count > 0
    return result
    }
    }

    private func description(ops:[Op])->(result: String?, remainingOps: [Op], precedence: Int?){
    if !ops.isEmpty{
    var remainingOps = ops
    var op = remainingOps.removeLast()
    switch op{
    case .Operator(let operand):
    return (String(format: “%g”, operand), remainingOps, op.precedence)
    case .Variable(let symbol):
    return (symbol, remainingOps, op.precedence)
    case .NullaryOperation(let symbol, _):
    return (symbol, remainingOps, op.precedence)
    case .UnaryOperation(let symbol, _):
    let opDescription = description(remainingOps)
    if var op0 = opDescription.result{
    if op.precedence > opDescription.precedence {
    op0 = “(\(op0))”
    }
    return (“\(symbol)\(op0)”, opDescription.remainingOps, op.precedence)
    }
    case .BinaryOperation(let symbol, _,_):
    let op1Description = description(remainingOps)
    if var op1 = op1Description.result {
    if op.precedence > op1Description.precedence{
    op1 = “(\(op1))”
    }
    let op2Description = description(op1Description.remainingOps)
    if var op2 = op2Description.result{
    if op.precedence > op2Description.precedence{
    op2 = “(\(op2))”
    }
    return (“\(op2)\(symbol)\(op1)”, op2Description.remainingOps, op.precedence)
    }
    }
    }
    }
    return (“?”, ops, Int.max)
    }

Leave a Reply

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