问题
顾客们在你的机器App或者网页上进行报价。最终的报价包含了2个部分,零件成本和劳动成本。我们希望能够对劳动力部分或者零件成本部分都可以进行适当的调整。这些调整可能来自优惠券,保修索赔或讲价。我们需要一种简单的语言来表达这些定制的调整。例如,我们应该能够定义一个表达式,可以为部分添加20%的折扣。或者是另一个减少10美元劳动力的,或者两者都有。
解决方案
我们会定义一种简单语言来做调整。我们将会有一套规则来涵盖加法,减法,乘法和除法。我们还将定义两个变量,他们将映射到零件和劳动力价格。一旦定义好了语言,我们的用户可以通过遵守语法的字符串来表达调整。例如,从我们将要使用的部分中减去10美元:
var expression = "l + p - 10.00"
//adjustment: total price is labor + partsPrice - $10
在开始之前,让我们先了解一下我们需要构建什么。为了能够解析字符串并将其解释为一个数学函数,我们需要定义一组规则和对象。通过观察我们的问题,我们发现了一些问题:
- 数字:比如$10或者15%。
- 变量:零件和劳动成本的占位符或者替代符。
- 操作: +, -, * , / 这些操作符会定义好2个变量之间的操作运算。
虽然这些对象都有他们自己的含义和解析,但是他们都有一个共同的特性,他们都需要用我们定义的语言来解析。因此,我们定义一个协议,所有表达式都需要实现我们这个协议。
protocol Expression {
func interpret(variables:[String: Expression]) -> Double
}
这个协议有一个函数方法叫作interpret。他有一个参数字典,和一个Double
返回值。之前我们提到,没一个表达式都需要一个函数去解析它,而且,我们希望,从表达式得到的最终返回值是一个Double
。
有了基础的表达式协议之后,我们可以看看var expression = "l + p - 10.00"
这个代码,他有3部分组成(我们上面提到的)。
那么,我们先来定义第一部分,值(数字)
class Number: Expression {
var value: Double
init(value: Double) {
self.value = value
}
func interpret(variables:[String: Expression]) -> Double {
return value
}
}
Number类是一个值类型,有一个属性value,代表着这个对象的值,有初始化方法,还实现了表达式协议interpret方法,解析出变量的值。
上面我们也提到,变量,变量是一个很重要的东西,我们这个场景下的变量就是l 和 p。那么,我们来定义一个这样的类:
/// 变量类
class Variable: Expression {
private var name: String
init(name: String) {
self.name = name
}
func interpret(variables: [String : Expression]) -> Double {
if let expression = variables[name] {
return expression.interpret(variables: variables)
}else {
return 0.00
}
}
}
这个变量类有一个属性name,还实现了表达式协议interpret方法,解析出变量的值。
有了变量和值类型之后,我们需要为他们提供一些加减乘除的基础API
class Add: Expression {
var leftOperand: Expression
var rightOperand: Expression
init(leftOperand: Expression, rightOperand: Expression) {
self.leftOperand = leftOperand
self.rightOperand = rightOperand
}
func interpret(variables:[String: Expression]) -> Double {
return leftOperand.interpret(variables: variables) + rightOperand.interpret(variables: variables)
}
}
class Subtract: Expression {
var leftOperand: Expression
var rightOperand: Expression
init(leftOperand: Expression, rightOperand: Expression) {
self.leftOperand = leftOperand
self.rightOperand = rightOperand
}
func interpret(variables: [String : Expression]) -> Double {
return leftOperand.interpret(variables: variables) - rightOperand.interpret(variables: variables)
}
}
class Multiply: Expression {
var leftOperand: Expression
var rightOperand: Expression
init(leftOperand: Expression, rightOperand: Expression) {
self.leftOperand = leftOperand
self.rightOperand = rightOperand
}
func interpret(variables: [String : Expression]) -> Double {
return leftOperand.interpret(variables: variables) * rightOperand.interpret(variables: variables)
}
}
class Divide: Expression {
var leftOperand: Expression
var rightOperand: Expression
init(leftOperand: Expression, rightOperand: Expression) {
self.leftOperand = leftOperand
self.rightOperand = rightOperand
}
func interpret(variables: [String : Expression]) -> Double {
return leftOperand.interpret(variables: variables) / rightOperand.interpret(variables: variables)
}
}
他们的写法都是一样的意思,有左右两边的表达式,可以是Variable
也可能是Number
类型,然后可以通过interpret
方法解析出值来。
接下来就是整个运算的核心了,他实现了如何报价具体,可以看我们写的注释,我就不详细说明了:
/// 评估价格类
class Evaluator: Expression {
/// 默认的语法树
var syntaxTree: Expression = Number(value: 0.00)
/// 表达式
var expression: String
/// 自定义的一个堆栈容器
struct Stack<T> {
var items = [T]()
/// 增加一个元素,入栈
///
/// - Parameter item: 元素
mutating func push(item: T) {
items.append(item)
}
/// 移除最后的元素,出栈
///
/// - Returns: 元素
mutating func pop() -> T {
return items.removeLast()
}
}
/// 初始化
///
/// - Parameter expression: 表达式
init(expression: String) {
self.expression = expression
}
/// 建立语法树
private func buildSyntaxTree() {
var expressionStack = Stack<Expression>()
/// 把所有元素按空格切割
var items = expression.components(separatedBy: " ")
var index = 0
while index < items.count {
switch items[index] {
case "*":
let nextExpression = getNextExpression(items: items, index: index)
expressionStack.push(item: Multiply(leftOperand: expressionStack.pop(), rightOperand: nextExpression))
index += 2
case "/":
let nextExpression = getNextExpression(items: items, index: index)
expressionStack.push(item: Divide(leftOperand: expressionStack.pop(), rightOperand: nextExpression))
index += 2
case "+":
let nextExpression = getNextExpression(items: items, index: index)
expressionStack.push(item: Add(leftOperand: expressionStack.pop(), rightOperand: nextExpression))
index += 2
case "-":
let nextExpression = getNextExpression(items: items, index: index)
expressionStack.push(item: Subtract(leftOperand: expressionStack.pop(), rightOperand: nextExpression))
index += 2
default:
/// 如果是值就初始化一个Number变量
if let doubleValue = items[index].doubleValue {
expressionStack.push(item: Number(value: doubleValue))
index += 1
} else {
//如果是l,p这样的变量,就初始化一个Variable变量
expressionStack.push(item: Variable(name: items[index]))
index += 1
}
}
}
syntaxTree = expressionStack.pop()
}
/// 获取下一个表达式
///
/// - Parameters:
/// - items: 元素
/// - index: 位置
/// - Returns: 表达式
private func getNextExpression(items: [String], index: Int) -> Expression {
/// 如果
let next = items[index + 1]
var nextExpression: Expression
/// 能解析成值
if let doubleValue = next.doubleValue {
nextExpression = Number(value: doubleValue)
} else {
//不能解析成值,则认为是一个变量,l,p,零件或者劳动力
nextExpression = Variable(name: next)
}
return nextExpression
}
/// 解析
///
/// - Parameter variables: 变量
/// - Returns: 返回值
func interpret(variables: [String : Expression]) -> Double {
//如果有2种运算
if (expression.contains("/") || expression.contains("*")) &&
(expression.contains("+") || expression.contains("-")) {
let expressions = parseoutAdditionsAndSubtractions(input: expression)
var newExpression = ""
var index = 0
for expression in expressions {
if expression == "+" || expression == "-" {
newExpression += expression
} else {
let eval = Evaluator(expression: expression)
let result = eval.interpret(variables: variables)
newExpression += String(result)
}
if index != expressions.count - 1 {
newExpression += " "
}
index += 1
}
let evaluator = Evaluator(expression: newExpression)
return evaluator.interpret(variables: variables)
} else {
//只有一种运算
//建立语法树
buildSyntaxTree()
return syntaxTree.interpret(variables: variables)//返回解析值
}
}
private func parseoutAdditionsAndSubtractions(input: String) -> [String] {
var result = [String]()
let items = input.components(separatedBy: " ")
var sentence = ""
var index = 0
for item in items {
if item == "+" || item == "-" {
result.append(sentence.trim())
result.append(item)
sentence = ""
} else {
sentence += item
if index != items.count - 1 {
sentence += " "
}
}
index += 1
}
result.append(sentence)
return result
}
}
然后我们再提供一个用户操作的封装类:
/// 报价类
class Quote {
/// 零件价格
var partsPrice: Double
/// 劳动力价格
var laborPrice: Double
/// 调整价格
var adjustments: String?
/// 初始化报价
///
/// - Parameters:
/// - partsPrice: 零件价格
/// - laborPrice: 劳动力价格
/// - adjustments: 调整价格
init(partsPrice: Double, laborPrice: Double, adjustments: String?) {
self.partsPrice = partsPrice
self.laborPrice = laborPrice
self.adjustments = adjustments
}
/// 初始化报价
///
/// - Parameters:
/// - partsPrice: 零件报价
/// - laborPrice: 劳动力报价
convenience init(partsPrice: Double, laborPrice: Double) {
self.init(partsPrice: partsPrice, laborPrice: laborPrice, adjustments: nil)
}
/// 总价格
var totalPrice: Double {
/// 需要调整价格
if let adjustments = adjustments {
var variables = [String: Expression] ()
variables["l"] = Number(value: laborPrice)
variables["p"] = Number(value: partsPrice)
/// 评估价格
let evaluator = Evaluator(expression: adjustments)
return evaluator.interpret(variables: variables)
} else {
return partsPrice + laborPrice
}
}
}
最后,我们写一个测试方法:
func interpreterTest() {
let quote = Quote(partsPrice: 145.00, laborPrice: 45.00)
//adjustment: total price is only labor plus $20
quote.adjustments = "l + 20.00"
print(quote.totalPrice)
//adjustment: total price is partsPrice - $10
quote.adjustments = "p - 10.00"
print(quote.totalPrice)
//adjustment: total price is labor and part - $10
quote.adjustments = "l + p - 10.00"
print(quote.totalPrice)
//adjustment: total price is labor + 10% off parts
quote.adjustments = "l + p - p * 0.1"
print(quote.totalPrice)
//adjustment: total price is 20% off labor + 10% off parts
quote.adjustments = "l - l * 0.2 + p - p * 0.1"
print(quote.totalPrice)
//adjustment: total price is %20 off total price
quote.adjustments = "l - l * 0.2 + p - p * 0.2"
print(quote.totalPrice)
//adjustment total price is parts * labor :|
quote.adjustments = "p * l"
print(quote.totalPrice)
}
这样,我们就可以自定义我们的表达式来做一些运算了。有兴趣可以查看具体的英文链接
打印的结果:
网友评论