forked from TheAlgorithms/Swift
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request TheAlgorithms#5 from 5sw/shunting_yard
Implement shunting yard algorithm to evaluate simple math expressions
- Loading branch information
Showing
1 changed file
with
97 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import Foundation | ||
|
||
enum ShuntingYard { | ||
enum Operator: String, CaseIterable { | ||
case power = "^" | ||
case plus = "+" | ||
case minus = "-" | ||
case times = "*" | ||
case divide = "/" | ||
} | ||
|
||
static func evaluate(_ string: String) -> Double { | ||
let scanner = Scanner(string: string) | ||
var numberStack: [Double] = [] | ||
var operatorStack: [Operator] = [] | ||
|
||
func applyOperator(_ op: Operator) { | ||
guard let a = numberStack.popLast(), let b = numberStack.popLast() else { | ||
return | ||
} | ||
|
||
numberStack.append(op.apply(a, b)) | ||
} | ||
|
||
while !scanner.isAtEnd { | ||
if let op = scanner.scanOperator() { | ||
while let last = operatorStack.last, last.precedence > op.precedence || (op.leftAssociative && last.precedence == op.precedence) { | ||
applyOperator(last) | ||
operatorStack.removeLast() | ||
} | ||
operatorStack.append(op) | ||
} else if let number = scanner.scanDouble() { | ||
numberStack.append(number) | ||
} else { | ||
break | ||
} | ||
} | ||
|
||
while let op = operatorStack.popLast() { | ||
applyOperator(op) | ||
} | ||
|
||
return numberStack.first ?? 0 | ||
} | ||
} | ||
|
||
extension ShuntingYard.Operator { | ||
var precedence: Int { | ||
switch self { | ||
case .power: return 3 | ||
case .divide, .times: return 2 | ||
case .plus, .minus: return 1 | ||
} | ||
} | ||
|
||
var leftAssociative: Bool { | ||
switch self { | ||
case .power: return false | ||
case .plus, .minus, .times, .divide: return true | ||
} | ||
} | ||
|
||
func apply(_ a: Double, _ b: Double) -> Double { | ||
switch self { | ||
case .power: return pow(b, a) | ||
case .divide: return b / a | ||
case .times: return a * b | ||
case .plus: return a + b | ||
case .minus: return b - a | ||
} | ||
} | ||
} | ||
|
||
private extension Scanner { | ||
func scanOperator() -> ShuntingYard.Operator? { | ||
for op in ShuntingYard.Operator.allCases { | ||
if scanString(op.rawValue) != nil { | ||
return op | ||
} | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
func testShuntingYard() { | ||
func test(_ x: String) { | ||
print(x,"=", ShuntingYard.evaluate(x)) | ||
} | ||
|
||
test("3 + 4 * 5") | ||
test("4 * 5 + 3") | ||
test("2 ^ 3 ^ 4") | ||
test("10.5 - 4 * 5") | ||
test("2 + 3 ^ 4") | ||
test("2 * 3 ^ 4") | ||
test("3 ^ 4") | ||
} |