ความต้องการของผมคือ Calculator ไม่ได้รับค่าเหมือนเครื่องคิดเลขขนาดเล็กที่รับค่า operand (ตัวเลข) สลับกับ operator (เครื่องหมาย + - x /) แล้วตอบกลับด้วยผลลัพธ์ แต่ต้องสามารถรับค่าเป็น String เข้ามาแล้วทำการประมวลผลทีเดียวเลย เพื่อให้สามารถทำงานแบบที่ต้องการได้ เราต้องกล่าวถึงเรื่องนิพจน์ (expressions) แบบ infix และ postfix กันนิดหน่อย ครับ
เมื่อเราสร้างนิพจน์ทางคณิตศาสตร์ เช่น 9 + 3 มนุษย์จะสามารถตีความได้ง่ายมากคือเป็นการเพิ่มค่าให้ 9 ไปอีก 3 รูปแบบการเขียนนิพจน์แบบนี้เรียกว่า "infix" มีรูปแบบการเขียนสลับกันไปมาระหว่าง operand (หรือตัวเลขหรือตัวแปร) กับ operator (สัญญลักษณ์ทางคณิตศาสตร์) หากนิพจน์นี้ยาวหรือสลับขึ้นก็มีการจัดกลุ่มด้วยวงเล็บ เช่น (6+3) x (3+6) รูปแบบ infix เป็นรูปแบบที่เราเรียนรู้กันในวิชาคณิตศาสตร์ตั้งแต่เด็กเราจึงตีความได้ง่าย แต่รูปแบบนี้ไม่เหมาะกับการทำให้คอมพิวเตอร์เข้าใจ เช่น หากเรามีนิพจน์ 9 + 3 x 10 / 2 ก็จะเริ่มเกิดความสับสนว่าจะเริ่มต้นจากตรงไหนดี 3 x 10 แล้ว หารด้วย 2 ตามด้วยบวกด้วย 9 หรือเริ่มต้นจาก 9 + 3 แล้วคูณด้วย 10 แล้วปิดท้ายด้วยการหารด้วย 2 (ที่ว่าสับสนนี้หมายถึงการเขียนชุดคำสั่งนะครับ คนเองไม่งงหรอก)
เพื่อลดความสับสนดังกล่าวเราจึงมีนิพจน์อีกแบบหนึ่งคือ "postfix" ซึ่งใช้การเขียนนิพจน์ที่ต้องนำเอา operator วางไว้หลัง operand สองตัวที่กระทำต่อกันเสมอ รูปแบบนี้ทำให้การเขียนชุดคำสั่งง่ายเพราะมีรูปแบบที่แน่นอน (สำหรับคนแล้วอาจจะงงหน่อยหากยังไม่ชิน) ตัวอย่าง เช่น
Infix | Postfix |
---|---|
9 + 3 | 9 3 + |
9 + 3 x 10 / 2 | 3 10 2 x / 9 + |
class Calculator():
def __init__(self):
self.prec = {}
self.prec["x"] = 3
self.prec["/"] = 3
self.prec["+"] = 2
self.prec["-"] = 2
self.prec["("] = 1
self.operators = ['+','-','x','/']
def calculate(self,input_str=None):
if input_str is None :
return "Error"
postfix_str = self.infix2postfix(input_str)
#print postfix_str
if len(postfix_str) > 0 :
return self.eval_postfix(postfix_str)
else :
return "Error"
def infix2postfix(self,infix_str):
out_Queue = Queue()
operator_Stack = Stack()
tokens = infix_str.split()
postfix_str=""
for token in tokens :
try :
d = float(token)
out_Queue.enqueue(d)
except :
if token == '(':
operator_Stack.push(token)
elif token == ')':
tmp = operator_Stack.pop()
while tmp != '(' :
out_Queue.enqueue(tmp)
tmp = operator_Stack.pop()
else : # token is an operator
while (not operator_Stack.is_empty()) and \
(self.prec[operator_Stack.peek()] >= self.prec[token]) :
out_Queue.enqueue(operator_Stack.pop())
operator_Stack.push(token)
while not operator_Stack.is_empty() :
out_Queue.enqueue(operator_Stack.pop())
while not out_Queue.is_empty():
postfix_str += str(out_Queue.dequeue())+" "
return postfix_str
def eval_postfix(self,postfix_str):
operand_queue = Queue()
operand_1=None
operand_2=None
tokens = postfix_str.split()
#print tokens
for token in tokens :
try :
d = float(token)
operand_queue.enqueue(d)
except:
if not operand_queue.is_empty() :
operand_1 = operand_queue.dequeue()
else :
operand_1 = None
if not operand_queue.is_empty() :
operand_2 = operand_queue.dequeue()
else :
operand_2 = None
#print operand_1,operand_2
if not (operand_1 is None or operand_2 is None) :
try:
result = self.do_math(token,operand_1,operand_2)
except ZeroDivisionError :
return "Division Error"
else :
result = 0
operand_queue.enqueue(result)
return operand_queue.dequeue()
def do_math(self,operator,operand1,operand2):
if operator == "x":
return operand1 * operand2
elif operator == "/":
return operand1 / operand2
elif operator == "+":
return operand1 + operand2
else:
return operand1 - operand2
จาก script ข้างบนมีการอ้างอิงถึงอีกสอง Class คือ Stack และ Queue ซึ่งทั้งสองเป็น Abstract Data Type ที่ใช้ใน algorithm นี้ มีขึ้นมาก็เพื่ออำนวยความสะดวกในการจัดการกับข้อมูลครับ ซึ่ง Python script แสดงไว้ข้างล่างนี้
class Stack():
def __init__(self):
self.items = []
def push(self,val):
self.items.append(val)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[-1:][0]
def is_empty(self):
if self.items == [] :
return True
else :
return False
class Queue:
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def enqueue(self,item):
self.items.append(item)
def dequeue(self):
return self.items.pop(0)
def front(self):
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
มาถึงตรงนี้เราก็มีทุกอย่างครบถ้วนแล้ว ก็มาดูผลการเรียกใช้งานบน Raspberry Pi กับ source code
Complete source code
import sys
from PyQt4 import QtCore, QtGui, uic
Ui_MainWindow, QtBaseClass = uic.loadUiType("calculator_ui.ui")
class Stack():
def __init__(self):
self.items = []
def push(self,val):
self.items.append(val)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[-1:][0]
def is_empty(self):
if self.items == [] :
return True
else :
return False
class Queue:
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def enqueue(self,item):
self.items.append(item)
def dequeue(self):
return self.items.pop(0)
def front(self):
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
class Calculator():
def __init__(self):
self.prec = {}
self.prec["x"] = 3
self.prec["/"] = 3
self.prec["+"] = 2
self.prec["-"] = 2
self.prec["("] = 1
self.operators = ['+','-','x','/']
def calculate(self,input_str=None):
if input_str is None :
return "Error"
postfix_str = self.infix2postfix(input_str)
#print postfix_str
if len(postfix_str) > 0 :
return self.eval_postfix(postfix_str)
else :
return "Error"
def infix2postfix(self,infix_str):
out_Queue = Queue()
operator_Stack = Stack()
tokens = infix_str.split()
postfix_str=""
for token in tokens :
try :
d = float(token)
out_Queue.enqueue(d)
except :
if token == '(':
operator_Stack.push(token)
elif token == ')':
tmp = operator_Stack.pop()
while tmp != '(' :
out_Queue.enqueue(tmp)
tmp = operator_Stack.pop()
else : # token is an operator
while (not operator_Stack.is_empty()) and (self.prec[operator_Stack.peek()] >= self.prec[token]) :
out_Queue.enqueue(operator_Stack.pop())
operator_Stack.push(token)
while not operator_Stack.is_empty() :
out_Queue.enqueue(operator_Stack.pop())
while not out_Queue.is_empty():
postfix_str += str(out_Queue.dequeue())+" "
return postfix_str
def eval_postfix(self,postfix_str):
operand_queue = Queue()
operand_1=None
operand_2=None
tokens = postfix_str.split()
for token in tokens :
try :
d = float(token)
operand_queue.enqueue(d)
except:
if not operand_queue.is_empty() :
operand_1 = operand_queue.dequeue()
else :
operand_1 = None
if not operand_queue.is_empty() :
operand_2 = operand_queue.dequeue()
else :
operand_2 = None
if not (operand_1 is None or operand_2 is None) :
try:
result = self.do_math(token,operand_1,operand_2)
except ZeroDivisionError :
return "Division Error"
else :
result = 0
operand_queue.enqueue(result)
return operand_queue.dequeue()
def do_math(self,operator,operand1,operand2):
if operator == "x":
return operand1 * operand2
elif operator == "/":
return operand1 / operand2
elif operator == "+":
return operand1 + operand2
else:
return operand1 - operand2
class MyApp(QtGui.QMainWindow, Ui_MainWindow):
#on_click = pyqtSignal(str)
def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.init_ui()
self.calculator = Calculator()
def init_ui(self):
self.num_btn_1.clicked.connect(self.append_text)
self.num_btn_2.clicked.connect(self.append_text)
self.num_btn_3.clicked.connect(self.append_text)
self.num_btn_4.clicked.connect(self.append_text)
self.num_btn_5.clicked.connect(self.append_text)
self.num_btn_6.clicked.connect(self.append_text)
self.num_btn_7.clicked.connect(self.append_text)
self.num_btn_8.clicked.connect(self.append_text)
self.num_btn_9.clicked.connect(self.append_text)
self.num_btn_0.clicked.connect(self.append_text)
self.left_paren_btn.clicked.connect(self.append_text)
self.right_paren_btn.clicked.connect(self.append_text)
self.plus_btn.clicked.connect(self.append_text)
self.minus_btn.clicked.connect(self.append_text)
self.mult_btn.clicked.connect(self.append_text)
self.div_btn.clicked.connect(self.append_text)
self.dot_btn.clicked.connect(self.append_text)
self.bsp_btn.clicked.connect(self.backspace)
self.clear_btn.clicked.connect(self.clear_text)
self.exe_btn.clicked.connect(self.calculate)
def append_text(self):
sender = self.sender()
val = sender.text()
if val in ['/','+','-','(',')','x'] :
val = " "+val+" "
disp = self.disp_text.text()
disp += val
self.disp_text.clear()
self.disp_text.setText(disp)
def backspace(self):
self.disp_text.backspace()
def clear_text(self):
self.disp_text.clear()
def calculate(self):
# need to change to pythonic string
txt = str(self.disp_text.text())
if len(txt) > 0 :
result = self.calculator.calculate(txt)
self.disp_text.clear()
self.disp_text.setText(str(result))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
ปล. โปรแกรมนี้เป็นโปรแกรมตัวอย่างที่นำมาประกอบการเขียนบทความ ยังไม่ใช่โปรแกรมที่สมบูรณ์แบบท่านสามารถนำ source code ไปพัฒนาและปรับปรุงได้อีกมากมาย ครับ ท่านที่สนใจสามารถดาวน์โหลดได้ที่นี่
Sign up here with your email
ConversionConversion EmoticonEmoticon