How to execute multiplications and/or divisions in the right order?

后端 未结 5 1707
伪装坚强ぢ
伪装坚强ぢ 2020-12-21 21:11

I am doing a simple calculator, but when performing the multiplication and division, my code doesn\'t make them a priority over plus and minus. When doing -> 2 + 2 * 4, res

5条回答
  •  不思量自难忘°
    2020-12-21 21:32

    Assuming you want a generalised and perhaps extensible algorithm for any arithmetic expression, the right way to do this is to use the Shunting Yard algorithm.

    You have an input stream, which is the numbers and operators as the user typed them in and you have an output stream, which is the same numbers and operators but rearranged into reverse Polish notation. So, for example 2 + 2 * 4 would be transformed into 2 2 4 * + which is easily calculated by putting the numbers on a stack as you read them and applying the operators to the top items on the stack as you read them.

    To do this the algorithm has an operator stack which can be visualised as a siding (hence "shunting yard") into which low priority operators are shunted until they are needed.

    The general algorithm is

    • read an item from the input
    • if it is a number send it to the output
    • if the number is an operator then
      • while the operator on the top of the stack is of higher precedence than the operator you have pop the operator on the stack and send it to the output
      • push the operator you read from input onto the stack
    • repeat the above until the input is empty
    • pop all the operators on the stack into the output

    So if you have 2 + 2 * 4 (NB top of the stack is on the left, bottom of the stack is on the right)

    start:
        input: 2 + 2 * 4
        output: 
        stack: 
    
    step 1: send the 2 to output
        input: + 2 * 4
        output: 2
        stack: 
    
    step 2: stack is empty so put + on the stack
        input: 2 * 4
        output: 2
        stack: +
    
    step 3: send the 2 to output
        input: * 4
        output: 2 2
        stack: +
    
    step 4: + is lower priority than * so just put * on the stack
        input: 4
        output: 2 2
        stack: * +
    
    step 5: Send 4 to output
        input:
        output: 2 2 4
        stack: * +
    
    step 6: Input is empty so pop the stack to output
        input:
        output: 2 2 4 * +
        stack:
    
    

    The Wikipedia entry I linked above has a more detailed description and an algorithm that can handle parentheses and function calls and is much more extensible.


    For completeness, here is an implementation of my simplified version of the algorithm

    enum Token: CustomStringConvertible
    {
        var description: String
        {
            switch self
            {
            case .number(let num):
                return "\(num)"
            case .op(let symbol):
                return "\(symbol)"
            }
        }
    
        case op(String)
        case number(Int)
    
        var precedence: Int
        {
            switch self
            {
            case .op(let symbol):
                return Token.precedences[symbol] ?? -1
            default:
                return -1
            }
        }
        var operation: (inout Stack) -> ()
        {
            switch self
            {
            case .op(let symbol):
                return Token.operations[symbol]!
            case .number(let value):
                return { $0.push(value) }
            }
        }
        static let precedences = [ "+" : 10, "-" : 10, "*" : 20, "/" : 20]
        static let operations: [String : (inout Stack) -> ()] =
        [
            "+" : { $0.push($0.pop() + $0.pop()) },
            "-" : { $0.push($0.pop() - $0.pop()) },
            "*" : { $0.push($0.pop() * $0.pop()) },
            "/" : { $0.push($0.pop() / $0.pop()) }
        ]
    }
    
    struct Stack
    {
        var values: [T] = []
    
        var isEmpty: Bool { return values.isEmpty }
        mutating func push(_ n: T)
        {
            values.append(n)
        }
    
        mutating func pop() -> T
        {
            return values.removeLast()
        }
    
        func peek() -> T
        {
            return values.last!
        }
    }
    
    func shuntingYard(input: [Token]) -> [Token]
    {
        var operatorStack = Stack()
        var output: [Token] = []
    
        for token in input
        {
            switch token
            {
            case .number:
                output.append(token)
            case .op:
                while !operatorStack.isEmpty && operatorStack.peek().precedence >= token.precedence
                {
                    output.append(operatorStack.pop())
                }
                operatorStack.push(token)
            }
        }
        while !operatorStack.isEmpty
        {
            output.append(operatorStack.pop())
        }
        return output
    }
    
    let input: [Token] = [ .number(2), .op("+"), .number(2), .op("*"), .number(4)]
    let output = shuntingYard(input: input)
    
    print("\(output)")
    
    var dataStack = Stack()
    
    for token in output
    {
        token.operation(&dataStack)
    }
    
    print(dataStack.pop())
    

提交回复
热议问题