• Vui lòng đọc nội qui diễn đàn để tránh bị xóa bài viết
  • Tìm kiếm trước khi đặt câu hỏi

Viết chương trình tính giá trị biểu thức

Các bài viết hướng dẫn, giúp các bạn hiểu và tiếp cận với Visual Basic nhanh hơn
Hình đại diện của người dùng
alexanderdna
Guru
Guru
Bài viết: 214
Ngày tham gia: T.Ba 14/07/2009 11:13 am
Đến từ: Sài Gòn
Has thanked: 3 time
Been thanked: 15 time

Viết chương trình tính giá trị biểu thức

Gửi bàigửi bởi alexanderdna » T.Bảy 08/08/2009 10:54 am

Tên bài viết: 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:
  1. ' Khai báo các biến, hằng cần thiết
  2. ' Hằng MAX là số lượng tối đa của các phần tử,
  3. ' thêm vào quá số này sẽ báo lỗi "Stack Overflow"
  4. Private Const MAX As Integer = 256
  5.  
  6. ' Mảng chứa các phần tử, phải đặt chỉ số từ 1
  7. Private Nodes(1 To MAX) As Integer
  8.  
  9. ' Biến con trỏ, tức chỉ số của phần tử trên cùng.
  10. Private Ptr As Integer
  11.  
  12. ' 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.
  13. ' 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ó.
  14. ' Đó là lý do Nodes(1 To MAX)
  15.  
  16. ' Phương thức Push
  17. Public Sub Push(ByVal data As Integer)
  18.     If Ptr < MAX Then
  19.         Ptr = Ptr + 1
  20.         Nodes(Ptr) = data
  21.     Else
  22.         'Báo lỗi Stack Overflow
  23.     End If
  24. End Sub
  25.  
  26. ' Phương thức Pop
  27. Public Function Pop() As Integer
  28.     If Ptr > 0 Then
  29.         'Trả về phần tử trên cùng
  30.         Pop = Nodes(Ptr)
  31.         'Xóa nó khỏi ngăn xếp
  32.         Nodes(Ptr) = 0
  33.         Ptr = Ptr - 1 'Đẩy con trỏ xuống
  34.     Else
  35.         'Báo lỗi Stack Empty
  36.         Pop = 0
  37.     End If
  38. End Function
  39.  
  40. ' Phương thức Peek
  41. Public Function Peek() As Integer
  42.     If Ptr > 0 Then
  43.         ' Trả về phần tử trên cùng
  44.         Peek = Nodes(Ptr)
  45.     Else
  46.         'Báo lỗi Stack Empty
  47.         Peek = 0
  48.     End If
  49. End Function
  50.  
  51. ' Thuộc tính Length
  52. Public Property Get Length() As Integer
  53.     Length = Ptr
  54. End Property


Bạn thử kiểm tra bằng đoạn mã sau:
  1. Dim stack As New IntStack
  2. stack.Push 8
  3. stack.Push 3
  4. MsgBox stack.Pop()
  5. MsgBox stack.Peek()
  6. 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à rpnStackoprStack. 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.
  • 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

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:
  1. Option Explicit
  2.  
  3. Private Const MAX As Integer = 256
  4. Private Nodes(1 To MAX) As String
  5. Private Ptr As Integer
  6.  
  7. Public Property Get Length() As Integer
  8.     Length = Ptr
  9. End Property
  10.  
  11. Public Sub Push(ByVal data As String)
  12.     If Ptr < MAX Then
  13.         Ptr = Ptr + 1
  14.         Nodes(Ptr) = data
  15.     End If
  16. End Sub
  17.  
  18. Public Function Pop() As String
  19.     If Ptr > 0 Then
  20.         Pop = Nodes(Ptr)
  21.         Nodes(Ptr) = ""
  22.         Ptr = Ptr - 1
  23.     Else
  24.         Pop = ""
  25.     End If
  26. End Function
  27.  
  28. Public Function Peek() As String
  29.     If Ptr >= 0 Then
  30.         Peek = Nodes(Ptr)
  31.     Else
  32.         Peek = ""
  33.     End If
  34. End Function
  35.  
  36. ' Ngăn xếp tiêu chuẩn không có phương thức này,
  37. ' nhưng ta thêm vào để giúp cho việc tính toán
  38. Public Function GetAt(ByVal Position As Integer) As String
  39.     GetAt = Nodes(Position + 1)
  40. End Function


Tạo module, đặt là MainModule và biên vào đoạn mã sau đây:
  1. Option Explicit
  2.  
  3. ' Loại bỏ các khoảng trắng không cần thiết khỏi biểu thức
  4. Private Function EliminateWhite(ByVal sExpr As String) As String
  5.     Dim s As String
  6.     s = Replace(sExpr, " ", "")
  7.     s = Replace(s, vbTab, "")
  8.     EliminateWhite = s
  9. End Function
  10.  
  11. ' Kiểm tra xem tham số đưa vào có phải là toán tử hay không,
  12. ' ngoại trừ dấu ")"
  13. ' Ta dùng hàm này để phân biệt dấu âm dương với dấu cộng trừ
  14. Private Function IsOperator(ByVal token As String) As Boolean
  15.     Const sOp As String = "(+-*/^" ' Không có ")"
  16.     If InStr(1, sOp, token) Then
  17.         IsOperator = True
  18.     Else
  19.         IsOperator = False
  20.     End If
  21. End Function
  22.  
  23. ' Lấy giá trị của toán hạng
  24. Private Function GetNumber(ByVal sExpr As String, iPos As Integer) _
  25.         As Double
  26.     ' Để xem số này có phải số nguyên hay không, mặc nhiên là phải
  27.     Dim bIsInt As Boolean: bIsInt = True
  28.     ' Chuỗi tạm chứa kết quả
  29.     Dim sValue As String
  30.     ' Phần nguyên và phần thập phân (nếu có)
  31.     Dim sInt As String, sDec As String
  32.     ' Miễn là con số thì cứ đưa vào sInt
  33.     Do While IsNumeric(Mid$(sExpr, iPos, 1))
  34.         sInt = sInt & Mid$(sExpr, iPos, 1)
  35.         iPos = iPos + 1
  36.     Loop
  37.     ' Không phải là số nữa, vậy có phải dấu chấm thập phân?
  38.     If Mid$(sExpr, iPos, 1) = "." Then
  39.         ' Nếu phải thì đây không còn là số nguyên nữa
  40.         bIsInt = False
  41.         ' Đẩy vị trí lên để lấy số cho phần thập phân
  42.         iPos = iPos + 1
  43.         Do While IsNumeric(Mid$(sExpr, iPos, 1))
  44.             sDec = sDec & Mid$(sExpr, iPos, 1)
  45.             iPos = iPos + 1
  46.         Loop
  47.     End If
  48.     ' Kết hợp phần nguyên và phần thập phân (nếu có)
  49.     sValue = sInt & IIf(bIsInt, "", "." & sDec)
  50.     GetNumber = Val(sValue) ' Trả về giá trị Double
  51. End Function
  52.  
  53. ' Tính mức ưu tiên toán tử
  54. Private Function OprPrec(ByVal op As String) As Byte
  55.     Select Case op
  56.         Case "(", ")"
  57.             OprPrec = 0
  58.         Case "+", "-"
  59.             OprPrec = 1
  60.         Case "*", "/"
  61.             OprPrec = 2
  62.         Case "^"
  63.             OprPrec = 3
  64.         Case "u+", "u-" 'Toán tử đơn phân (unary plus, unary minus)
  65.             OprPrec = 4
  66.         Case Else
  67.     End Select
  68. End Function
  69.  
  70. ' 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)
  71. Private Sub Tokenise(ByVal sExpr As String, Tokens() As String)
  72.     Dim sResult(255) As String
  73.     Dim iToken As Integer
  74.     Dim i As Integer: i = 1
  75.    
  76.     ' Cuối biểu thức có một ký tự NULL để báo hiệu kết thúc
  77.     Do While Mid$(sExpr, i, 1) <> Chr$(0)
  78.         Select Case Mid$(sExpr, i, 1)
  79.             Case "0" To "9" 'Là số, dùng hàm GetNumber để lấy toàn bộ con số
  80.                 sResult(iToken) = GetNumber(sExpr, i)
  81.                 iToken = iToken + 1
  82.             Case "(", ")", "+", "-", "*", "/", "^" 'Là toán tử
  83.                 sResult(iToken) = Mid$(sExpr, i, 1)
  84.                 iToken = iToken + 1
  85.                 i = i + 1
  86.             Case Else 'Không hay rồi!
  87.                 MsgBox "Ky hieu khong hop le!", vbCritical, "Loi"
  88.                 GoTo ReturnValue
  89.         End Select
  90.     Loop
  91.    
  92. ReturnValue:
  93.     Tokens = sResult
  94.     ' Chỉ lấy đủ số cho đỡ tốn bộ nhớ
  95.     ReDim Preserve Tokens(iToken - 1) As String
  96. End Sub
  97.  
  98. ' Hàm quan trọng nhất
  99. Public Function ExprEval(ByVal sExpr As String) As Double
  100.     ' Nếu là chuỗi rỗng thì kết thúc, trả về 0
  101.     If Trim$(sExpr) = vbNullString Then
  102.         ExprEval = 0: Exit Function
  103.     End If
  104.    
  105.     ' Loại bỏ khoảng trắng dư thừa
  106.     sExpr = EliminateWhite(sExpr)
  107.  
  108.     ' Thêm ký tự NULL vào cuối biểu thức để báo kết thúc
  109.     sExpr = sExpr & Chr$(0)
  110.    
  111.     ' Hai ngăn xếp cần cho chuyển đổi
  112.     Dim rpnStack As New StringStack
  113.     Dim oprStack As New StringStack
  114.    
  115.     '##### Chuyển đổi #####
  116.  
  117.     ' Mảng chứa các token
  118.     Dim Tokens() As String
  119.    
  120.     ' Thiết lập mảng token
  121.     Tokenise sExpr, Tokens
  122.    
  123.     ' Toán tử tạm thời
  124.     Dim tmpOp As String
  125.    
  126.     Dim i As Integer
  127.     For i = 0 To UBound(Tokens)
  128.         Select Case Tokens(i)
  129.             Case "("
  130.                 oprStack.Push Tokens(i)
  131.             Case ")"
  132.                 If oprStack.Length > 0 Then
  133.                     Do While (oprStack.Length > 0 And oprStack.Peek <> "(")
  134.                         rpnStack.Push oprStack.Pop
  135.                     Loop
  136.                     If oprStack.Length > 0 Then
  137.                         oprStack.Pop
  138.                     End If
  139.                 End If
  140.             Case "+", "-"
  141.                 ' Phải xem đây là toán tử nhị phân hay đơn phân
  142.                 If i > 0 Then 'Nếu không phải token đầu tiên
  143.                     ' Nếu trước nó là toán tử, ngoại trừ ")"
  144.                     If IsOperator(Tokens(i - 1)) = True Then
  145.                         'Nó là dấu âm dương, thêm chữ u vào để phân biệt
  146.                         tmpOp = "u" & Tokens(i)
  147.                     Else 'Còn không thì nó là dấu cộng trừ bình thường
  148.                         tmpOp = Tokens(i)
  149.                     End If
  150.                 Else
  151.                     'Nếu là token đầu tiên, nó hẳn phải là toán tử đơn phân
  152.                     tmpOp = "u" & Tokens(i)
  153.                 End If
  154.                 If oprStack.Length > 0 Then
  155.                     Do While OprPrec(oprStack.Peek) >= OprPrec(tmpOp)
  156.                         rpnStack.Push oprStack.Pop
  157.                     Loop
  158.                     oprStack.Push tmpOp
  159.                 Else
  160.                     oprStack.Push tmpOp
  161.                 End If
  162.             Case "^", "*", "/"
  163.                 If oprStack.Length > 0 Then
  164.                     Do While OprPrec(oprStack.Peek) >= OprPrec(Tokens(i))
  165.                         rpnStack.Push oprStack.Pop
  166.                     Loop
  167.                     oprStack.Push Tokens(i)
  168.                 Else
  169.                     oprStack.Push Tokens(i)
  170.                 End If
  171.             Case Else
  172.                 rpnStack.Push Tokens(i)
  173.         End Select
  174.         tmpOp = ""
  175.     Next i
  176.    
  177.     ' Xả hết từ oprStack vào rpnStack
  178.     Do While oprStack.Length > 0
  179.         rpnStack.Push oprStack.Pop
  180.     Loop
  181.    
  182.     '##### Tính toán #####
  183.  
  184.     ' Phòng hờ "bất trắc"
  185.     On Error Resume Next
  186.    
  187.     ' Ngăn xếp kết quả
  188.     Dim ResultStack As New StringStack
  189.    
  190.     Dim j As Integer
  191.     For j = 0 To rpnStack.Length - 1
  192.         Dim a As Double, b As Double
  193.         Select Case rpnStack.GetAt(j)
  194.             Case "u+"
  195.                 'ResultStack.Push ResultStack.Pop
  196.             Case "u-"
  197.                 ResultStack.Push CStr(-Val(ResultStack.Pop))
  198.             Case "^"
  199.                 b = Val(ResultStack.Pop)
  200.                 a = Val(ResultStack.Pop)
  201.                 ResultStack.Push CStr(a ^ b)
  202.             Case "*"
  203.                 b = Val(ResultStack.Pop)
  204.                 a = Val(ResultStack.Pop)
  205.                 ResultStack.Push CStr(a * b)
  206.             Case "/"
  207.                 b = Val(ResultStack.Pop)
  208.                 a = Val(ResultStack.Pop)
  209.                 If b <> 0 Then
  210.                     ResultStack.Push CStr(a / b)
  211.                 Else
  212.                     MsgBox "Chia cho 0", vbCritical, "Loi"
  213.                     Exit Function
  214.                 End If
  215.             Case "+"
  216.                 b = Val(ResultStack.Pop)
  217.                 a = Val(ResultStack.Pop)
  218.                 ResultStack.Push CStr(a + b)
  219.             Case "-"
  220.                 b = Val(ResultStack.Pop)
  221.                 a = Val(ResultStack.Pop)
  222.                 ResultStack.Push CStr(a - b)
  223.             Case Else
  224.                 ResultStack.Push rpnStack.GetAt(j)
  225.         End Select
  226.     Next j
  227.    
  228. ReturnValue:
  229.     ExprEval = Val(ResultStack.Pop)
  230. End Function


Trong sự kiện click của cmdEvaluate, ta ghi:
  1. Private Sub cmdEvaluate_Click()
  2.     txtResult.Text = ExprEval(txtExpression.Text)
  3. 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.

Phần bổ sung
Sửa lần cuối bởi alexanderdna vào ngày T.Ba 19/01/2010 10:39 am với 4 lần sửa.



Hình đại diện của người dùng
truongphu
VIP
VIP
Bài viết: 4756
Ngày tham gia: CN 04/11/2007 10:57 am
Đến từ: Cam Đức, Khánh hòa
Has thanked: 14 time
Been thanked: 509 time

Re: Viết chương trình tính giá trị biểu thức

Gửi bàigửi bởi truongphu » T.Bảy 08/08/2009 2:28 pm

Bạn Alex thân mến (thế là mình lại gặp nhau)
Với những gì bạn viết ở trên, tôi thành thật thán phục bạn. Bạn có một cơ sở vững chắc về VB6 và việc tự viết các control cho riêng mình là chuyện trong tầm tay bạn. Tham gia CLB VB, chắc chắn bạn sẽ là một thành viên xuất sắc, và ngoài đời, bạn sẽ là một Lập trình viên vững vàng.

Tôi mới tiếp thu VB6 gần đây, chủ yếu là xử lý logic trên các ứng dụng sẵn có, nên đọc bài viết của bạn tôi học thêm được nhiều điều... đúng như phần tóm tắt bạn viết:
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


Tuy nhiên vì bạn có viết:
..Trong VB6 mặc nhiên không có kiểu dữ liệu ngăn xếp..

* Tôi lưu ý đến Alex đoạn code sau:

Mã: Chọn hết

  1. Dim stack As New Collection
  2. stack.Add 8
  3. stack.Add 7
  4. stack.Add 6
  5. stack.Add 5
  6. MsgBox stack.Item(stack.Count)
  7. stack.Remove (stack.Count)
  8. MsgBox stack.Item(stack.Count)
  9. stack.Remove (1)
  10. MsgBox stack.Item(1)
  11. MsgBox stack.Count


Như thế với dòng code:

Mã: Chọn hết

  1. MsgBox stack.Item(stack.Count)

thì tương đương với Function Peek của Alex

Câu trên, kết hợp với câu:

Mã: Chọn hết

  1. stack.Remove (stack.Count)

thì tương đương với Function Pop của Alex

và:

Mã: Chọn hết

  1. MsgBox stack.Count

thì tương đương với Property Length của Alex

Ngoài ra, Collection hơn cả ngăn xếp:

Mã: Chọn hết

  1. stack.Remove (1)

= Xóa dữ liệu trên cùng (hay bất cứ một phần tử nào trong Collection)

Mã: Chọn hết

  1. MsgBox stack.Item(i)

Lấy dữ liệu phần tử bất kỳ thứ i mà i<=stack.Count

* và đoạn code sau:

Mã: Chọn hết

  1. Private Declare Function EbExecuteLine Lib "vba6.dll" (ByVal pStringToExec As Long, ByVal Unknownn1 As Long, ByVal Unknownn2 As Long, ByVal fCheckOnly As Long) As Long
  2.  
  3. Public Function ExecuteLine(sCode As String, Optional fCheckOnly As Boolean) As Boolean
  4.    ExecuteLine = EbExecuteLine(StrPtr(sCode), 0&, 0&, Abs(fCheckOnly)) = 0
  5. End Function
  6.  
  7. Private Sub Command1_Click()
  8. ExecuteLine "var=" & "((1 + 2) * 4) + 3" & ":MsgBox Var"
  9. End Sub

thì các dòng code trên, với hàm API EbExecuteLine đã hoàn toàn thỏa bài viết của bạn

Một lần nữa, tôi đánh giá cao bài viết của bạn
Thân
o0o--truongphu--o0o

.........
Ghé thăm:
Chuyện Linh Tinh

Hình đại diện của người dùng
alexanderdna
Guru
Guru
Bài viết: 214
Ngày tham gia: T.Ba 14/07/2009 11:13 am
Đến từ: Sài Gòn
Has thanked: 3 time
Been thanked: 15 time

Re: Viết chương trình tính giá trị biểu thức

Gửi bàigửi bởi alexanderdna » T.Hai 10/08/2009 8:52 am

Gửi bác truongphu
Cám ơn bác đã quá khen. Con chỉ e mình múa rìu qua mắt thợ (vì hẳn nhiều cao thủ của CLB cũng rành các kỹ thuật này). Song, con vẫn viết bài này vì: 1. Để hướng dẫn các anh em chưa rành, 2. Để đóng góp cho diễn dàn thêm phong phú. Thiệt sự kinh nghiệm và kiến thức của con cũng chưa thực nhiều lắm, cần học hỏi thêm ở các bậc tiền bối.
- Về Collection: con đồng ý với bác là Collection tiện lợi hơn Stack, nhưng con cũng muốn trình bày vài ý:
a. Con định giới thiệu về cấu trúc dữ liệu ngăn xếp để mọi người cùng biết.
b. Collection chứa các phần tử có kiểu dữ liệu tổng quát Variant, trong khi Stack có thể chứa kiểu dữ liệu chuyên biệt (như Integer, String, Single, tùy theo nhu cầu) nên có thể Collection chiếm nhiều không gian bộ nhớ hơn Stack.
c. Mã lệnh Pop của Stack hẳn mau lẹ hơn 2 dòng mã lệnh tương đương của Collection.
- Về hàm API EbExecuteLine: đây là một hàm rất hay. Tuy nhiên nó có một nhược điểm là không dễ dàng đưa kết quả ra ngoài. Thí dụ:

Mã: Chọn hết

  1. Dim a As Integer
  2. ExecuteLine "a=6"
  3. MsgBox a

Khi hộp thông báo hiện ra, thì kết quả vẫn là 0 như giá trị tiền thiết lập của biến a, mà không bị thay đổi bởi hàm EbExecuteLine.
Có thể con chưa hiểu sâu về hàm này, mong bác chỉ dẫn rõ hơn.

Gửi phanphan
Hàm API EbExecuteLine chỉ thực thi một dòng lệnh, do đó phải để phát biểu gán và hàm MsgBox cùng một hàng, phân cách bằng dấu hai chấm. Nếu bạn ghi code như sau:

Mã: Chọn hết

  1. ExecuteLine "var=1+2" & vbCrLf & "MsgBox var"

Thì cũng không được. Còn nếu bạn ghi:

Mã: Chọn hết

  1. ExecuteLine "var=1+2"
  2. MsgBox var

Thì có thể bạn sẽ gặp lỗi Variable not defined vì thực chất biến var chỉ tồn tại khi thực thi hàm EbExecuteLine và bị xóa khỏi bộ nhớ ngay sau khi hàm này kết thúc.

Hình đại diện của người dùng
alexanderdna
Guru
Guru
Bài viết: 214
Ngày tham gia: T.Ba 14/07/2009 11:13 am
Đến từ: Sài Gòn
Has thanked: 3 time
Been thanked: 15 time

Re: Viết chương trình tính giá trị biểu thức

Gửi bàigửi bởi alexanderdna » T.Tư 12/08/2009 9:49 am

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ó nào đó của VB6 để thay thế? Câu trả lời là CÓ và đã được bác truongphu nhắc tới. Sau đây, AlexDNA xin gửi đến quý vị phương pháp dùng kiểu dữ liệu Collection (tập hợp).

I. Phương 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ị 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:

Mã: Chọn hết

  1. ' Hàm mới: ExprEvalNoStack
  2. ' Tương tự hàm cũ, tuy nhiên không dùng ngăn xếp
  3. Public Function ExprEvalNoStack(ByVal sExpr As String) As Double
  4.     If Trim$(sExpr) = vbNullString Then
  5.         ExprEvalNoStack = 0: Exit Function
  6.     End If
  7.    
  8.     sExpr = EliminateWhite(sExpr)
  9.  
  10.     sExpr = sExpr & Chr$(0)
  11.    
  12.     Dim rpnStack As New Collection 'Thay đổi
  13.     Dim oprStack As New Collection 'Thay đổi
  14.    
  15.     '##### Chuyen doi #####
  16.    
  17.     Dim Tokens() As String
  18.    
  19.     Tokenise sExpr, Tokens
  20.    
  21.     Dim tmpOp As String
  22.    
  23.     Dim i As Integer
  24.     For i = 0 To UBound(Tokens)
  25.         Select Case Tokens(i)
  26.             Case "("
  27.                 Push oprStack, Tokens(i)
  28.             Case ")"
  29.                 If oprStack.Count > 0 Then
  30.                     Do While (oprStack.Count > 0 And Peek(oprStack) <> "(")
  31.                         Push rpnStack, Pop(oprStack)
  32.                     Loop
  33.                     If oprStack.Count > 0 Then
  34.                         Pop oprStack
  35.                     End If
  36.                 End If
  37.             Case "+", "-"
  38.                 If i > 0 Then
  39.                     If IsOperator(Tokens(i - 1)) = True Then
  40.                         tmpOp = "u" & Tokens(i)
  41.                     Else
  42.                         tmpOp = Tokens(i)
  43.                     End If
  44.                 Else
  45.                     tmpOp = "u" & Tokens(i)
  46.                 End If
  47.                 If oprStack.Count > 0 Then
  48.                     Do While OprPrec(Peek(oprStack)) > OprPrec(tmpOp)
  49.                         Push rpnStack, Pop(oprStack)
  50.                     Loop
  51.                     Push oprStack, tmpOp
  52.                 Else
  53.                     Push oprStack, tmpOp
  54.                 End If
  55.             Case "^", "*", "/"
  56.                 If oprStack.Count > 0 Then
  57.                     Do While OprPrec(Peek(oprStack)) > OprPrec(Tokens(i))
  58.                         Push rpnStack, Pop(oprStack)
  59.                     Loop
  60.                     Push oprStack, Tokens(i)
  61.                 Else
  62.                     Push oprStack, Tokens(i)
  63.                 End If
  64.             Case Else
  65.                 Push rpnStack, Tokens(i)
  66.         End Select
  67.         tmpOp = ""
  68.     Next i
  69.    
  70.     Do While oprStack.Count > 0
  71.         Push rpnStack, Pop(oprStack)
  72.     Loop
  73.    
  74.     '##### Tinh toan #####
  75.    
  76.     On Error Resume Next
  77.    
  78.     Dim ResultStack As New Collection
  79.    
  80.     Dim j As Integer
  81.     For j = 1 To rpnStack.Count
  82.         Dim a As Double, b As Double
  83.         Select Case rpnStack.Item(j)
  84.             Case "u+"
  85.             Case "u-"
  86.                 Push ResultStack, CStr(-Val(Pop(ResultStack)))
  87.             Case "^"
  88.                 b = Val(Pop(ResultStack))
  89.                 a = Val(Pop(ResultStack))
  90.                 Push ResultStack, CStr(a ^ b)
  91.             Case "*"
  92.                 b = Val(Pop(ResultStack))
  93.                 a = Val(Pop(ResultStack))
  94.                 Push ResultStack, CStr(a * b)
  95.             Case "/"
  96.                 b = Val(Pop(ResultStack))
  97.                 a = Val(Pop(ResultStack))
  98.                 If b <> 0 Then
  99.                     Push ResultStack, CStr(a / b)
  100.                 Else
  101.                     MsgBox "Chia cho 0", vbCritical, "Loi"
  102.                     Exit Function
  103.                 End If
  104.             Case "+"
  105.                 b = Val(Pop(ResultStack))
  106.                 a = Val(Pop(ResultStack))
  107.                 Push ResultStack, CStr(a + b)
  108.             Case "-"
  109.                 b = Val(Pop(ResultStack))
  110.                 a = Val(Pop(ResultStack))
  111.                 Push ResultStack, CStr(a - b)
  112.             Case Else
  113.                 Push ResultStack, rpnStack.Item(j)
  114.         End Select
  115.     Next j
  116.    
  117. ReturnValue:
  118.     ExprEvalNoStack = Val(Pop(ResultStack))
  119. End Function
  120.  
  121. ' Ba hàm cần thiết để dùng Collection thay cho ngăn xếp
  122. Private Sub Push(ByRef c As Collection, ByVal value As String)
  123.     c.Add value
  124. End Sub
  125.  
  126. Private Function Pop(ByRef c As Collection) As String
  127.     Pop = c.Item(c.Count)
  128.     c.Remove c.Count
  129. End Function
  130.  
  131. Private Function Peek(ByVal c As Collection) As 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.

QuangHoa
Guru
Guru
Bài viết: 542
Ngày tham gia: T.Năm 27/03/2008 9:02 am
Đến từ: Quê hương Đại tướng Võ Nguyên Giáp
Been thanked: 5 time
Liên hệ:

Re: Viết chương trình tính giá trị biểu thức

Gửi bàigửi bởi QuangHoa » T.Năm 01/10/2009 11:13 am

Ngoài lề chút nhé, mình hồi trước cũng viết một chương trình chạy bằng C để tính giá tính giá trị biểu thức. Có thể tính các phép tính cơ bản +-*/^(lũy thừa)! (giai thừa) sin, cos, tan, asin, acos, atan, log, ln, exp chạy tương đối ổn. Viết bằng VB thì tiện lợi hơn C ở chổ là xử lý chuổi dễ hơn, có nhiều kiểu dữ kiệu khác nhau để lưu trữ và cũng dễ kiểm soát lỗi nên tất nhiên khi trình bày cho người khác cũng có nhiều cái thuận lợi. Hi vọng các bài viết của tác giả Topic đủ làm các bạn hiểu phương pháp tính toán và cũng có thể tự chế tác cho phù hợp với mục đích của mình.
tải về
MAIN.rar
Đây là file exe và source, chạy trên Turbo C
(25.47 KiB) Đã tải 544 lần

Code

Mã: Chọn hết

  1. #include<stdio.h>
  2. #include<conio.h>
  3. #include<math.h>
  4. #include<ctype.h>
  5. #include<string.h>
  6. #include<stdlib.h>
  7. float number[200]={0};
  8. int n=0;
  9. /* chuyen sang dang hau to*/
  10. char st[100];
  11. /*Ham cho queue Char*/
  12. char queueChar[200]={'@'};int first=0,last=0;
  13. int isEmptyQueueChar(){return last==0;};
  14. void emptyQueueChar(){last=first=0;}
  15. void baoloi(char *x){
  16.     clrscr();
  17.     printf(" Khong the tinh %s\n",st);
  18.     puts(x);
  19.     getch();
  20.     abort();
  21. }
  22. // Ham dung cho queue, queue la ket qua cua qua trinh phan tich sang hau to
  23. void pushQueueChar(char x){
  24.       last++;
  25.       queueChar[last]=x;
  26. }
  27. char getQueueChar(){
  28.       last++;
  29.       return queueChar[last-1];
  30. }
  31. /*====================================*/
  32. // Stack dung de lam bo nho tam, phuc vu cho qua trinh phan tich sang hau to
  33. char stack[200],top=0;
  34. void empty(){    // Khoi tao stack
  35.      top=0;
  36. }
  37. int isemptyStack(){  //Kiem tra stack co rong
  38.     return top==0;
  39. }
  40. void pushStack(char x){  //Dua mot ky tu vao stack
  41.      top+=1;
  42.      stack[top]=x;
  43. }
  44. char popStack(){  // Lay ky tu ra
  45.      top-=1;
  46.      return stack[top+1];
  47. }
  48. char getStack(){                 // xem ky tu dinh
  49.      return stack[top];
  50. }
  51. int priority(char x){// Tinh muc uu tien
  52.     if(strchr("!~SCTPELaA",toupper(x)))return 4;//Cac phep toan 1 hang tu uu tien cao nhat
  53.     if(strchr("*/^%&|\\",toupper(x)))return 3;
  54.     if(x=='+' || x=='-')return 2;
  55.     if(x=='(' || x==')')return 1;
  56.     if(x>='0' && x<='9')return 0;//Cac so co muc uu tien thap nhat
  57.     return -1;
  58. }
  59. //Ham lay 1 so tu chuoi
  60. // vi du 12+36, so o vi tri tu 3~4 la 36
  61. float getNumber(int b,int e){
  62.      int i;
  63.      char x[20]={0};
  64.      for(i=b;i<=e;i++)x[i-b]=st[i];
  65.      return atof(x);//ham co san, chuyen chuoi sang so
  66. }
  67. //Ham nay chuyen st la phep tinh dang trung to (kieu viet tu nhien)
  68. //Sang dang hau to : la dang de tinh toan
  69. // Vi du 1+2 chuyen thanh 1 2 +
  70. void Convert(){
  71.      empty();
  72.      emptyQueueChar();last=0;
  73.      int i=0,j=0;
  74.      char x;
  75.      do{
  76.       switch(priority(st[i])){
  77.          case 0://So hang, cac chu so
  78.           j=i+1;
  79.           while((isdigit(st[j]) || st[j]=='.'))j++;
  80.           //Tim ky tu ko phai la so
  81.           number[n]=(getNumber(i,j-1)); //lay gia tri so o day
  82.           pushQueueChar(-n);//Dua vao queueChar
  83.           n++;
  84.           i=j-1;
  85.           break;
  86.           case 1: // La dau mo/dong ngoac ( hoac )
  87.         if(st[i]=='(')pushStack('(');
  88.         else
  89.             do{
  90.             x=popStack();
  91.             if(x!='(') pushQueueChar(x);
  92.             }while(x!='(');
  93.             break;
  94.           case 2://phep toan 2 hang tu +,-   Co muc uu tien thap
  95.           case 3://Phep toan 1 hang tu */^...Muc uu tien cao
  96.            while(!isemptyStack() && (priority(st[i])<=priority(getStack())))
  97.             //UU tien dua */&^ truoc +- vi */ co uu tien cao hon +-
  98.             pushQueueChar(popStack());
  99.            pushStack(st[i]);
  100.            break;
  101.           case 4://cac toan tu 1 hang tu, nhu sin, cos, tan, cac hang so
  102.            switch(st[i]){
  103.            case 'c':
  104.            case 'C'://Xem co phai la COS khong
  105.             if((toupper(st[i+1])=='O')&&(toupper(st[i+2])=='S')){
  106.                 pushStack('c');
  107.                 i+=2;
  108.             }else baoloi("Syntax error : ham khong duoc khai bao !");
  109.             break;
  110.            case 's':
  111.            case 'S'://Xem co phai la Sin ko
  112.             if((toupper(st[i+1])=='I')&&(toupper(st[i+2])=='N')){
  113.                 pushStack('s');
  114.                 i+=2;
  115.             }else baoloi("Syntax error : ham khong duoc khai bao !");
  116.             break;
  117.            case 't':
  118.            case 'T': //Co phai la tan
  119.             if((toupper(st[i+1])=='A')&&(toupper(st[i+2])=='N')){
  120.                 pushStack('t');
  121.                 i+=2;
  122.             }else baoloi("Syntax error : ham khong duoc khai bao !");
  123.             break;
  124.            case 'l':
  125.            case 'L'://Co the la Log, ln
  126.             if((toupper(st[i+1])=='O')&&(toupper(st[i+2])=='G')){
  127.                 pushStack('l');
  128.                 i+=2;
  129.             }else if((toupper(st[i+1])=='N')){
  130.                 pushStack('L');
  131.                 i+=1;
  132.             }else baoloi("Syntax error : ham khong duoc khai bao !");
  133.             break;
  134.            case 'e':
  135.            case 'E'://neu ko phai la EXP thi la e=2.71828
  136.             if((toupper(st[i+1])=='X')&&(toupper(st[i+2])=='P')){
  137.                 pushStack('e');
  138.                 i+=2;
  139.             }else {
  140.                 i++;
  141.                 number[n]=M_E;
  142.                 pushQueueChar(-n);
  143.                 n++;
  144.             }break;
  145.            case 'p':
  146.            case 'P'://Co phai la PI
  147.              if((toupper(st[i+1])=='I')){
  148.                     i++;
  149.                 number[n]=M_PI;
  150.                 pushQueueChar(-n);
  151.                 n++;
  152.             }break;
  153.            case 'a':
  154.            case 'A':
  155.             switch(st[i+1]){
  156.                 case 'c':
  157.                 case 'C'://Xem co phai la COS khong
  158.                     if((toupper(st[i+2])=='O')&&(toupper(st[i+3])=='S')){
  159.                         pushStack('C');
  160.                         i+=3;
  161.                     }else baoloi("Syntax error : ham khong duoc khai bao !");
  162.                     break;
  163.                  case 's':
  164.                  case 'S'://Xem co phai la Sin ko
  165.                     if((toupper(st[i+2])=='I')&&(toupper(st[i+3])=='N')){
  166.                         pushStack('S');
  167.                         i+=3;
  168.                     }else baoloi("Syntax error : ham khong duoc khai bao !");
  169.                     break;
  170.                  case 't':
  171.                  case 'T': //Co phai la tan
  172.                     if((toupper(st[i+2])=='A')&&(toupper(st[i+3])=='N')){
  173.                         pushStack('T');
  174.                         i+=3;
  175.                     }else baoloi("Syntax error : ham khong duoc khai bao !");
  176.                     break;
  177.             }
  178.             break;
  179.            case '!':pushQueueChar('!');break;
  180.            case '~':pushQueueChar('~');break;
  181.            }break;
  182.  
  183.       }
  184.       i++;
  185.      }while(i<strlen(st));
  186.      while(!isemptyStack())pushQueueChar(popStack());
  187. }
  188. /*===========================================================*/
  189. //Ham phuc vu bao loi, tranh cac loi nhu log(0)
  190. int matherr (struct exception *a)
  191. {
  192.     if (a->type == DOMAIN)
  193.     baoloi("Loi tham so");
  194.     else baoloi(strcat("Math error : Loi khi tinh ham ",a->name));
  195.   return 0;
  196. }
  197. float giaithua(float x){
  198.     float t=1,i=1;
  199.     if(x!=(int)x)baoloi("Math error : Loi khi tinh giai thua cua so thuc");
  200.     else if(x>33)baoloi("Math error : So qua lon khi tinh giai thua");
  201.     for(;i<=x;i++)t*=i;
  202.     return t;
  203. }
  204. //cac phep toan 1 toan tu nhu sin, cos, tan, giai thua
  205. float tinh1(float x,char pt){
  206.     switch(pt){
  207.         case 's':return sin(x);
  208.         case 'S':return asin(x);
  209.         case 'c':return cos(x);
  210.         case 'C':return acos(x);
  211.         case 't':return tan(x);
  212.         case 'T':return atan(x);
  213.         case '~':return ~((int)x);
  214.         case '!':return giaithua(x);
  215.         case 'l':return log10(x);
  216.         case 'L':return log(x);
  217.         case 'e':return exp(x);
  218.     };
  219.     return 0;
  220. }
  221. //Phep toan 2 hang tu nhu +, -, *, /, ^ ....
  222. float tinh2(float x,float y, char pt){
  223.     switch(pt){
  224.        case '+':return x+y;
  225.        case '-':return x-y;
  226.        case '*':return x*y;
  227.        case '%':return ((int)x)%((int)y);
  228.        case '\\':return (int)x /(int) y;
  229.        case '&':return (int)x&&(int) y;
  230.        case '|':return (int)x ||(int) y;
  231.        case '/':
  232.         if(y==0)baoloi("Loi chia cho 0");
  233.         return x/y;
  234.        case '^':return pow(x,y);
  235.     }
  236.     return 0;
  237. }
  238. float StackTinh[200]={0};int topStackTinh=0;
  239. void pushStackTinh(float x){topStackTinh++;StackTinh[topStackTinh]=x;}
  240. float getStackTinh(){topStackTinh--;return StackTinh[topStackTinh+1];}
  241. float tinhtoan(){
  242.     int i;
  243.     float a,b,c;
  244.     for(i=1;i<=last;i++)
  245.      if(queueChar[i]>0){
  246.     if(priority(queueChar[i])<=3){
  247.         //neu la phep toan 2 hang tu nhu +,-,*,/,^ , priority=3
  248.         //lay ra 2 so gan nhat roi tinh toan, thay gia tri vao
  249.         if(topStackTinh<2)baoloi(0);
  250.         b=getStackTinh();
  251.         a=getStackTinh();
  252.         c=tinh2(a,b,queueChar[i]);
  253.         pushStackTinh(c);
  254.      }else{
  255.         //Neu la phep toan 1 hang tu, priority=4
  256.         //Lay ra 1 so va tinh toan, the vao
  257.         if(topStackTinh<1)baoloi(0);
  258.         a=getStackTinh();
  259.         c=tinh1(a,queueChar[i]);
  260.         pushStackTinh(c);
  261.      }
  262.      }else{
  263.     //Neu la 1 so thi dua vao stack
  264.     c=number[-queueChar[i]];
  265.     pushStackTinh(c);
  266.      }
  267.      return c;
  268. }
  269. void xuat_hau_to(){
  270.     int i;
  271.     for(i=1;i<=last;i++)
  272.     if(queueChar[i]>0)printf("%c",queueChar[i]);
  273.     else printf("{%g}",number[-queueChar[i]]);
  274. }
  275. int main(){
  276.     clrscr();
  277.     printf("\t Tinh gia tri bieu thuc\n Nhap bieu thuc nhu binh thuong\n");
  278.     printf(" Cac phep toan +-*/%!|^\\\n");
  279.     printf(" Cac ham sin,asin,cos,acos,tan,atan,log,ln,exp\n");
  280.     printf(" Cac hang pi=3.1415926535, e=2.718281828\n");
  281.     printf("\n\n\n\n  St = ");gets(st); //Doc phep toan
  282.     Convert(); //Chuyen sang hau to : 12+34*56 --> 12 34 56 * +
  283.     printf("\n Dang hau to : ");
  284.     xuat_hau_to();printf("\n");
  285.     printf("\n\n\n\n  %s = %g",st,tinhtoan());
  286.     getch();
  287.     return 0;
  288. }
  289.  

khoaph
Thành viên chính thức
Thành viên chính thức
Bài viết: 34
Ngày tham gia: T.Ba 08/12/2015 12:57 pm
Been thanked: 4 time

Re: Viết chương trình tính giá trị biểu thức

Gửi bàigửi bởi khoaph » T.Năm 18/02/2016 12:11 pm

bạn đã có cải tiến thuật toán Ký pháp Ba Lan khi thêm vào toán tử đơn phân nhưng nó lại chạy không chính xác khi có nhiều dấu + - liên tiếp nhau, cụ thể là 2 dấu sau "(" hoặc ký tự đầu tiên, hoặc 3 dấu ở những chỗ khác
ví dụ nếu là "--3+(--4+5)" thì nó cho ra biểu thức nghịch đảo là "u- 3 u- u- 4 u- 5 + + "
và trong cái OprPrec, cái case "(",")" là không cần thiết
chắc có lẽ ta trước chuyển về ký pháp Ba Lan ta phải thay thế tất cả các dãy + - liên tiếp nhau thành 1 dấu + hoặc - duy nhất
sau đây là cải tiến của tôi, có thêm phần xử lí các hàm sin cos hay các hàm tự tạo có nhiều tham số
code viết bằng VB Net
  1. Imports System.Text.RegularExpressions
  2. Public Class Form1
  3.     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
  4.         Dim bBttt As BieuThucTrungTo = New BieuThucTrungTo(TextBox1.Text)
  5.         Dim bNX As NganXep = bBttt.Parse
  6.         Dim bS As String
  7.         For Each item As String In bNX
  8.             bS += item + " "
  9.         Next
  10.         TextBox2.Text = bS
  11.     End Sub
  12. End Class
  13. Public Class BieuThucTrungTo
  14.     Private trBieuThuc As String
  15.     Private trPos As Integer = -1
  16.     Private trRegExp As Regex
  17.     Private trOperators As String()
  18.     Public Sub New(ByVal tsBieuThuc As String)
  19.         trBieuThuc = tsBieuThuc
  20.         trBieuThuc = trBieuThuc.Replace(" ", "")
  21.         trBieuThuc = trBieuThuc.Replace(vbTab, "")
  22.         trBieuThuc = trBieuThuc.Replace(vbNewLine, "")
  23.         trRegExp = New Regex("([a-zA-Z][a-zA-Z0-9]*\()|([a-zA-Z][a-zA-Z0-9]*)|\(|\)|([0-9]+)|[,\+\-\*\^/]")
  24.         If trBieuThuc <> "" Then
  25.             trPos = 0
  26.         Else
  27.             Throw New Exception("Biểu thức không được là chuỗi rỗng")
  28.         End If
  29.         trOperators = New String(11) {"+", "-", "*", "/", "^", "(", "sin(", "cos(", "tan(", "cotan(", "cong3so(", ","}
  30.     End Sub
  31.     Private Function IsOperator(ByVal tsToken As String) As Boolean
  32.         Return Array.IndexOf(trOperators, tsToken) > -1
  33.     End Function
  34.     Public Function Parse() As NganXep
  35.         'Dim bKetQua As List(Of String) = New List(Of String)
  36.         Dim bToken As String = NextToken()
  37.         Dim bPreviousToken As String = ""
  38.         Dim bBaLan As NganXep = New NganXep
  39.         Dim bOperators As NganXep = New NganXep
  40.         Dim bIsFirstToken As Boolean = True
  41.         Dim bTempOp As String
  42.         Do While Not bToken = ""
  43.             Select Case bToken
  44.                 Case "(", "sin(", "cos(", "tan(", "cotan(", "cong3so("
  45.                     bOperators.Add(bToken)
  46.                 Case ","
  47.                     If bOperators.Count > 0 Then
  48.                         Do While bOperators.Count > 0
  49.                             If bOperators.Peek(bOperators.Peek.Length - 1) <> "(" Then
  50.                                 bBaLan.Push(bOperators.Pop)
  51.                             Else
  52.                                 Exit Do
  53.                             End If
  54.                         Loop
  55.                         If bOperators.Count <= 0 Then
  56.                             Throw New Exception("Biểu thức không hợp lệ")
  57.                         End If
  58.                     End If
  59.                 Case ")"
  60.                     If bOperators.Count > 0 Then
  61.                         Do While bOperators.Count > 0
  62.                             If bOperators.Peek(bOperators.Peek.Length - 1) <> "(" Then
  63.                                 bBaLan.Push(bOperators.Pop)
  64.                             Else
  65.                                 Exit Do
  66.                             End If
  67.                         Loop
  68.                         If bOperators.Count > 0 Then
  69.                             If bOperators.Peek.Length > 1 Then
  70.                                 bBaLan.Push(bOperators.Peek.Substring(0, bOperators.Peek.Length - 1))
  71.                             End If
  72.                             bOperators.Pop()
  73.                         End If
  74.                     End If
  75.                 Case "+", "-"
  76.                     If bIsFirstToken Then
  77.                         bTempOp = "u" + bToken
  78.                     Else
  79.                         If IsOperator(bPreviousToken) Then
  80.                             bTempOp = "u" + bToken
  81.                         Else
  82.                             bTempOp = bToken
  83.                         End If
  84.                     End If
  85.                     If bOperators.Count > 0 Then
  86.                         Do While OprPrec(bOperators.Peek) >= OprPrec(bTempOp)
  87.                             bBaLan.Push(bOperators.Pop)
  88.                         Loop
  89.                     End If
  90.                     bOperators.Push(bTempOp)
  91.                 Case "*", "/", "^"
  92.                     If bOperators.Count > 0 Then
  93.                         Do While OprPrec(bOperators.Peek) >= OprPrec(bToken)
  94.                             bBaLan.Push(bOperators.Pop)
  95.                         Loop
  96.                     End If
  97.                     bOperators.Push(bToken)
  98.                 Case Else
  99.                     bBaLan.Push(bToken)
  100.             End Select
  101.             If bIsFirstToken Then
  102.                 bIsFirstToken = False
  103.             End If
  104.             bPreviousToken = bToken
  105.             bToken = NextToken()
  106.             bTempOp = ""
  107.         Loop
  108.         Do While bOperators.Count > 0
  109.             bBaLan.Push(bOperators.Pop)
  110.         Loop
  111.         Return bBaLan
  112.     End Function
  113.     Private Function OprPrec(ByVal op As String) As Byte
  114.         Select Case op
  115.             'Case "(", "sin(", "cos(", "tan(", "cotan("
  116.             'OprPrec = 0
  117.             Case "+", "-"
  118.                 OprPrec = 1
  119.             Case "*", "/"
  120.                 OprPrec = 2
  121.             Case "^"
  122.                 OprPrec = 3
  123.             Case "u+", "u-" 'Toán tử đơn phân (unary plus, unary minus)
  124.                 OprPrec = 4
  125.             Case Else
  126.         End Select
  127.     End Function
  128.     Public Function NextToken() As String
  129.         If trPos < trBieuThuc.Length Then
  130.             Dim bMatch As Match = trRegExp.Match(trBieuThuc, trPos)
  131.             trPos += bMatch.Value.Length
  132.             Return bMatch.Value
  133.         Else
  134.             Return ""
  135.         End If
  136.     End Function
  137. End Class
  138. Public Class NganXep : Inherits List(Of String)
  139.     Public Sub New()
  140.         MyBase.New()
  141.     End Sub
  142.     Public Sub Push(ByVal tsS As String)
  143.         Add(tsS)
  144.     End Sub
  145.     Public Function Pop() As String
  146.         If Count > 0 Then
  147.             Dim bS As String
  148.             bS = Me(Count - 1)
  149.             Me.RemoveAt(Count - 1)
  150.             Return bS
  151.         Else
  152.             Return ""
  153.         End If
  154.     End Function
  155.     Public Function Peek() As String
  156.         If Count > 0 Then
  157.             Dim bS As String
  158.             bS = Me(Count - 1)
  159.             Return bS
  160.         Else
  161.             Return ""
  162.         End If
  163.     End Function
  164.     Public Function GetAt(ByVal tsChiSo As Integer) As String
  165.         Return Me(tsChiSo)
  166.     End Function
  167. End Class

thêm 2 textbox và 1 button vào form, textbox1 để nhập input,textbox2 hiện kết quả
KyPhapBaLan.JPG
KyPhapBaLan.JPG (18.37 KiB) Đã xem 2584 lần

đã tìm ra cách khắc phục lỗi toán tử đơn phân, trong cái case
[vbnet]Case "+", "-"
If bIsFirstToken Then
bTempOp = "u" + bToken
Else
If IsOperator(bPreviousToken) Then
bTempOp = "u" + bToken
Else
bTempOp = bToken
End If
End If
If bOperators.Count > 0 Then
Do While OprPrec(bOperators.Peek) >= OprPrec(bTempOp)'<-----------------------Sửa dấu ">=" thành dấu ">" sẽ khắc phục lỗi nói trên, kết quả sẽ khác trước nhưng vẫn đúng
bBaLan.Push(bOperators.Pop)
Loop
End If
bOperators.Push(bTempOp)[/vbnet]
nhưng đằng nào cũng phải thay thế dãy các toán tử đơn phân liên tiếp thành 1 dấu duy nhất, hi hi

khoaph
Thành viên chính thức
Thành viên chính thức
Bài viết: 34
Ngày tham gia: T.Ba 08/12/2015 12:57 pm
Been thanked: 4 time

Re: Viết chương trình tính giá trị biểu thức

Gửi bàigửi bởi khoaph » T.Năm 18/02/2016 2:22 pm

mình đã sai lầm khi khắc phục lỗi phép +- đơn phân bằng cách đổi dấu ">=" thành ">"
làm như vậy sẽ làm cho việc tính toán + - (có cùng ưu tiên) diễn ra từ bên phải sang trái, mà như vậy cho kết quả sai, cho nên không có chuyện "kết quả sẽ khác trước nhưng vẫn đúng"
còn dòng case mà mình nói không cần thiết, không biết trong VB nó có không cần thiết không nhỉ, chứ mình chạy vb Net mình thử lấy giá trị OprPrec("(") thì nó cho kết quả là 0


Quay về “[VB] Bài viết hướng dẫn”

Đang trực tuyến

Đang xem chuyên mục này: Không có thành viên nào trực tuyến.1 khách