Bằng cách nào để không dùng GoTo? (1 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

Hoàng Trọng Nghĩa

Chuyên gia GPE
Thành viên BQT
Moderator
Tham gia
17/8/08
Bài viết
8,662
Được thích
16,725
Giới tính
Nam
Tôi có 2 mảng chuỗi (có thể là chuỗi số, có thể là chuỗi), một mảng chứa phần tử bị hủy và một mảng chứa các phần tử hủy.

Phần tử trong mảng hủy phải tồn tại trong mảng bị hủy, dù sắp xếp không trật tự, dù có trùng nhau và mỗi một phần tử hủy chỉ hủy đúng 1 phần tử ở mảng bị hủy.

Nhằm tiết kiệm số lần lặp của 2 mảng tôi dùng GoTo để "đi tắt".

Code như thế này:

Mã:
Option Explicit


Sub XulyChuoi()


    Dim ArrChuoiHuy(), ArrChuoiBiHuy(), ArrConLai()
    Dim i As Long, j As Long, n As Long, uHuy As Long, uBiHuy As Long
    
    ArrChuoiHuy = Array(2, 4, 9, 3, 1, 6, 8, 10, 3)
    ArrChuoiBiHuy = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 13, 14, 15, 16, 17, 18, 19, 20)
    
    uHuy = UBound(ArrChuoiHuy): uBiHuy = UBound(ArrChuoiBiHuy)
    
    For i = 0 To uHuy
        For j = 0 To uBiHuy
            If ArrChuoiHuy(i) = ArrChuoiBiHuy(j) Then
                ArrChuoiBiHuy(j) = ""
[COLOR=#ff0000][B]                GoTo Next_i[/B][/COLOR]
            End If
        Next
[COLOR=#ff0000][B]Next_i:[/B][/COLOR]
    Next
    
    For i = 0 To uBiHuy
        If ArrChuoiBiHuy(i) > "" Then
          ReDim Preserve ArrConLai(0 To n)
          ArrConLai(n) = ArrChuoiBiHuy(i)
          n = n + 1
        End If
    Next
    
    MsgBox Join(ArrConLai, ",")


End Sub

Dĩ nhiên 2 mảng trên chỉ là tượng trưng, thực tế nó đồ sộ hơn rất nhiều nhiều lần.

Vậy nếu không dùng GOTO, ta có thể tiết kiệm được số lần lặp ở vòng lặp For j hay không? Tôi nghĩ mãi mà chưa ra, chắc đầu óc bị mụ rồi!

Nhờ các bạn hướng dẫn dùm ạ.

Cám ơn rất nhiều!

----------------------------------------
P/s: Xin vui lòng đừng hướng dẫn tôi dùng Dictionary nhé, vì dữ liệu còn lại có trùng, còn Dic. thì không trùng, vã lại tôi cũng đang cố gắng tìm hướng khác mà không dùng Dic.
 
Lần chỉnh sửa cuối:
Ngay chỗ GoTo thay bằng Exit For
 
Upvote 0
Upvote 0
Anh gán
Mã:
 j = uBiHuy
thay thế Goto xem có được không.
 
Upvote 0
Tôi đã thử, Exit For tại chỗ đó chỉ thoát ra khỏi vòng lặp chứa nó.
 
Upvote 0
Hay là thế này đại ca
PHP:
    For i = 0 To uHuy
        Do Until j = uBiHuy
            If ArrChuoiHuy(i) = ArrChuoiBiHuy(j) Then
                ArrChuoiBiHuy(j) = ""
                Exit Do
            End If
            j = j + 1
         Loop
         j = 0
    Next
Đúng là chỉ dùng cách này mới có thể không dùng GoTo.

Không biết về tốc độ sau này có chậm hơn không vì Do ... Loop có thêm khoản j=j+1 rồi giải phóng biến j=0 nên có thể phát sinh chậm tí.

Mà thôi, mục đích cuối cùng của mình đã đạt được. Cám ơn quanghai1969 nhiều!
 
Upvote 0
Không biết về tốc độ sau này có chậm hơn không vì Do ... Loop có thêm khoản j=j+1 rồi giải phóng biến j=0 nên có thể phát sinh chậm tí.
For j - Next cũng có công đoạn tăng J lên 1 vậy? Còn giải phóng biến j thực ra là gán cho j 1 giá trị, so với GoTo thì cũng bằng 1 công đoạn.
 
Upvote 0
Thế sao không dùng Dictionary cho khỏe?
Số lần lập chắc chắn sẽ ít hơn và đương nhiên tốc độ sẽ nhanh hơn gấp nhiều lần
Thử xem sẽ biết
 
Lần chỉnh sửa cuối:
Upvote 0
Lần chỉnh sửa cuối:
Upvote 0
Vì hắn muốn 1 hủy 1
Thí dụ ArrHuy có 2 phần tử có giá trị 3, ArrBiHuy có 3 phần tử có giá trị 3, thì chỉ hủy 2, chừa lại 1.

Thì đúng vậy mà sư phụ.
Nhìn sơ cũng biết đây là bài toán so sánh 2 list. Cái "hắn" cần là lấy những phần tử tồn tại trong list 2 nhưng không tồn tại trong list 1
Quá dễ:
Mã:
Sub XulyChuoi()
  Dim dic As Object
  Dim ArrChuoiHuy(), ArrChuoiBiHuy(), ArrConLai()
  Dim i As Long, n As Long, lTmp As Long
  Set dic = CreateObject("Scripting.Dictionary")
  ArrChuoiHuy = Array(2, 4, 9, 3, 1, 6, 8, 10, 3)
  ArrChuoiBiHuy = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 13, 14, 15, 16, 17, 18, 19, 20)
  For i = 0 To UBound(ArrChuoiHuy)
    lTmp = ArrChuoiHuy(i)
    If Not dic.Exists(lTmp) Then dic.Add lTmp, ""
  Next
  For i = 0 To UBound(ArrChuoiBiHuy)
    lTmp = ArrChuoiBiHuy(i)
    If Not dic.Exists(lTmp) Then
      n = n + 1
      ReDim Preserve ArrConLai(1 To n)
      ArrConLai(n) = lTmp
    End If
  Next
  MsgBox Join(ArrConLai, ",")
End Sub
Kết quả khác gì với kết quả của bài 1 chứ
 
Upvote 0
Thì đúng vậy mà sư phụ.
Nhìn sơ cũng biết đây là bài toán so sánh 2 list. Cái "hắn" cần là lấy những phần tử tồn tại trong list 2 nhưng không tồn tại trong list 1
Quá dễ:
Mã:
Sub XulyChuoi()
  Dim dic As Object
  Dim ArrChuoiHuy(), ArrChuoiBiHuy(), ArrConLai()
  Dim i As Long, n As Long, lTmp As Long
  Set dic = CreateObject("Scripting.Dictionary")
  ArrChuoiHuy = Array(2, 4, 9, 3, 1, 6, 8, 10, 3)
  ArrChuoiBiHuy = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 13, 14, 15, 16, 17, 18, 19, 20)
  For i = 0 To UBound(ArrChuoiHuy)
    lTmp = ArrChuoiHuy(i)
    If Not dic.Exists(lTmp) Then dic.Add lTmp, ""
  Next
  For i = 0 To UBound(ArrChuoiBiHuy)
    lTmp = ArrChuoiBiHuy(i)
    If Not dic.Exists(lTmp) Then
      n = n + 1
      ReDim Preserve ArrConLai(1 To n)
      ArrConLai(n) = lTmp
    End If
  Next
  MsgBox Join(ArrConLai, ",")
End Sub
Kết quả khác gì với kết quả của bài 1 chứ

Kết quả khác đó Thầy, giả sử ở ArrChuoiBiHuy có 3 số bị trùng và chỉ xử lý 2 số thôi thì kết quả của Dict sẽ cho sai:

ArrChuoiBiHuy = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 13, 14, 15, 3, 16, 17, 18, 19, 20)

Thầy thử đi sẽ thấy vì sao em không dùng Dic là như thế!
 
Upvote 0
Kết quả khác đó Thầy, giả sử ở ArrChuoiBiHuy có 3 số bị trùng và chỉ xử lý 2 số thôi thì kết quả của Dict sẽ cho sai:

ArrChuoiBiHuy = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 13, 14, 15, 3, 16, 17, 18, 19, 20)

Thầy thử đi sẽ thấy vì sao em không dùng Dic là như thế!
Ah, hiểu rồi
Vậy thử code kiểu khác:
Mã:
Sub XulyChuoi()
  Dim dic As Object
  Dim ArrChuoiHuy(), ArrChuoiBiHuy(), ArrConLai()
  Dim i As Long, n As Long, lTmp As Long
  Set dic = CreateObject("Scripting.Dictionary")
   ArrChuoiHuy = Array(2, 4, 9, 3, 1, 6, 8, 10, 3)
    ArrChuoiBiHuy = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 13, 14, 15, 16, 17, 18, 19, 20)
  For i = 0 To UBound(ArrChuoiHuy)
    lTmp = ArrChuoiHuy(i)
    If Not dic.Exists(lTmp) Then
      dic.Add lTmp, 1
    Else
      dic.Item(lTmp) = dic.Item(lTmp) + 1
    End If
  Next
  For i = 0 To UBound(ArrChuoiBiHuy)
    lTmp = ArrChuoiBiHuy(i)
    If Not dic.Exists(lTmp) Then
      n = n + 1
      ReDim Preserve ArrConLai(1 To n)
      ArrConLai(n) = lTmp
    Else
      dic.Item(lTmp) = dic.Item(lTmp) - 1
      If dic.Item(lTmp) = 0 Then dic.Remove lTmp
    End If
  Next
  [A2] = Join(ArrConLai, ",")
End Sub
 
Upvote 0
Thêm chiêu nữa!
Nếu không quan tâm đến trật tự sắp xếp của mảng kết quả thì dùng code này:
Mã:
Sub XulyChuoi2()
  Dim ArrChuoiHuy(), ArrChuoiBiHuy(), ArrConLai()
  Dim i As Long, j As Long, n As Long, uHuy As Long, uBiHuy As Long, lTmp As Long
  
  ArrChuoiHuy = Array(2, 4, 9, 3, 1, 6, 8, 10, 3)
  ArrChuoiBiHuy = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 13, 14, 15, 3, 16, 17, 18, 19, 20)
  uHuy = UBound(ArrChuoiHuy): uBiHuy = UBound(ArrChuoiBiHuy)
  On Error Resume Next
  For i = 0 To uHuy
    For j = 0 To uBiHuy
      If ArrChuoiHuy(i) = ArrChuoiBiHuy(j) Then
        ArrChuoiBiHuy(j) = ArrChuoiBiHuy(uBiHuy)
        uBiHuy = uBiHuy - 1
        ReDim Preserve ArrChuoiBiHuy(uBiHuy)
        Exit For
      End If
    Next
  Next
  [A3] = Join(ArrChuoiBiHuy, ",")
End Sub
Xử lý trực tiếp trên mảng ArrChuoiBiHuy luôn, bằng cách khi điều kiện so sánh thỏa mản ta sẽ đổi chổ vị trí ấy với vị cuối cùng, xong ReDim Preserve cho "biến" luôn giá trị vừa so sánh
Nghĩa test thử xem (mới nghĩ thôi, không chắc lắm vì chiêu này hơi "tà đạo")
 
Lần chỉnh sửa cuối:
Upvote 0
Ah, hiểu rồi
Vậy thử code kiểu khác:
Mã:
Sub XulyChuoi()
  Dim dic As Object
  Dim ArrChuoiHuy(), ArrChuoiBiHuy(), ArrConLai()
  Dim i As Long, n As Long, lTmp As Long
  Set dic = CreateObject("Scripting.Dictionary")
   ArrChuoiHuy = Array(2, 4, 9, 3, 1, 6, 8, 10, 3)
    ArrChuoiBiHuy = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 13, 14, 15, 16, 17, 18, 19, 20)
  For i = 0 To UBound(ArrChuoiHuy)
    lTmp = ArrChuoiHuy(i)
    If Not dic.Exists(lTmp) Then
      dic.Add lTmp, 1
    Else
      dic.Item(lTmp) = dic.Item(lTmp) + 1
    End If
  Next
  For i = 0 To UBound(ArrChuoiBiHuy)
    lTmp = ArrChuoiBiHuy(i)
    If Not dic.Exists(lTmp) Then
      n = n + 1
      ReDim Preserve ArrConLai(1 To n)
      ArrConLai(n) = lTmp
    Else
      dic.Item(lTmp) = dic.Item(lTmp) - 1
      If dic.Item(lTmp) = 0 Then dic.Remove lTmp
    End If
  Next
  [A2] = Join(ArrConLai, ",")
End Sub

Theo như vậy, Thầy nghĩ nó sẽ nhanh hơn cách em làm nếu sửa lại chút xíu không ạ? hihihi

Code của em đây:

Mã:
Sub XulyChuoi()
Dim t As Double
t = Timer


    Dim ArrChuoiHuy(), ArrConLai(), ArrChuoiBiHuy()
    Dim i As Long, j As Long, n As Long, uHuy As Long, uBiHuy As Long
    
    ArrChuoiHuy = Array(2, 4, 9, 99998, 3, 1, 6, 2000, 8, 20000, 10, 9099, 99999)
    
    ReDim ArrChuoiBiHuy(0 To 100000)
    For i = 0 To 100000
        ArrChuoiBiHuy(i) = i
    Next
    uHuy = UBound(ArrChuoiHuy): uBiHuy = UBound(ArrChuoiBiHuy)
    
    Dim m As Long
    m = uBiHuy
    For i = 0 To uHuy
        For j = 0 To uBiHuy
            If ArrChuoiHuy(i) = ArrChuoiBiHuy(j) Then
                ArrChuoiBiHuy(j) = ""
                m = m - 1
                Exit For
            End If
        Next
    Next
    
    ReDim ArrConLai(0 To m)
    For i = 0 To uBiHuy
        If ArrChuoiBiHuy(i) > "" Then
          ArrConLai(n) = ArrChuoiBiHuy(i)
          n = n + 1
        End If
    Next
    
    Dim chuoi As String
    chuoi = Join(ArrConLai, ",")


    Debug.Print Timer - t & "/ " & n


End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Ăn chắc 100% nhanh hơn
Nghĩa đếm bằng tay số lần lập là biết liền chứ gì
Cũng khó nói cái code nào hơn code nào lắm Thầy ơi, nếu số lượng khủng thì không biết như thế nào, nhưng với 100000 item (như code vừa đưa lên) là ăn chắc Thầy về thời gian đó!
 
Upvote 0
Cũng khó nói cái code nào hơn code nào lắm Thầy ơi, nếu số lượng khủng thì không biết như thế nào, nhưng với 100000 item (như code vừa đưa lên) là ăn chắc Thầy về thời gian đó!

Nghĩ cũng lạ! Giờ mới thấy là dic.Add và dic.Exists cho tốc độ rất chậm khi số lượng phần tử nhiều (đã test)
Trong khi code ở bài 15 cho tốc độ cực khủng: 1 triệu phần tử ra kết quả trong vòng 0.5s
(Mình cứ tưởng ReDim Preserve sẽ chậm chứ, ai dè là không phải vậy)
 
Upvote 0
Nghĩ cũng lạ! Giờ mới thấy là dic.Add và dic.Exists cho tốc độ rất chậm khi số lượng phần tử nhiều (đã test)
Trong khi code ở bài 15 cho tốc độ cực khủng: 1 triệu phần tử ra kết quả trong vòng 0.5s
(Mình cứ tưởng ReDim Preserve sẽ chậm chứ, ai dè là không phải vậy)
Bài 15 đó tuy sắp xếp lộn xộn xì ngầu, nhưng là nhanh nhất đấy nhé! nếu lấy n thì phải là n=uBiHuy+1.
 
Upvote 0
Với code đó, Thầy sửa một chút như vầy sẽ còn nhanh hơn nữa!

Mã:
    For i = 0 To uHuy
        For j = 0 To uBiHuy
            If ArrChuoiHuy(i) = ArrChuoiBiHuy(j) Then
                ArrChuoiBiHuy(j) = ArrChuoiBiHuy(uBiHuy)
                uBiHuy = uBiHuy - 1
                Exit For
            End If
        Next
    Next
[COLOR=#ff0000]    ReDim Preserve ArrChuoiBiHuy(uBiHuy)[/COLOR]

Tức là sau khi xử lý xong thì mình mới "chặt đứt đuôi" một lần!


Mình hỏi ngoải lề chút: Bài toán này ứng dụng vào việc gì?
Đây là một bài toán cho việc nhập Phiếu vận chuyển tại Cảng của em, một cuốn vận chuyển vẫn có thể có số trùng và số thiếu, vì vậy nhằm xử lý khi thực tế giao sẽ khác với ban đầu. Thêm nữa cũng có những lúc ta nhập vào máy sai nên sửa lại, phải xóa những số sai đã nhập v.v...

Cũng có thể làm chương trình bóc thăm trúng thưởng mà không dùng dict nữa!
 
Upvote 0
Với code đó, Thầy sửa một chút như vầy sẽ còn nhanh hơn nữa!

Mã:
    For i = 0 To uHuy
        For j = 0 To uBiHuy
            If ArrChuoiHuy(i) = ArrChuoiBiHuy(j) Then
                ArrChuoiBiHuy(j) = ArrChuoiBiHuy(uBiHuy)
                uBiHuy = uBiHuy - 1
                Exit For
            End If
        Next
    Next
[COLOR=#ff0000]    ReDim Preserve ArrChuoiBiHuy(uBiHuy)[/COLOR]

Tức là sau khi xử lý xong thì mình mới "chặt đứt đuôi" một lần!

Bài này có một lỗi tiềm ẩn, đó chính là khi đã xử lý hết số, có nghĩa là uBiHuy = -1

Vì vậy ta phải bẩy thêm một lỗi nữa cho nó:

Mã:
    For i = 0 To uHuy
        For j = 0 To uBiHuy
            If ArrChuoiHuy(i) = ArrChuoiBiHuy(j) Then
                ArrChuoiBiHuy(j) = ArrChuoiBiHuy(uBiHuy)
                uBiHuy = uBiHuy - 1
                Exit For
            End If
        Next
    Next
    
[COLOR=#ff0000]    If uBiHuy > -1 Then[/COLOR]
        ReDim Preserve ArrChuoiBiHuy(0 To uBiHuy)
[COLOR=#ff0000]    End If[/COLOR]
 
Upvote 0
Đề tài chính:

Dùng goto <-> Exit For
Exit For áp dụng cho vòng lặp trực tiếp chứa nó. Nếu 2 vòng lặp lồng nhau thì nó chỉ thoát ra khỏi vòng trong.
Vì vậy trong bài này, dùng Exit For để kết thúc vòng lặp trong và tiếp tục vòng lặp ngoài là đúng rồi.
Khi có vòng lặp lồng nhau, nguoiwf ta dùng Goto để thoát một lúc nhiều vòng lặp.

Đề tài phụ:

Trong Basic, đổi trị chuỗi là công việc tốn năng lượng. Muốn đánh dấu một chuỗi không muốn dùng nữa thì người ta dùng một ký hiệu đánh vào ngay đầu chuỗi. Ví dụ ký hiệu này là NULL (0)

private const XOA = char(0) ' hoặc vbNull gì đó
' Trong vòng lặp xét chuỗi, nếu gặp chô muốn xoá
MID(chuoi,1,1) = XOA
' Sau đó trong vòng lặp xét lại
IF MID(chuoi,1,1) = XOA THEN ...

Tôi chỉ bàn về kỹ thuật làm việc với chuỗi thôi. Còn giải thuật của bài này là chuyện khác.
 
Upvote 0

Bài viết mới nhất

Back
Top Bottom