VIẾT CHƯƠNG TRÌNH TÍNH GIÁ TRỊ BIỂU THỨC pdf

16 2.5K 2
VIẾT CHƯƠNG TRÌNH TÍNH GIÁ TRỊ BIỂU THỨC pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

VIẾT CHƯƠNG TRÌNH TÍNH GIÁ TRỊ BIỂU THỨC Tác giả: Đặng Nhật Anh Cấp độ bài viết: Trung bình Tóm tắt: Bài tut này có mục đích hướng dẫn các bạn tự viết ra một chương trình tính giá trị biểu thức toán học đơn giản bằng một số kỹ thuật căn bản Bài hướng dẫn này bao gồm 3 phần: 1. Ngăn xếp và cách tạo lập; 2. Ký pháp Nghịch đảo Ba Lan; 3. Viết chương trình. Cả ba phần sẽ được để trong cùng một bài viết cho liên tục. Phần 1 - Ngăn xếp và cách tạo lập I. Giới thiệu Ngăn xếp (stack) là một loại cấu trúc dữ liệu tuân theo quy tắc Last In, First Out (LIFO, tức vào sau cùng, ra trước tiên). Có thể hình dung ngăn xếp như một chồng sách mà mỗi quyển sách là một phần tử dữ liệu. Quyển sách để lên trên cùng (tức quyển sau cùng) sẽ được lấy ra đầu tiên, quyển được bỏ vào đầu tiên thì lấy ra sau cùng. Ngăn xếp có nhiều ứng dụng trong lập trình, đặc biệt là trong các trình xử lý biểu thức, cây cú pháp, v.v. Có thể tìm hiểu thêm về ngăn xếp ở Wikipedia. http://vi.wikipedia.org/wiki/Ng%C4%83n_x %E1%BA%BFp II. Phương thức và thuộc tính Theo mặc định, ngăn xếp có hai phương thức (còn gọi là phép toán) chính yếu: Push: bỏ một phần tử vào ngăn xếp, ở vị trí trên cùng (sau cùng). Pop: lấy phần tử trên cùng (sau cùng) ra khỏi ngăn xếp, trả về giá trị của phần tử đó. Ngoài ra, người ta còn có thể thêm một phương thức nữa: Peek: trả về giá trị phần tử trên cùng của ngăn xếp nhưng không lấy nó ra khỏi ngăn xếp. Thuộc tính Length của ngăn xếp cho biết độ lớn của nó. Nếu ngăn xếp không có phần tử nào, ta có Length = 0. III. Tạo lập Trong VB6 mặc nhiên không có kiểu dữ liệu ngăn xếp. Do đó ta sẽ tự tạo. Trên lý thuyết, các phần tử trong ngăn xếp có thể có một kiểu bất kỳ. Trong ví dụ sau đây, chúng ta sẽ làm một ngăn xếp chứa các số nguyên, đặt tên là IntStack. Ta tạo ngăn xếp bằng một class, có 3 phương thức Push, Pop, Peek và 1 thuộc tính Length. Đoạn mã của class sẽ như sau: ' Khai báo các biến, hằng cần thiết ' Hằng MAX là số lượng tối đa của các phần tử, ' thêm vào quá số này sẽ báo lỗi "Stack Overflow" Private Const MAX As Integer = 256 1 ' Mảng chứa các phần tử, phải đặt chỉ số từ 1 Private Nodes(1 To MAX) As Integer ' Biến con trỏ, tức chỉ số của phần tử trên cùng. Private Ptr As Integer ' Do mặc nhiên Ptr=0, mà khi tạo lập thì ngăn xếp chưa có phần tử nào. ' Nếu đặt Nodes(0 To MAX-1) thì hóa ra Ptr=0 chỉ phần tử đầu tiên, vốn không có. ' Đó là lý do Nodes(1 To MAX) ' Phương thức Push Public Sub Push(ByVal data As Integer) If Ptr < MAX Then Ptr = Ptr + 1 Nodes(Ptr) = data Else 'Báo lỗi Stack Overflow End If End Sub ' Phương thức Pop Public Function Pop() As Integer If Ptr > 0 Then 'Trả về phần tử trên cùng Pop = Nodes(Ptr) 'Xóa nó khỏi ngăn xếp Nodes(Ptr) = 0 Ptr = Ptr - 1 'Đẩy con trỏ xuống Else 'Báo lỗi Stack Empty Pop = 0 End If End Function ' Phương thức Peek Public Function Peek() As Integer If Ptr > 0 Then ' Trả về phần tử trên cùng Peek = Nodes(Ptr) Else 'Báo lỗi Stack Empty Peek = 0 End If End Function 2 ' Thuộc tính Length Public Property Get Length() As Integer Length = Ptr End Property Bạn thử kiểm tra bằng đoạn mã sau: Dim stack As New IntStack stack.Push 8 stack.Push 3 MsgBox stack.Pop() MsgBox stack.Peek() MsgBox stack.Length Phần 2 - Ký pháp Nghịch đảo Ba Lan (Quý vị có thể tham khảo bài Ký pháp Nghịch đảo Ba Lan của bác truongphu ở trong chuyên mục Tut VB & VBA) I. Giới thiệu Bình thường, để viết một biểu thức tổng 2 số, ta viết a + b. Cách viết này gọi là trung tố (infix). Bởi vì dấu + (ta gọi là toán tử) nằm ở giữa a, b (toán hạng). Một biểu thức phức tạp hơn có thể ở dạng (a + b) * c. Ký pháp Nghịch đảo Ba Lan (Reversed Polish Notation - RPN) là dạng biểu thức hậu tố (postfix), nghĩa là toán tử nằm sau toán hạng. Theo đó, biểu thức a + b viết thành a b +; biểu thức (a + b) * c sẽ viết thành a b + c *. Phương pháp này giúp loại bỏ dấu ngoặc trong các biểu thức, cũng như không phải lưu ý tới mức ưu tiên toán tử (operator precedence). RPN là một phương tiện tốt giúp máy tính "đọc" được các biểu thức toán học, từ đó tính ra giá trị. Vậy, khi ta nhập một biểu thức toán vào một chương trình tính giá trị biểu thức, nó sẽ chuyển biểu thức về dạng RPN, sau đó mới tính toán. Toán tử nhị phân & Toán tử đơn phân: Một toán tử gọi là nhị phân khi nó tác động lên 2 toán hạng, vậy cộng (+), trừ (-), nhân ( *), chia (/) và lũy thừa (^) là toán tử nhị phân. Một toán tử gọi là đơn phân khi nó chỉ tác động lên 1 toán hạng, vậy dấu âm (-) dương (+) là toán tử đơn phân. II. Trình tự chuyển đổi Trước hết là tạo hai ngăn xếp, đặt tên là rpnStack và oprStack. rpnStack chứa các toán tử/toán hạng trong và sau lúc chuyển đổi. oprStack tạm thời chứa các toán tử trong lúc chờ đưa vào rpnStack. Ta theo một vòng lặp từ đầu cho tới cuối biểu thức, như sau: • Nếu gặp toán hạng, push vào rpnStack. 3 • Nếu gặp dấu "(", push vào oprStack. • Nếu gặp dấu ")", lần lượt pop các toán tử trong oprStack cho tới khi gặp dấu "(", bấy giờ pop dấu "(" ấy bỏ đi. • Nếu gặp toán tử, tạm gọi là opCurrent, kiểm tra xem toán tử trên cùng của oprStack có mức ưu tiên cao hơn hoặc bằng opCurrent hay không. Nếu có thì pop nó push vào rpnStack, cứ vậy hoài cho tới khi gặp một toán tử có mức ưu tiên nhỏ hơn opCurrent, hoặc khi oprStack trống rỗng, thì dừng, bấy giờ push opCurrent vào oprStack. Mức ưu tiên toán tử (thấp lên cao): dấu ngoặc < cộng, trừ < nhân chia < lũy thừa < dấu âm/dương (cộng trừ đơn phân). • Cuối cùng, lần lượt pop các phần tử trong oprStack và push vào rpnStack cho tới hết. Vì dấu âm dương và dấu cộng trừ giống nhau nên khi gặp phải thì cần phân biệt. Nếu trước nó là dấu ( hoặc một toán tử thì nó là dấu âm dương, nếu trước nó là dấu ) hoặc một toán hạng thì nó là dấu cộng trừ. III. Trình tự tính toán Tạo một ngăn xếp nữa gọi là ResultStack. Xét từng phần tử của rpnStack, từ thấp lên cao (từ đầu về cuối). • Nếu gặp toán hạng thì push vào ResultStack. • Nếu gặp toán tử nhị phân, pop hai phần tử từ ResultStack, thực hiện phép toán, push kết quả trở lại ResultStack. • Nếu gặp toán tử đơn phân, pop một phần tử từ ResultStack, thực hiện phép toán, push kết quả trở lại ResultStack. Kết quả cuối cùng là phần tử trên cùng của ResultStack. Tham khảo thêm: http://en.wikipedia.org/wiki/Reverse_Polish_notation 4 Phần 3 - Viết chương trình I. Định hình Khả năng của chương trình là tính được các phép toán cộng, trừ, nhân, chia, lũy thừa, âm dương trên hệ số thực. Chương trình của chúng ta sẽ có một TextBox gọi là txtExpression để nhập biểu thức, một TextBox là txtResult chứa kết quả, cùng một Command Button là cmdEvaluate để tính toán. Vậy ta cần một module (MainModule) để chứa các hàm cần thiết cho việc xử lý chuỗi biểu thức và tính giá trị, một class làm ngăn xếp và ngăn xếp này chứa các phần tử kiểu String (StringStack). II. Viết mã Tạo form theo hình dạng tùy ý, với 3 điều khiển có tên như đã nêu. Tạo class StringStack, biên vào đoạn mã sau: Option Explicit Private Const MAX As Integer = 256 Private Nodes(1 To MAX) As String Private Ptr As Integer Public Property Get Length() As Integer Length = Ptr End Property Public Sub Push(ByVal data As String) If Ptr < MAX Then Ptr = Ptr + 1 Nodes(Ptr) = data End If End Sub Public Function Pop() As String If Ptr > 0 Then Pop = Nodes(Ptr) Nodes(Ptr) = "" Ptr = Ptr - 1 Else Pop = "" End If End Function Public Function Peek() As String If Ptr >= 0 Then Peek = Nodes(Ptr) 5 Else Peek = "" End If End Function ' Ngăn xếp tiêu chuẩn không có phương thức này, ' nhưng ta thêm vào để giúp cho việc tính toán Public Function GetAt(ByVal Position As Integer) As String GetAt = Nodes(Position + 1) End Function Tạo module, đặt là MainModule và biên vào đoạn mã sau đây: Option Explicit ' Loại bỏ các khoảng trắng không cần thiết khỏi biểu thức Private Function EliminateWhite(ByVal sExpr As String) As String Dim s As String s = Replace(sExpr, " ", "") s = Replace(s, vbTab, "") EliminateWhite = s End Function ' Kiểm tra xem tham số đưa vào có phải là toán tử hay không, ' ngoại trừ dấu ")" ' Ta dùng hàm này để phân biệt dấu âm dương với dấu cộng trừ Private Function IsOperator(ByVal token As String) As Boolean Const sOp As String = "(+-*/^" ' Không có ")" If InStr(1, sOp, token) Then IsOperator = True Else IsOperator = False End If End Function ' Lấy giá trị của toán hạng Private Function GetNumber(ByVal sExpr As String, iPos As Integer) _ As Double ' Để xem số này có phải số nguyên hay không, mặc nhiên là phải Dim bIsInt As Boolean: bIsInt = True ' Chuỗi tạm chứa kết quả Dim sValue As String ' Phần nguyên và phần thập phân (nếu có) Dim sInt As String, sDec As String ' Miễn là con số thì cứ đưa vào sInt Do While IsNumeric(Mid$(sExpr, iPos, 1)) sInt = sInt & Mid$(sExpr, iPos, 1) iPos = iPos + 1 Loop ' Không phải là số nữa, vậy có phải dấu chấm thập phân? If Mid$(sExpr, iPos, 1) = "." Then ' Nếu phải thì đây không còn là số nguyên nữa bIsInt = False ' Đẩy vị trí lên để lấy số cho phần thập phân iPos = iPos + 1 Do While IsNumeric(Mid$(sExpr, iPos, 1)) 6 sDec = sDec & Mid$(sExpr, iPos, 1) iPos = iPos + 1 Loop End If ' Kết hợp phần nguyên và phần thập phân (nếu có) sValue = sInt & IIf(bIsInt, "", "." & sDec) GetNumber = Val(sValue) ' Trả về giá trị Double End Function ' Tính mức ưu tiên toán tử Private Function OprPrec(ByVal op As String) As Byte Select Case op Case "(", ")" OprPrec = 0 Case "+", "-" OprPrec = 1 Case "*", "/" OprPrec = 2 Case "^" OprPrec = 3 Case "u+", "u-" 'Toán tử đơn phân (unary plus, unary minus) OprPrec = 4 Case Else End Select End Function ' Biến biểu thức thành một mảng các token (mỗi token là một toán tử hay toán hạng) Private Sub Tokenise(ByVal sExpr As String, Tokens() As String) Dim sResult(255) As String Dim iToken As Integer Dim i As Integer: i = 1 ' Cuối biểu thức có một ký tự NULL để báo hiệu kết thúc Do While Mid$(sExpr, i, 1) <> Chr$(0) Select Case Mid$(sExpr, i, 1) Case "0" To "9" 'Là số, dùng hàm GetNumber để lấy toàn bộ con số sResult(iToken) = GetNumber(sExpr, i) iToken = iToken + 1 Case "(", ")", "+", "-", "*", "/", "^" 'Là toán tử sResult(iToken) = Mid$(sExpr, i, 1) iToken = iToken + 1 i = i + 1 Case Else 'Không hay rồi! MsgBox "Ky hieu khong hop le!", vbCritical, "Loi" GoTo ReturnValue End Select Loop ReturnValue: Tokens = sResult ' Chỉ lấy đủ số cho đỡ tốn bộ nhớ ReDim Preserve Tokens(iToken - 1) As String End Sub 7 ' Hàm quan trọng nhất Public Function ExprEval(ByVal sExpr As String) As Double ' Nếu là chuỗi rỗng thì kết thúc, trả về 0 If Trim$(sExpr) = vbNullString Then ExprEval = 0: Exit Function End If ' Loại bỏ khoảng trắng dư thừa sExpr = EliminateWhite(sExpr) ' Thêm ký tự NULL vào cuối biểu thức để báo kết thúc sExpr = sExpr & Chr$(0) ' Hai ngăn xếp cần cho chuyển đổi Dim rpnStack As New StringStack Dim oprStack As New StringStack '##### Chuyển đổi ##### ' Mảng chứa các token Dim Tokens() As String ' Thiết lập mảng token Tokenise sExpr, Tokens ' Toán tử tạm thời Dim tmpOp As String Dim i As Integer For i = 0 To UBound(Tokens) Select Case Tokens(i) Case "(" oprStack.Push Tokens(i) Case ")" If oprStack.Length > 0 Then Do While (oprStack.Length > 0 And oprStack.Peek <> "(") rpnStack.Push oprStack.Pop Loop If oprStack.Length > 0 Then oprStack.Pop End If End If Case "+", "-" ' Phải xem đây là toán tử nhị phân hay đơn phân If i > 0 Then 'Nếu không phải token đầu tiên ' Nếu trước nó là toán tử, ngoại trừ ")" If IsOperator(Tokens(i - 1)) = True Then 'Nó là dấu âm dương, thêm chữ u vào để phân biệt tmpOp = "u" & Tokens(i) Else 'Còn không thì nó là dấu cộng trừ bình thường tmpOp = Tokens(i) 8 End If Else 'Nếu là token đầu tiên, nó hẳn phải là toán tử đơn phân tmpOp = "u" & Tokens(i) End If If oprStack.Length > 0 Then Do While OprPrec(oprStack.Peek) >= OprPrec(tmpOp) rpnStack.Push oprStack.Pop Loop oprStack.Push tmpOp Else oprStack.Push tmpOp End If Case "^", "*", "/" If oprStack.Length > 0 Then Do While OprPrec(oprStack.Peek) >= OprPrec(Tokens(i)) rpnStack.Push oprStack.Pop Loop oprStack.Push Tokens(i) Else oprStack.Push Tokens(i) End If Case Else rpnStack.Push Tokens(i) End Select tmpOp = "" Next i ' Xả hết từ oprStack vào rpnStack Do While oprStack.Length > 0 rpnStack.Push oprStack.Pop Loop '##### Tính toán ##### ' Phòng hờ "bất trắc" On Error Resume Next ' Ngăn xếp kết quả Dim ResultStack As New StringStack Dim j As Integer For j = 0 To rpnStack.Length - 1 Dim a As Double, b As Double Select Case rpnStack.GetAt(j) Case "u+" 'ResultStack.Push ResultStack.Pop Case "u-" ResultStack.Push CStr(-Val(ResultStack.Pop)) Case "^" b = Val(ResultStack.Pop) a = Val(ResultStack.Pop) ResultStack.Push CStr(a ^ b) Case "*" 9 b = Val(ResultStack.Pop) a = Val(ResultStack.Pop) ResultStack.Push CStr(a * b) Case "/" b = Val(ResultStack.Pop) a = Val(ResultStack.Pop) If b <> 0 Then ResultStack.Push CStr(a / b) Else MsgBox "Chia cho 0", vbCritical,"Loi" Exit Function End If Case "+" b = Val(ResultStack.Pop) a = Val(ResultStack.Pop) ResultStack.Push CStr(a + b) Case "-" b = Val(ResultStack.Pop) a = Val(ResultStack.Pop) ResultStack.Push CStr(a - b) Case Else ResultStack.Push rpnStack.GetAt(j) End Select Next j ReturnValue: ExprEval = Val(ResultStack.Pop) End Function Trong sự kiện click của cmdEvaluate, ta ghi: Private Sub cmdEvaluate_Click() txtResult.Text = ExprEval(txtExpression.Text) End Sub Lưu project và chạy thử. Bạn nhập vào một biểu thức đại loại như ((1 + 2) * 4) + 3 và bấm nút Tính để thấy kết quả. Kết luận Bài hướng dẫn đã giới thiệu đến quý vị một số khái niệm căn bản trong kỹ thuật xử lý chuỗi biểu thức, cũng như chỉ dẫn cách viết một chương trình tính giá trị biểu thức đơn giản. Chương trình còn một vài chỗ cần mở rộng, như bộ bẫy lỗi và báo lỗi, xử lý số dạng Hexa (vd: 0xFF) và dấu chấm động (vd: 1.342E5), xử lý biến. Những phần này được để ngỏ cho quý vị có dịp động não và phát huy sự sáng tạo của mình! Cám ơn quý vị vì đã đọc. 10 [...]...Phần Bổ Sung Viết chương trình tính giá trị biểu thức không dùng ngăn xếp Thưa quý vị, như phần trước đã đề cập, chúng ta dùng kiểu dữ liệu ngăn xếp để chứa các toán tử, toán hạng giúp cho việc tính toán biểu thức Câu hỏi đặt ra là, nếu không dùng ngăn xếp, liệu chúng ta có thể tận dụng một kiểu dữ liệu sẵn có... thức của Collection Collection có bốn phương thức: 1 Add (Item, [ Key ], [ Before ], [ After ]): thêm một phần tử có giá trị là Item, khóa định danh là Key Các tham số Before và After lần lượt là khóa của phần tử trước và sau Chỉ có tham số Item là bắt buộc Mặc định, phần tử sẽ được thêm vào sau chót 2 Remove (Index): xóa phần tử ở vị trí Index Lưu ý, index của các phần tử trong Collection có giá trị. .. String 132 Peek = c.Item(c.Count) 133 End Function Lưu ý Do Collection sử dụng kiểu dữ liệu Variant (có kích thước 16 byte) nên sẽ cần nhiều bộ nhớ hơn các kiểu dữ liệu khác Tác giả bài viết không khuyên dùng trong chương trình này 16 ... trong Collection có giá trị bắt đầu từ 1 chứ không phải từ 0 như mảng 3 Count: trả về số lượng các phần tử, tức cũng là chỉ số của phần tử sau cùng 4 Item (Index): trả về phần tử ở vị trí Index II Mã chương trình Mã nguồn hàm ExprEval ở phần trước có thể được giữ nguyên để tham khảo, các hàm khác phải giữ lại không được bỏ Ở cuối module chính, ta thêm vào các hàm sau: 1 2 ' Hàm mới: ExprEvalNoStack ' . VIẾT CHƯƠNG TRÌNH TÍNH GIÁ TRỊ BIỂU THỨC Tác giả: Đặng Nhật Anh Cấp độ bài viết: Trung bình Tóm tắt: Bài tut này có mục đích hướng dẫn các bạn tự viết ra một chương trình tính giá trị biểu thức. đó tính ra giá trị. Vậy, khi ta nhập một biểu thức toán vào một chương trình tính giá trị biểu thức, nó sẽ chuyển biểu thức về dạng RPN, sau đó mới tính toán. Toán tử nhị phân & Toán tử. bản trong kỹ thuật xử lý chuỗi biểu thức, cũng như chỉ dẫn cách viết một chương trình tính giá trị biểu thức đơn giản. Chương trình còn một vài chỗ cần mở rộng, như bộ bẫy lỗi và báo lỗi, xử

Ngày đăng: 07/07/2014, 14:20

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan