cs193p – Assignment #2 Extra Task #1

Have your calculator report errors. For example, the square root of a negative number or divide by zero. There are a number of ways to go about “detecting” these errors (maybe add an associated value to the unary/binaryOperation cases which is a function that detects an error or perhaps have the function that is associated with a unary/binaryOperation return something that is either an error or a result or ???). How you report any discovered errors back to users of the CalculatorBrain API will require some API design on your part, but don’t force users of the CalculatorBrain API to deal with errors if they don’t want to (i.e. allow Controllers that want to display errors to do so, but let those that don’t just deal with NaN and +∞ appearing in their UI). In other words, don’t break any callers of the API described above (who don’t care about errors) to support this feature (i.e., add methods/ properties as needed instead). You are allowed to violate Required Task 11 to implement this Extra Credit item, but not Required Task 1 (you can enhance that data structure, but not switch to a new one).

Starting with the calculator brain, we could adjust the existing operations to include the error handling. Because most operations do not need error handling, create a new structure to hold the error functions:

    private enum ErrorOperation {
        case unaryOperation((Double) -> String?)
        case binaryOperation((Double, Double) -> String?)
    }

    private let errorOperations: Dictionary<String,ErrorOperation> = [
        "√": ErrorOperation.unaryOperation({ 0.0 > $0 ? "SQRT of negative Number" : nil }),
        "÷": ErrorOperation.binaryOperation({ 1e-8 >= fabs($0.1) ? "Division by Zero" : nil }),
        "x⁻¹" : ErrorOperation.unaryOperation({ 1e-8 > fabs($0) ? "Division by Zero" : nil }),
        "ln" : ErrorOperation.unaryOperation({ 0 > $0 ? "LN of negative Number" : nil }),
        "log" : ErrorOperation.unaryOperation({ 0 > $0 ? "LOG of negative Number" : nil }),
        "x!" : ErrorOperation.unaryOperation({ 0 > $0 ? "Factorial of negative Number" : nil })
    ]

We will use those to set and return an error message string in the evaluate method:

    func evaluate(using variables: Dictionary<String,Double>? = nil)
        -> (result: Double?, isPending: Bool, description: String, error: String?)
    {
        var error: String?
        ...
        return (result, nil != pendingBinaryOperation, description ?? "", error)
    }
}

For operations with a single operand, we call the error message directly – if it exisits:

                    case .unaryOperation(let function, let description):
                        if nil != accumulator {
                            if let errorOperation = errorOperations[symbol],
                            case .unaryOperation(let errorFunction) = errorOperation {
                                error = errorFunction(accumulator!.0)
                            }
                            ...
                        }

For binary operations we have to wait till we get the second operand – thus we handle errors when we perform the pending binary operation. … and need to store the symbol of the current operation:

        struct PendingBinaryOperation {
            let symbol: String
            ...
        }
        
        func performPendingBinaryOperation() {
            if nil != pendingBinaryOperation && nil != accumulator {
                if let errorOperation = errorOperations[pendingBinaryOperation!.symbol],
                    case .binaryOperation(let errorFunction) = errorOperation {
                    error = errorFunction(pendingBinaryOperation!.firstOperand.0, accumulator!.0)
                }
                ...
            }
        }

… and provide the symbol during the evaluation:

                    case .binaryOperation(let function, let description):
                        ...
                            pendingBinaryOperation = PendingBinaryOperation(symbol: symbol, function: function, description: description, firstOperand: accumulator!)
                            ...

This way the view controller still works without changing anything.

We want to know the errors, therefore display them when they occur:

    private func displayResult() {
        ...        
        if let error = evaluated.error {
            display.text = error
        } else if let result = evaluated.result {
            displayValue = result
        }
        ...
    }

The complete code for the assignment #2 extra task #1 is available on GitHub.

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

cs193p – Assignment #2 Task #10

Add an Undo button to your Calculator. In Assignment 1’s Extra Credit, you might have added a “backspace” button. Here we’re talking about combining both backspace and actual undo into a single button. If the user is in the middle of entering a number, this Undo button should be backspace. When the user is not in the middle of entering a number, it should undo the last thing that was done in the CalculatorBrain. Do not undo the storing of M’s value (but DO undo the setting of a variable as an operand).

Rename the backspace button in the storyboard:
cs193p-assignment-2-task-10-winter-2017-storyboard
Continue reading “cs193p – Assignment #2 Task #10”

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

cs193p – Assignment #2 Task #9

Make sure your C button from Assignment 1 works properly in this assignment. In addition, it should discard the Dictionary it was using for the M variable (it should not set M to zero or any other value, just stop using that Dictionary until →M is pressed again). This will allow you to test the case of an “unset” variable.

… sorry, done that already …

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

cs193p – Assignment #2 Task #8

Show the value of M (if set) in your UI somewhere.

Start by adding a new label to your storyboard. I put it into a new horizontal stack with the description display and increased the content hugging of the new label so that it only takes the necessary space:
cs193p-assignment-2-task-8-winter-2017-storyboard

Create an outlet for the new label in the view controller:

@IBOutlet weak var memoryDisplay: UILabel!

Continue reading “cs193p – Assignment #2 Task #8”

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

cs193p – Assignment #2 Task #7

Add two new buttons to your Calculator’s UI: →M and M. Don’t sacrifice any of the required operation buttons from Assignment 1 to add these (though you may add yet more operations buttons if you want). These two buttons will set and get (respectively) a variable in the CalculatorBrain called M.
a. →M calls evaluate in your Model with a Dictionary which has a single entry whose key is M and whose value is the current value of the display, and then updates the display to show the result that comes back from evaluate. Until this button (or the clear button) is pressed again, this same Dictionary should be used every time evaluate is called.
b. →M does not perform setOperand.
c. Touching M should setOperand(variable: “M”) in the brain and then show the
result of calling evaluate in the display.
d. →M and M are Controller mechanics, not Model mechanics (though they both use
the Model mechanic of variables).
e. This is not a very great “memory” button on our Calculator, but it can be used for testing whether our variable function implemented in our Model is working properly. Examples …
9 + M = √ ⇒ description is √(9+M), display is 3 because M is not set (thus 0.0).
7 →M ⇒ display now shows 4 (the square root of 16), description is still √(9+M) + 14 = ⇒ display now shows 18, description is now √(9+M)+14

Continue reading “cs193p – Assignment #2 Task #7”

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

cs193p – Assignment #2 Task #5

We made the result, description and resultIsPending vars non-private API in Assignment 1. That means we signed up to continue to support them even though we are now adding a new feature (variables) in this assignment which sort of makes them irrelevant. Really what we want to do is deprecate these (you’ll see all sorts of deprecated iOS API in Xcode), but for now we will keep the old result, description and resultIsPending vars around and just implement each of them by calling evaluate with the argument nil (i.e. they will give their answer assuming the value of any variables is zero). However, do not use any of these vars anywhere in your code in this assignment. Use evaluate instead.

We use already the evaluate method for those three properties. Just place something like the following above each of them:

@available(*, deprecated, message: "no longer needed ...")

… and don’t use them any more in the view controller:

    @IBAction func performOperation(_ sender: UIButton) {
        ...
        let evaluated = brain.evaluate()        
        if let result = evaluated.result {
            displayValue = result
        }        
        if "" != evaluated.description {
            descriptionDisplay.text = evaluated.description.beautifyNumbers() + (evaluated.isPending ? "…" : "=")
        } else {
            descriptionDisplay.text = " "
        }
    }

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

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!