enum CalculatorState {
case oneOperand(screen: String)
case oneOperandAndOperator(operand: Double, operator: Operator)
case twoOperandsAndOperator(operand: Double, operator: Operator, screen: String)
}
enum Operator {
case addition
case subtraction
case multiplication
case division
}
enum CalculatorCommand {
case clear
case changeSign
case percent
case operation(Operator)
case equal
case addNumber(Character)
case addDot
}
extension CalculatorState {
static func reduce(state: CalculatorState, _ x: CalculatorCommand) -> CalculatorState {
switch x {
case .clear:
return CalculatorState.initial
case .addNumber(let c):
return state.mapScreen { $0 == "0" ? String(c) : $0 + String(c) }
case .addDot:
return state.mapScreen { $0.range(of: ".") == nil ? $0 + "." : $0 }
case .changeSign:
return state.mapScreen { "\(-(Double($0) ?? 0.0))" }
case .percent:
return state.mapScreen { "\((Double($0) ?? 0.0) / 100.0)" }
case .operation(let o):
switch state {
case let .oneOperand(screen):
return .oneOperandAndOperator(operand: screen.doubleValue, operator: o)
case let .oneOperandAndOperator(operand, _):
return .oneOperandAndOperator(operand: operand, operator: o)
case let .twoOperandsAndOperator(operand, oldOperator, screen):
return .twoOperandsAndOperator(operand: oldOperator.perform(operand, screen.doubleValue), operator: o, screen: "0")
}
case .equal:
switch state {
case let .twoOperandsAndOperator(operand, operat, screen):
let result = operat.perform(operand, screen.doubleValue)
return .oneOperand(screen: String(result))
default:
return state
}
}
}
}
class CalculatorViewController: ViewController {
@IBOutlet weak var lastSignLabel: UILabel!
@IBOutlet weak var resultLabel: UILabel!
@IBOutlet weak var allClearButton: UIButton!
@IBOutlet weak var changeSignButton: UIButton!
@IBOutlet weak var percentButton: UIButton!
@IBOutlet weak var divideButton: UIButton!
@IBOutlet weak var multiplyButton: UIButton!
@IBOutlet weak var minusButton: UIButton!
@IBOutlet weak var plusButton: UIButton!
@IBOutlet weak var equalButton: UIButton!
@IBOutlet weak var dotButton: UIButton!
@IBOutlet weak var zeroButton: UIButton!
@IBOutlet weak var oneButton: UIButton!
@IBOutlet weak var twoButton: UIButton!
@IBOutlet weak var threeButton: UIButton!
@IBOutlet weak var fourButton: UIButton!
@IBOutlet weak var fiveButton: UIButton!
@IBOutlet weak var sixButton: UIButton!
@IBOutlet weak var sevenButton: UIButton!
@IBOutlet weak var eightButton: UIButton!
@IBOutlet weak var nineButton: UIButton!
override func viewDidLoad() {
let commands: Observable<CalculatorCommand> = Observable.merge([
allClearButton.rx.tap.map { _ in .clear },
changeSignButton.rx.tap.map { _ in .changeSign },
percentButton.rx.tap.map { _ in .percent },
divideButton.rx.tap.map { _ in .operation(.division) },
multiplyButton.rx.tap.map { _ in .operation(.multiplication) },
minusButton.rx.tap.map { _ in .operation(.subtraction) },
plusButton.rx.tap.map { _ in .operation(.addition) },
equalButton.rx.tap.map { _ in .equal },
dotButton.rx.tap.map { _ in .addDot },
zeroButton.rx.tap.map { _ in .addNumber("0") },
oneButton.rx.tap.map { _ in .addNumber("1") },
twoButton.rx.tap.map { _ in .addNumber("2") },
threeButton.rx.tap.map { _ in .addNumber("3") },
fourButton.rx.tap.map { _ in .addNumber("4") },
fiveButton.rx.tap.map { _ in .addNumber("5") },
sixButton.rx.tap.map { _ in .addNumber("6") },
sevenButton.rx.tap.map { _ in .addNumber("7") },
eightButton.rx.tap.map { _ in .addNumber("8") },
nineButton.rx.tap.map { _ in .addNumber("9") }
])
let system = Observable.system(
CalculatorState.initial,
accumulator: CalculatorState.reduce,
scheduler: MainScheduler.instance,
feedback: { _ in commands }
)
.debug("calculator state")
.share(replay: 1)
system.map { $0.screen }
.bind(to: resultLabel.rx.text)
.disposed(by: disposeBag)
system.map { $0.sign }
.bind(to: lastSignLabel.rx.text)
.disposed(by: disposeBag)
}
func formatResult(_ result: String) -> String {
if result.hasSuffix(".0") {
return result.substring(to: result.index(result.endIndex, offsetBy: -2))
} else {
return result
}
}
}
enum Operator {
case addition
case subtraction
case multiplication
case division
}
enum CalculatorCommand {
case clear
case changeSign
case percent
case operation(Operator)
case equal
case addNumber(Character)
case addDot
}
enum CalculatorState {
case oneOperand(screen: String)
case oneOperandAndOperator(operand: Double, operator: Operator)
case twoOperandsAndOperator(operand: Double, operator: Operator, screen: String)
}
extension CalculatorState {
static let initial = CalculatorState.oneOperand(screen: "0")
func mapScreen(transform: (String) -> String) -> CalculatorState {
switch self {
case let .oneOperand(screen):
return .oneOperand(screen: transform(screen))
case let .oneOperandAndOperator(operand, operat):
return .twoOperandsAndOperator(operand: operand, operator: operat, screen: transform("0"))
case let .twoOperandsAndOperator(operand, operat, screen):
return .twoOperandsAndOperator(operand: operand, operator: operat, screen: transform(screen))
}
}
var screen: String {
switch self {
case let .oneOperand(screen):
return screen
case .oneOperandAndOperator:
return "0"
case let .twoOperandsAndOperator(_, _, screen):
return screen
}
}
var sign: String {
switch self {
case .oneOperand:
return ""
case let .oneOperandAndOperator(_, o):
return o.sign
case let .twoOperandsAndOperator(_, o, _):
return o.sign
}
}
}
extension CalculatorState {
static func reduce(state: CalculatorState, _ x: CalculatorCommand) -> CalculatorState {
switch x {
case .clear:
return CalculatorState.initial
case .addNumber(let c):
return state.mapScreen { $0 == "0" ? String(c) : $0 + String(c) }
case .addDot:
return state.mapScreen { $0.range(of: ".") == nil ? $0 + "." : $0 }
case .changeSign:
return state.mapScreen { "\(-(Double($0) ?? 0.0))" }
case .percent:
return state.mapScreen { "\((Double($0) ?? 0.0) / 100.0)" }
case .operation(let o):
switch state {
case let .oneOperand(screen):
return .oneOperandAndOperator(operand: screen.doubleValue, operator: o)
case let .oneOperandAndOperator(operand, _):
return .oneOperandAndOperator(operand: operand, operator: o)
case let .twoOperandsAndOperator(operand, oldOperator, screen):
return .twoOperandsAndOperator(operand: oldOperator.perform(operand, screen.doubleValue), operator: o, screen: "0")
}
case .equal:
switch state {
case let .twoOperandsAndOperator(operand, operat, screen):
let result = operat.perform(operand, screen.doubleValue)
return .oneOperand(screen: String(result))
default:
return state
}
}
}
}
extension Operator {
var sign: String {
switch self {
case .addition: return "+"
case .subtraction: return "-"
case .multiplication: return "×"
case .division: return "/"
}
}
var perform: (Double, Double) -> Double {
switch self {
case .addition: return (+)
case .subtraction: return (-)
case .multiplication: return (*)
case .division: return (/)
}
}
}
private extension String {
var doubleValue: Double {
guard let double = Double(self) else {
return Double.infinity
}
return double
}
}