Nhờ chỉnh code loại trùng text dùng REGEXP

Liên hệ QC

eke_rula

Thành viên tích cực
Tham gia
12/11/16
Bài viết
1,076
Được thích
1,245
Em có đoạn code:
PHP:
Sub tachtrung2()
    Dim i As Long, j As Long, text As String, text2 As String, text3 As String
    text3 = "Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;2017;2017;dien;EXCEL;2017"
    text = Replace(";" & text3 & ";", ";", "   ")
    With CreateObject("vbscript.regexp")
        .Global = True
        .ignorecase = True
        .Pattern = "((\s\w+\s).+)\2"
        Do While .test(text)
            text = .Replace(text, "$1 ")
            i = 0
            For Each subl In .Execute(text)
                If InStr(text2, Trim(.Execute(text).Item(i).submatches(1))) = 0 Then
                    text2 = text2 & " | " & Trim(.Execute(text).Item(i).submatches(1))
                End If
                i = i + 1
            Next
        Loop
    End With
    MsgBox ("- " & Application.Trim(text) + ChrW(10) & "- " & text2)
End Sub
Kết quả của đoạn code trên tạo ra là chuỗi sau khi loại trùng hết (text) và liệt kê các chuỗi text bị trùng (text2)
- Sau khi chay code được text2, nhưng đoạn text2 lúc nào cũng có dấu "|" ở đầu và cuối, em có thể dùng Application.SUBSTITUTE để loại dấu "|" đầu và cuối, nhưng trong VBA có hàm replace , em đã thử replace(text,text2,"",,1) nhưng không được, cho em hỏi là em có thể dùng replace trong trường hợp này được không?
- Kết quả text2="| Dien | dan | 2017 | phap | Dan | excel" , bị trùng chữ "dan" và "Dan", em đã dùng Instr để xác định xem xuất hiện trong chuỗi không nhưng sao kết quả text2 vẫn có trùng, nhờ các anh/chị xem và chỉnh code dùm em.
Em cám ơn!!
 
Lần chỉnh sửa cuối:
Cái "subl" là đại diện cho mỗi item trong regexp, nó chính là .Execute(text).Item(i) đấy bạn, vì regexp tạo ra dạng mảng collection nên mình dùng for Each để lấy ra.
Nếu thế thì trong vòng lặp for each có lẽ nên thay .Execute(text).Item(i) = subl thì dễ hiểu hơn
Instr không phân biệt chữ hoa chữ thường thì phải, vì mấy cái text kia đều có hoa thường hết, nhưng loại được. Có lẽ nên dùng replace sẽ hợp lý hơn!!!
Hình như hàm instr có phân biệt hoa thường instr( start, str1, str2, compare ) chỗ màu đỏ thì phải.

Dạng bài này hình như trước đây đã có lần thấy không dùng 1 vòng lặp nào cả .
 
Lần chỉnh sửa cuối:
Upvote 0
Nếu thế thì trong vòng lặp for each có lẽ nên thay .Execute(text).Item(i) = subl thì dễ hiểu hơn

Hình như hàm instr có phân biệt hoa thường instr( start, str1, str2, compare ) chỗ màu đỏ thì phải.

Dạng bài này hình như trước đây đã có lần thấy không dùng 1 vòng lặp nào cả .
Tại mình làm tắt bạn ạ, viết đúng là for each subl...next subl, không dùng for each thì dùng fphor i=0 to .Execute(text).Count - 1 cũng được
Mình dùng lcase hay ucase thì được rồi bạn ạ, nhưng ngộ quá trong chuỗi này "Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;2017;2017;dien;EXCEL;2017" , các chữ khác đều có viết hoa viết thường tùm lum hết , mà loại được ,mà riêng chữa "dan" lại phải dùng ucase hay lcase mới được.
Theo mình nghĩ cái code mình phải chạy vòng lặp mới được, vì pattern đó nó làm 2 việc là loại trùng các chữ bị trùng nhau và liệt kê các chữ trùng nhau đấy, chỉ riêng phần liệt kê không là phải chạy vòng lặp rồi, vì mỗi cái số \2 nó sẽ lưu vào submatches vì là nó tự nghi nhớ nên những đoạn tetxt phù hợp với pattern là nó tự động đưa vào luôn, nếu mà đoạn text bị trùng nhiều hơn 2 lần thì cái submacthes chắc chắn sẽ bị trùng nên mới dùng các cách để xét trùng (trong bài đang dùng instr) đồng thời mỗi cái submaches đó lại thuộc các item khác nhau, nên phải dùng vòng lặp duyệt qua mới lấy ra được.
Riêng phân loại trùng đoạn text trên mà không dùng vòng lặp mình không nghĩ ra được cái pattern khác bạn ạ, bạn làm giúp mình loại trùng mà không dùng vòng lặp được không bạn, không cần liệt kê các phần tử trùng. Cám ơn bạn.
 
Lần chỉnh sửa cuối:
Upvote 0
Cái \2 là cái (\s\w+\s) được lưu trong submacthes, và được dùng để liệt kê cái text2. Bài này em giải từ bài của anh @dhn46 thấy có anh @huuthang_bd giải rồi nhưng chỉ liệt kê cái text2 , chứ chưa loại trùng cái text, nên em làm lại với cái pattern khác, em có đọc bài về regexp của anh @hungpecc1 và anh @quanghai1969, thấy có mấy bài rất hay về Backreference của anh @siwtom nên có thể hiểu được phần nào.

Như tôi đã nói qua, nhìn thấy cái \2 thì biết cái pattern đó dùng bạckreference. Mà đã dùng kỹ thuật này thì là cao cấp. Bạn có thể dùng watch và debug để tìm hiểu lỗi.

siwtom là tay chuyên nghiệp về code ứng dụng trên Delphi. Đương nhiên là kỹ thuật cao rồi. Tuy nhiên, nếu bạn muốn biết thêm về Regex thì nên tìm vào các diễn đàn chuyên Unix (Linux, Ubuntu,...). Ba cái phân tích chuỗi này (kể cả réc éc) thì Perl mới là chúa tể. VBScript không hổ trợ ba cái dòm trước ngó sau này mạnh lắm nên tôi lười đi sâu.
 
Upvote 0
Riêng phân loại trùng đoạn text trên mà không dùng vòng lặp mình không nghĩ ra được cái pattern khác bạn ạ, bạn làm giúp mình loại trùng mà không dùng vòng lặp được không bạn, không cần liệt kê các phần tử trùng. Cám ơn bạn.
Để tìm lại file lưu hoặc đường dẫn sẽ gửi lại bạn.
Thân chào
 
Upvote 0
siwtom là tay chuyên nghiệp về code ứng dụng trên Delphi.
Lại bị gọi tên :D
Công việc của tôi không đòi hỏi. Chỉ là đam mê thôi.
Ba cái phân tích chuỗi này (kể cả réc éc) thì Perl mới là chúa tể. VBScript không hổ trợ ba cái dòm trước ngó sau này mạnh lắm nên tôi lười đi sâu.
Đúng 200%
Có một số giới hạn. Điển hình là nó khong có tính năng "dòm ngược"..
Đúng 200%. Tôi tiếc đứt ruột là ...

Có
mẫu2(?=mẫu1) - tìm các đoạn có dạng mẫu2 mà sau chúng là đoạn có dạng mẫu1
mẫu2(?!mẫu1) - tìm các đoạn có dạng mẫu2 mà sau chúng không có đoạn dạng mẫu1

nhưng không có
Tìm các đoạn có dạng mẫu2 mà trước chúng là đoạn có dạng mẫu1
Tìm các đoạn có dạng mẫu2 mà trước chúng không có đoạn dạng mẫu1

Em chỉ muốn nghiên cứu về Reg thôi anh ạ
Ví dụ:
Mã:
Sub tachtrung2()
Dim text As String, text3 As String
    text3 = "Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;2017;2017;dien;EXCEL;2017"
'    de phong co dau cach
    text = Replace(text3 & ";", " ", "")
    With CreateObject("vbscript.regexp")
        .Global = True
        .ignorecase = True
        .Pattern = "(\w+;)((\w+;)*)\1"
        Do While .test(text)
            text = .Replace(text, "$1$2")
        Loop
    End With
    MsgBox Mid(text, 1, Len(text) - 1)
End Sub

Hoặc chuỗi văn bản tự nhiên hơn - các từ cách nhau bằng dấu cách.
Mã:
Sub tachtrung2()
Dim text As String, text3 As String
    text3 = "Dien     Dien Dan    dien giai phap   dien    dan phap   excel  Excel phap   dan  2017 2017   dien  EXCEL 2017"
'    loai dau cach thua
    text = WorksheetFunction.Trim(text3) & " "
    With CreateObject("vbscript.regexp")
        .Global = True
        .ignorecase = True
        .Pattern = "(\w+ )((\w+ )*)\1"
        Do While .test(text)
            text = .Replace(text, "$1$2")
        Loop
    End With
    MsgBox Mid(text, 1, Len(text) - 1)
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Lại bị gọi tên :D
Công việc của tôi không đòi hỏi. Chỉ là đam mê thôi.

Đúng 200%

Đúng 200%. Tôi tiếc đứt ruột là ...

Có
mẫu2(?=mẫu1) - tìm các đoạn có dạng mẫu2 mà sau chúng là đoạn có dạng mẫu1
mẫu2(?!mẫu1) - tìm các đoạn có dạng mẫu2 mà sau chúng không có đoạn dạng mẫu1

nhưng không có
Tìm các đoạn có dạng mẫu2 mà trước chúng là đoạn có dạng mẫu1
Tìm các đoạn có dạng mẫu2 mà trước chúng không có đoạn dạng mẫu1


Ví dụ:
Mã:
Sub tachtrung2()
Dim text As String, text3 As String
    text3 = "Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;2017;2017;dien;EXCEL;2017"
'    de phong co dau cach
    text = Replace(text3 & ";", " ", "")
    With CreateObject("vbscript.regexp")
        .Global = True
        .ignorecase = True
        .Pattern = "(\w+;)((\w+;)*)\1"
        Do While .test(text)
            text = .Replace(text, "$1$2")
        Loop
    End With
    MsgBox Mid(text, 1, Len(text) - 1)
End Sub

Hoặc chuỗi văn bản tự nhiên hơn - các từ cách nhau bằng dấu cách.
Mã:
Sub tachtrung2()
Dim text As String, text3 As String
    text3 = "Dien     Dien Dan    dien giai phap   dien    dan phap   excel  Excel phap   dan  2017 2017   dien  EXCEL 2017"
'    loai dau cach thua
    text = WorksheetFunction.Trim(text3) & " "
    With CreateObject("vbscript.regexp")
        .Global = True
        .ignorecase = True
        .Pattern = "(\w+ )((\w+ )*)\1"
        Do While .test(text)
            text = .Replace(text, "$1$2")
        Loop
    End With
    MsgBox Mid(text, 1, Len(text) - 1)
End Sub
Cái code này của anh rất hay đấy ạ, dù code của em về cách chạy thì giống code anh, nhưng cái pattern của anh ổn hơn, em phải chỉnh cái parttern mấy lần để cho phù hợp với text những lần chạy sau, cái dấu * của anh rất hay , em dùng .+ nên bắc buộc lúc nào cũng phải có 1 kí tự nên rơi vào trường hợp dien;dien; là chịu. Anh cho em hỏi tý, khi code chạy vòng lặp Do thứ 2 thì cái đoạn phù hợp nhất với pattern là "Dien;Dien;Dan;dien;giai;phap;dien;", nhưng nếu em nhìn cái pattern của anh là"(\w+ ; )((\w+ ; )*)\1" thì đoạn "Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;" nó cũng phù hợp, nếu regexp để defaut thì nó sẽ lấy đoạn xa nhất tức là đoạn thứ 2. Không biết em có hiểu sai chỗ này của anh không, anh hãy giải thích cho em chỗ này, cám ơn anh!!
 
Upvote 0
nhưng nếu em nhìn cái pattern của anh là"(\w+ ; )((\w+ ; )*)\1" thì đoạn "Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;" nó cũng phù hợp
Không phải.

Sau vòng Do thứ 1 thì
text = "Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;2017;2017;EXCEL;2017;"

Chú ý: pattern không có dấu cách nhưng trong bài viết tôi thay cụm <dấu chấm phẩy+ dấu ")"> bằng <dấu chấm phẩy+ dấu cách + dấu ")"> vì nếu không thì script thay cụm bằng hình mặt cười.

Đoạn ((\w+; )*) sẽ hoặc là trống hoặc phải có dạng:
<từ1>;<từ2>;...<từk>;
Tức nếu không rỗng thì phải kết thúc bằng dấu chấm phẩy ;
(phải là <từ1> chứ không thể <một phần của từ1>. Vì trước nó phải có dấu chấm phẩy của (\w+; ))

Từ đây thấy rõ là đoạn:
"n;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;

không khớp với pattern. Vì nếu khớp thì:

((\w+; )*) = "Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;da"

rõ ràng không kết thúc bằng dấu chấm phẩy.

Đoạn
"Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;"

cũng không khớp với pattern vì lúc đó phải có (\w+; ) = "Dien;" và \1 = "dan;", vô lý. Vì (\w+; ) và \1 phải y hệt nhau (không phân biệt chữ hoa hay thường). Không có chuyện "n;" = "n;" vì cả (\w+; ) và \1 phải là <từ>; chứ không thể là <một phần của từ>;
---------------
Ở vòng Do thứ 2 ta có tận 3 đoạn khớp:
1. "Dien;Dien;Dan;dien;giai;phap;dien;"
\1 = "dien;" và nó được bỏ

2. "dan;phap;excel;Excel;phap;dan;"
\1 = "dan;" và nó được bỏ

3. "2017;2017;EXCEL;2017;"
\1 = "2017;" và nó được bỏ

Do đó sau vòng Do thứ 2 ta có:
text = "Dien;Dien;Dan;dien;giai;phap;dan;phap;excel;Excel;phap;2017;2017;EXCEL;"
---------------
Các tính chất của pattern:
1. ((\w+; )*) hoặc là trống hoặc là chuỗi các từ cách nhau bằng dấu chấm phẩy và kết thức bằng dấu chấm phẩy.
2. (\w+; ) và \1 phải là 1 từ và sau nó có dấu chấm phẩy. Là một từ chứ không là một phần của từ vì trước \1 phải là dấu chấm phẩy của (\w+; ) (khi ((\w+; )*) rỗng), hoặc dấu chấm phẩy của ((\w+; )*)
 
Lần chỉnh sửa cuối:
Upvote 0
Cái "subl" là đại diện cho mỗi item trong regexp, nó chính là .Execute(text).Item(i) đấy bạn, vì regexp tạo ra dạng mảng collection nên mình dùng for Each để lấy ra.
Instr không phân biệt chữ hoa chữ thường thì phải,
Nếu bạn dùng vbBinaryCompare (mặc định) thì phân biệt chữ hoa thường. Muốn không phân biệt thì phải dùng vbTextCompare

Tại sao lại dùng .Execute(text).Item(i) trong IF ... End If khi đó chính là subl?

Tóm lại thay
Mã:
i = 0
            For Each subl In .Execute(text)
                If InStr(text2, Trim(.Execute(text).Item(i).submatches(1))) = 0 Then
                    text2 = text2 & " | " & Trim(.Execute(text).Item(i).submatches(1))
                End If
                i = i + 1
            Next
bằng
Mã:
For Each subl In .Execute(text)
                If InStr(1, text2, Trim(subl.submatches(1)), vbTextCompare) = 0 Then
                    text2 = text2 & " | " & Trim(subl.submatches(1))
                End If
            Next
Nhưng code thực ra không đúng.
Nếu bạn có vd. text3 = "Dan;com;dan" thì text2 sẽ rỗng trong khi phải có text2 = "Dan". Tại sao?
Vì .Execute(text) sẽ trả về tập rỗng do Execute được thực hiện cho text = " Dan com ", tức cho text ở dòng text = .Replace(text, "$1 ") chứ không phải cho text = " Dan com dan " ở dòng Do While. Vậy ta sửa thành
Mã:
Do While .test(text)
    Set match = .Execute(text)
    text = .Replace(text, "$1 ")
    For Each subl In match
        If InStr(1, text2, Trim(subl.submatches(1)), vbTextCompare) = 0 Then
            text2 = text2 & " | " & Trim(subl.submatches(1))
        End If
    Next
Loop
Nhưng code trên vẫn chưa đúng vì mới giải quyết xong vấn đề .Execute(text). Còn vấn đề InStr thì chưa chính xác.
Bạn thử với text3 = "Dan;An;com;an;pho;dan" thì text2 không có "An". Vì sao?
Sau Do While thứ 1 có text = " Dan An com an Pho " và text2 = " | Dan"
Trong vòng thứ 2 thì Trim(subl.submatches(1)) = "An", sẽ tìm thấy trong text2 nên điều kiên
Mã:
InStr(1, text2, Trim(subl.submatches(1)), vbTextCompare) = 0
sẽ không thỏa nên "An" không được thêm vào text2.
Vậy phải sửa thành
Mã:
text2 = "|"
Do While .test(text)
    Set match = .Execute(text)
    text = .Replace(text, "$1 ")
    For Each subl In match
        If InStr(1, text2, "|" & Trim(subl.submatches(1)) & "|", vbTextCompare) = 0 Then
            text2 = text2 & Trim(subl.submatches(1)) & "|"
        End If
    Next
Loop

Mổ sẻ thế là đủ rồi nhỉ ;)
 
Upvote 0
Không phải.

Sau vòng Do thứ 1 thì
text = "Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;2017;2017;EXCEL;2017;"

Chú ý: pattern không có dấu cách nhưng trong bài viết tôi thay cụm <dấu chấm phẩy+ dấu ")"> bằng <dấu chấm phẩy+ dấu cách + dấu ")"> vì nếu không thì script thay cụm bằng hình mặt cười.

Đoạn ((\w+; )*) sẽ hoặc là trống hoặc phải có dạng:
<từ1>;<từ2>;...<từk>;
Tức nếu không rỗng thì phải kết thúc bằng dấu chấm phẩy ;
(phải là <từ1> chứ không thể <một phần của từ1>. Vì trước nó phải có dấu chấm phẩy của (\w+; ))

Từ đây thấy rõ là đoạn:
"n;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;

không khớp với pattern. Vì nếu khớp thì:

((\w+; )*) = "Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;da"

rõ ràng không kết thúc bằng dấu chấm phẩy.

Đoạn
"Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;"

cũng không khớp với pattern vì lúc đó phải có (\w+; ) = "Dien;" và \1 = "dan;", vô lý. Vì (\w+; ) và \1 phải y hệt nhau (không phân biệt chữ hoa hay thường). Không có chuyện "n;" = "n;" vì cả (\w+; ) và \1 phải là <từ>; chứ không thể là <một phần của từ>;
---------------
Ở vòng Do thứ 2 ta có tận 3 đoạn khớp:
1. "Dien;Dien;Dan;dien;giai;phap;dien;"
\1 = "dien;" và nó được bỏ

2. "dan;phap;excel;Excel;phap;dan;"
\1 = "dan;" và nó được bỏ

3. "2017;2017;EXCEL;2017;"
\1 = "2017;" và nó được bỏ

Do đó sau vòng Do thứ 2 ta có:
text = "Dien;Dien;Dan;dien;giai;phap;dan;phap;excel;Excel;phap;2017;2017;EXCEL;"
---------------
Các tính chất của pattern:
1. ((\w+; )*) hoặc là trống hoặc là chuỗi các từ cách nhau bằng dấu chấm phẩy và kết thức bằng dấu chấm phẩy.
2. (\w+; ) và \1 phải là 1 từ và sau nó có dấu chấm phẩy. Là một từ chứ không là một phần của từ vì trước \1 phải là dấu chấm phẩy của (\w+; ) (khi ((\w+; )*) rỗng), hoặc dấu chấm phẩy của ((\w+; )*)
Cám ơn anh đã giải thích rất chi tiết cho em, em đã biết thêm một số cái, đặc biệt chỗ dấu *, mới đầu xem em cứ ý là dấu * trong regexp và trong công thức excel là như nhau, nhưng không phải vậy dấu * trong công thức là đại diện cho một chuỗi bất kì (có thể không có), còn trong regexp thì nó lặp lại phần tử đứng trước nó với tần suất >=0, còn + thì >0, bởi vậy nhằm lúc làm ra sai mà không hiểu sao.
Em vẫn có chỗ thắc mác thêm anh ạ, như chỗ này:
(\w+; ) và \1 phải là 1 từ và sau nó có dấu chấm phẩy.
Lúc trước làm thì em có chạy thử cái pattern này:
Mã:
Pattern = "((\w+;).*)\2"
text = .Replace(text, "$1")
(xin lỗi vì phải cho vào đây vì hiện mặt cười). Em dùng debug để kiểm tra khi đoạn text còn là "Dien;Dan;giai;phap;phap;excel;Excel;2017;2017;" thì nó nhận diện đoạn phù hợp nhất với Pattern là "n;Dan;", "phap;phap;","excel;Excel;","2017;2017;" . Em cũng hiểu là \w+ là nó sẽ lấy những phần tử trong tập w đến khi nào không có thì dừng theo nguyên tắc như vậy thì lẽ ra nó phải nhận diện chỉ có phap;phap;,excel;Excel;,2017;2017; thôi anh nhỉ, sao nó lại lụm thằng "n;Dan;", bởi vậy em mới thắc mắc ở bài trên, anh giải thích cho em chỗ này, cám ơn anh!!
 
Lần chỉnh sửa cuối:
Upvote 0
Nếu bạn dùng vbBinaryCompare (mặc định) thì phân biệt chữ hoa thường. Muốn không phân biệt thì phải dùng vbTextCompare

Tại sao lại dùng .Execute(text).Item(i) trong IF ... End If khi đó chính là subl?

Tóm lại thay
Mã:
i = 0
            For Each subl In .Execute(text)
                If InStr(text2, Trim(.Execute(text).Item(i).submatches(1))) = 0 Then
                    text2 = text2 & " | " & Trim(.Execute(text).Item(i).submatches(1))
                End If
                i = i + 1
            Next
bằng
Mã:
For Each subl In .Execute(text)
                If InStr(1, text2, Trim(subl.submatches(1)), vbTextCompare) = 0 Then
                    text2 = text2 & " | " & Trim(subl.submatches(1))
                End If
            Next
Nhưng code thực ra không đúng.
Nếu bạn có vd. text3 = "Dan;com;dan" thì text2 sẽ rỗng trong khi phải có text2 = "Dan". Tại sao?
Vì .Execute(text) sẽ trả về tập rỗng do Execute được thực hiện cho text = " Dan com ", tức cho text ở dòng text = .Replace(text, "$1 ") chứ không phải cho text = " Dan com dan " ở dòng Do While. Vậy ta sửa thành
Mã:
Do While .test(text)
    Set match = .Execute(text)
    text = .Replace(text, "$1 ")
    For Each subl In match
        If InStr(1, text2, Trim(subl.submatches(1)), vbTextCompare) = 0 Then
            text2 = text2 & " | " & Trim(subl.submatches(1))
        End If
    Next
Loop
Nhưng code trên vẫn chưa đúng vì mới giải quyết xong vấn đề .Execute(text). Còn vấn đề InStr thì chưa chính xác.
Bạn thử với text3 = "Dan;An;com;an;pho;dan" thì text2 không có "An". Vì sao?
Sau Do While thứ 1 có text = " Dan An com an Pho " và text2 = " | Dan"
Trong vòng thứ 2 thì Trim(subl.submatches(1)) = "An", sẽ tìm thấy trong text2 nên điều kiên
Mã:
InStr(1, text2, Trim(subl.submatches(1)), vbTextCompare) = 0
sẽ không thỏa nên "An" không được thêm vào text2.
Vậy phải sửa thành
Mã:
text2 = "|"
Do While .test(text)
    Set match = .Execute(text)
    text = .Replace(text, "$1 ")
    For Each subl In match
        If InStr(1, text2, "|" & Trim(subl.submatches(1)) & "|", vbTextCompare) = 0 Then
            text2 = text2 & Trim(subl.submatches(1)) & "|"
        End If
    Next
Loop

Mổ sẻ thế là đủ rồi nhỉ ;)
Cám ơn anh đã giải thích đoạn chữ hoa chữ thường dùm em, thật ra mới đầu em có nghĩ tới nhưng em cho là không phải tại chỗ này , vì đoạn text này "
"Dien;Dien;Dan;dien;giai;phap;dien;dan;phap;excel;Excel;phap;dan;2017;2017;dien;EXCEL;2017", các chữ khác đề có chữ hoa và chữ thường như nhau chẵng hạn "Dien" và "dien", "excel" và "EXCEL".. mà không bị, chỉ bị mỗi "Dan" và "dan". Sau khi xem lại thì nó loại được là do may mắn, ví dụ cặp "Dien" và "dien" nó lưu vào submatches là "Dien" và loại thằng "dien" đi khi replace, nên thằng Instr sẽ chẵng có cơ hội để so sánh... Nhưng thằng "Dan" và "dan" nó lại khác nó lưu đầu tiên vào submaches là "dan" , vì khi chay sẽ có lúc 2 đoạn khớp với pattern trong đó có "dan...dan", khi chạy lần tiếp theo do "Dan" đứng trước "dan", nên nó sẽ lưu "Dan" vào submactes nữa, vì vậy khi dùng Instr defaut thì sẽ hiểu "Dan" và "dan" là khác nhau.
Nhưng code thực ra không đúng.
Nếu bạn có vd. text3 = "Dan;com;dan" thì text2 sẽ rỗng trong khi phải có text2 = "Dan". Tại sao?
Vì .Execute(text) sẽ trả về tập rỗng do Execute được thực hiện cho text = " Dan com ", tức cho text ở dòng text = .Replace(text, "$1 ") chứ không phải cho text = " Dan com dan " ở dòng Do While. Vậy ta sửa thành
Cám ơn anh, em không chú ý đến chỗ này thật, vì đã chạy replace rồi nên cái execute là của cái text mới chứ không phải cái text cũ.
Nhưng code trên vẫn chưa đúng vì mới giải quyết xong vấn đề .Execute(text). Còn vấn đề InStr thì chưa chính xác.
Bạn thử với text3 = "Dan;An;com;an;pho;dan" thì text2 không có "An". Vì sao?
Sau Do While thứ 1 có text = " Dan An com an Pho " và text2 = " | Dan"
Trong vòng thứ 2 thì Trim(subl.submatches(1)) = "An", sẽ tìm thấy trong text2 nên điều kiên
Vâng, chỗ này thì em hiểu rồi ạ, chỗ này em bị các anh chị khác trong diễn đàn nhắc nhở khi so sanh text rồi, cám ơn anh!!
 
Lần chỉnh sửa cuối:
Upvote 0
Thực ra pattern củas tôi vẫn chưa chính xác. Phải là
Mã:
.Pattern = "(\b\w+;)((\w+;)*)\1"
Nhưng có lẽ
Mã:
.Pattern = "(\b\w+;)(.*)\1"
còn hay hơn
Cái pattern
Mã:
.Pattern = "(\b\w+;)(.*)\1"
khi bỏ \b thì nó giống với cái pattern em đã nói ở trên:
Mã:
 .Pattern = "((\w+;).*)\2"
Thêm \b vào thì tức là tới đầu vị trí của mỗi chuỗi thì em hiểu nó sẽ loại được trường hợp "n;dan;". Nhưng (\b\w+ ; )và (\w+ ; ) là khác nhau như thế nào , (\w+ ; ) theo em hiểu là nó sẽ lấy đến khi nào không có phần tử thuộc w nữa thì thôi, em nghĩ nó cũng giống (\b\w+ ; ). Anh giải thích dùm em chỗ này, cám ơn anh!!!
(Xin lỗi thêm khoảng trắng vào mấy cái pattern vì hiện mặt cười)
 
Upvote 0
Em vẫn có chỗ thắc mác thêm anh ạ, như chỗ này:

Lúc trước làm thì em có chạy thử cái pattern này:
Mã:
Pattern = "((\w+;).*)\2"
text = .Replace(text, "$1")
(xin lỗi vì phải cho vào đây vì hiện mặt cười). Em dùng debug để kiểm tra khi đoạn text còn là "Dien;Dan;giai;phap;phap;excel;Excel;2017;2017;" thì nó nhận diện đoạn phù hợp nhất với Pattern là "n;Dan;", "phap;phap;","excel;Excel;","2017;2017;" .
, sao nó lại lụm thằng "n;Dan;", bởi vậy em mới thắc mắc ở bài trên, anh giải thích cho em chỗ này, cám ơn anh!!
pattern này của bạn cũng phạm lỗi như pattern cũ của tôi. Tôi phát hiện ra sự thiếu chính xác trong pattern của mình khi phân tích và nêu các tính chất của pattern.
Trước tiên nói về pattern của bạn
Mã:
.Pattern = "((\w+;).*)\2"

\1 = \w+;.*
"ông anh sinh đôi" của \2 = \w+;
Suy ra:
1. "ông anh sinh đôi" của \2 và cả \2 là chuỗi ký tự thuộc [a-z0-9] và kết thúc bằng dấu chấm phẩy? Chỉ thế thôi. Còn pattern không bắt buộc trước "ông anh sinh đôi" của \2 và \2 phải là "biên giới giữa text và không text". Vậy thì vẫn có trường hợp trước "ông anh sinh đôi" của \2 hoặc trước \2 không có "biên giới giữa text và không text", tức "ông anh sinh đôi" của \2 hoặc \2 chỉ là một phần của từ nào đấy, mà pattern vẫn thỏa. Tất cả những ký tự còn lại của đoạn khớp sẽ được chuyển vào tài khoản của .*
Ví dụ:
a. Trước \2 không có "biên giới giữa text và không text" nhưng trước "ông anh sinh đôi" của \2 có "biên giới giữa text và không text". Tức "ông anh sinh đôi" của \2 là cả từ trong khi \2 chỉ là một phần của từ: text = "om;em;luc;an;com;"
Rõ ràng không có từ nào lặp nhưng toàn bộ text là đoạn khớp. Regexp sẽ "đẩy" om; vào \2 và chuyển em;luc;an;c vào tài khoản của .*

b. Trước \2 có "biên giới giữa text và không text" nhưng trước "ông anh sinh đôi" của \2 không có "biên giới giữa text và không text". Tức \2 là cả từ trong khi "ông anh sinh đôi" của \2 chỉ là một phần của từ: text = "hom;qua;di;bia;om;"
Đoạn khớp là om;qua;di;bia;om;
om;
-> \2 và qua;di;bia; vào tài khoản của .*

Để có pettern đúng thì phải thêm điều kiện là trước "ông anh sinh đôi" của \2 và trước \2 phải là "biên giới giữa text và không text". Tức \w+ và \2 phải là cả từ chứ không là một phần của từ
Mã:
.Pattern = .Pattern = "((\b\w+;).*\b)\2"
---------------
Về code cũ của tôi
Mã:
"(\w+;)((\w+;)*)\1"
pattern đảm bảo tính chất: trước \1 có "biên giới giữa text và không text" (đó chính là dấu chấm phẩy). Chỉ thế thôi. Từ pattern không suy ra là trước "ông anh sinh đôi" của \1 phải là "biên giới giữa text và không text". Vậy tôi chỉ phải sửa sao cho trước "ông anh sinh đôi" của \1 phải là "biên giới giữa text và không text"
Mã:
.Pattern = "(\b\w+;)((\w+;)*)\1"
-------------
Tôi nhầm vì
Mã:
.Pattern = "(\b\w+;)(.*)\1"
không bắt buộc trước \1 phải là "biên giới giữa text và không text". Vậy pattern đó không chính xác.

Vậy phải là
Mã:
.Pattern = "(\b\w+;)(.*\b)\1"
------------
Tóm lại hiện ta có 3 pattern
Mã:
.Pattern =  "((\b\w+;).*\b)\2"
.Pattern = "(\b\w+;)((\w+;)*)\1"
.Pattern = "(\b\w+;)(.*\b)\1"
 
Upvote 0
pattern này của bạn cũng phạm lỗi như pattern cũ của tôi. Tôi phát hiện ra sự thiếu chính xác trong pattern của mình khi phân tích và nêu các tính chất của pattern.
Trước tiên nói về pattern của bạn
Mã:
.Pattern = "((\w+;).*)\2"

\1 = \w+;.*
"ông anh sinh đôi" của \2 = \w+;
Suy ra:
1. "ông anh sinh đôi" của \2 và cả \2 là chuỗi ký tự thuộc [a-z0-9] và kết thúc bằng dấu chấm phẩy? Chỉ thế thôi. Còn pattern không bắt buộc trước "ông anh sinh đôi" của \2 và \2 phải là "biên giới giữa text và không text". Vậy thì vẫn có trường hợp trước "ông anh sinh đôi" của \2 hoặc trước \2 không có "biên giới giữa text và không text", tức "ông anh sinh đôi" của \2 hoặc \2 chỉ là một phần của từ nào đấy, mà pattern vẫn thỏa. Tất cả những ký tự còn lại của đoạn khớp sẽ được chuyển vào tài khoản của .*
Ví dụ:
a. Trước \2 không có "biên giới giữa text và không text" nhưng trước "ông anh sinh đôi" của \2 có "biên giới giữa text và không text". Tức "ông anh sinh đôi" của \2 là cả từ trong khi \2 chỉ là một phần của từ: text = "om;em;luc;an;com;"
Rõ ràng không có từ nào lặp nhưng toàn bộ text là đoạn khớp. Regexp sẽ "đẩy" om; vào \2 và chuyển em;luc;an;c vào tài khoản của .*

b. Trước \2 có "biên giới giữa text và không text" nhưng trước "ông anh sinh đôi" của \2 không có "biên giới giữa text và không text". Tức \2 là cả từ trong khi "ông anh sinh đôi" của \2 chỉ là một phần của từ: text = "hom;qua;di;bia;om;"
Đoạn khớp là om;qua;di;bia;om;
om;
-> \2 và qua;di;bia; vào tài khoản của .*

Để có pettern đúng thì phải thêm điều kiện là trước "ông anh sinh đôi" của \2 và trước \2 phải là "biên giới giữa text và không text". Tức \w+ và \2 phải là cả từ chứ không là một phần của từ
Mã:
.Pattern = .Pattern = "((\b\w+;).*\b)\2"
---------------
Về code cũ của tôi
Mã:
"(\w+;)((\w+;)*)\1"
pattern đảm bảo tính chất: trước \1 có "biên giới giữa text và không text" (đó chính là dấu chấm phẩy). Chỉ thế thôi. Từ pattern không suy ra là trước "ông anh sinh đôi" của \1 phải là "biên giới giữa text và không text". Vậy tôi chỉ phải sửa sao cho trước "ông anh sinh đôi" của \1 phải là "biên giới giữa text và không text"
Mã:
.Pattern = "(\b\w+;)((\w+;)*)\1"
-------------
Tôi nhầm vì
Mã:
.Pattern = "(\b\w+;)(.*)\1"
không bắt buộc trước \1 phải là "biên giới giữa text và không text". Vậy pattern đó không chính xác.

Vậy phải là
Mã:
.Pattern = "(\b\w+;)(.*\b)\1"
------------
Tóm lại hiện ta có 3 pattern
Mã:
.Pattern =  "((\b\w+;).*\b)\2"
.Pattern = "(\b\w+;)((\w+;)*)\1"
.Pattern = "(\b\w+;)(.*\b)\1"
Anh giải thích dễ hiểu, lúc trước còn mơ hồ thằng "biên giới giữa text và không text" nhưng giờ đã hiểu ý nghĩ của nó rồi, trong pattern thì nên có nó để tránh trường hợp sai sót . Nhưng em nghĩ thay bằng "biên giới giữa hai nước" thì sẽ hợp lý cho mọi trường hợp hơn :p:p:p.Cám ơn anh!!!
 
Upvote 0
"biên giới giữa text và không text" (đó chính là dấu chấm phẩy)
Nói tắt quá sợ hiểu lầm.
\b không là ký tự nào cả. Nó chỉ là "vị trí" giữa ký tự thuộc [a-zA-Z0-9_]) và ký tự [^A-Za-z0-9_]. Tức trong trường hợp trên là "vị trí" giữa 2 ký tự ";" (dấu chấm phẩy) và "\w"

Tương tự như "^" và "$" không là ký tự mà chỉ là "vị trí" đầu và cuối mỗi text (cả đầu và cuối mỗi dòng nếu MultiLine = TRUE)
 
Lần chỉnh sửa cuối:
Upvote 0
Nói tắt quá sợ hiểu lầm.
\b không là ký tự nào cả. Nó chỉ là "vị trí" giữa ký tự [a-z0-9] và [a-z0-9]. Tức trong trường hợp trên là "vị trí" giữa 2 ký tự ";" (dấu chấm phẩy) và "\w"

Tương tự như "^" và "$" không là ký tự mà chỉ là "vị trí" đầu và cuối mỗi text (cả đầu và cuối mỗi dòng nếu MultiLine = TRUE)
Nói giữa thì nghe hơi mơ hồ anh nhỉ, em có đọc file kí tự của anh siwtom, cũng nói là đoạn giữa, nhưng không hiểu , nhưng có ví dụ cũng chỉ hiểu được chút, ví dụ đoạn text: "\saad55asd00dsd-" thì nó chính là "\s\w+-" ví dụ có pattern "\b\w+-" hoặc "\w+\b" nếu hiểu đoạn giữa của nó là khúc nào ???? nếu như anh nói "đường biên giới" thì em sẽ hiểu là "\b\w+-" thì đường biên giới của nó sẽ là \s và "\w+\b" là "-".
 
Upvote 0
\s là 1 ký tự của tập [ \f\n\r\t\v]
\b không là ký tự, chỉ là "vị trí" giữa ký tự thuộc [a-zA-Z0-9_]) và ký tự [^A-Za-z0-9_]. Cũng có nghĩa là vị trí ^ hoặc $ nếu ký tự đầu tiên hoặc cuối cùng trong chuỗi thuộc [A-Za-z0-9_]
Thế bạn hiểu ^ và $ thế nào? Chúng cũng không là ký tự gì cả mà là "vị trí" đầu và cuối chuỗi (cả đầu và cuối mỗi dòng nếu MultiLine = TRUE). Thì \b nó cũng thế, cũng chỉ là "vị trí" thôi chứ có là ký tự gì đâu?

Vài vd. cho dễ hiểu. Tôi có chuỗi text = "Mai17 Hoa21 Nga39 Hanh14"

1. Thế bạn cho ^ nó là ký tự nào? Là "M"? Làm gì có chuyện đó. Thế $ nó là ký tự nào? Là "4"? Làm gì có chuyện đó.

"^" là "vị trí" đầu chuỗi. Hay nói nôm na thì "trước ký tự đầu tiên của chuỗi" có một vị trí vô hình, ta cứ tưởng tượng là chỗ mà ký tự đầu tiên "tiếp xúc" với "thế giới" bên ngoài. Và "chỗ đó" người ta gọi là "^". Về "$" cũng tương tự.

2. Nếu bạn vẫn chưa hiểu về \b thì hãy hiểu như sau:
"\b\w" có nghĩa là hoặc ký tự đại diện bởi \w là ký tự đầu tiên của chuỗi (của dòng) - lúc này \b trùng với ^, có cùng nghĩa với ^ - hoặc trước ký tự đó là ký tự thuộc [^A-Za-z0-9_]

"\w\b" có nghĩa là hoặc ký tự đại diện bởi \w là ký tự cuối cùng của chuỗi (của dòng) - lúc này \b trùng với $, có cùng nghĩa với $ - hoặc sau ký tự đó là ký tự thuộc [^A-Za-z0-9_]

3. Ta làm bài toán cực đơn giản. Xóa từ đầu tiên trong chuỗi. Tất nhiên có nhiều cách nhưng ta xét các cách cụ thể nhằm mục đích giải thích vài chuyện.
Nếu bạn có .Pattern = "\s\w+?\s" thì sẽ chỉ tìm thấy các từ thứ 2, 3. Bởi trước từ đầu tiên và sau từ cuối cùng không có ký tự nào là "dấu cách", TAB, vbCr, vbLf ... Chú ý: \s là ký tự thuộc [ \f\n\r\t\v].
Nếu có .Pattern = "^\w+?\s" thì tìm thấy và chỉ tìm thấy từ đầu tiên thôi. Bạn thấy rõ ràng trước "M" không có ký tự nào. Chỉ có "vị trí", "nơi "tiếp xúc" gọi là "^"

4. Bạn có text = "Mai tieu thu, Hong mat nau, My Ha noi choi than voi Hoa (Manh la ban cua ca hai)"
Bài toán: Tìm tất cả các tên có chữ cái đầu là "M"
Bạn không thể dùng "\s\M\w*?\s" được vì trước "Mai" không có ký tự nào, vì trước "Manh" là ký tự "(" không thuộc tập [ \f\n\r\t\v]. Tức bạn chỉ tìm thấy " My "
Nếu .Pattren = "\bM\w*?\b" thì bạn tìm thấy hết. Vì trước "Mai" là "^" cùng nghĩa với "\b", trước "My" là "dấu cách", vậy giữa "dấu cách" (thuộc tập [^A-Za-z0-9_]) và "M" (thuộc tập [A-Za-z0-9_]) có "\b", trước "Manh" là ký tự "(", vậy giữa ký tự "(" (thuộc tập [^A-Za-z0-9_]) và "M" (thuộc tập [A-Za-z0-9_]) có "\b". Tức bạn tìm thấy hết.

Bạn thấy sự khác nhau giữa \s và \b chưa?

Ngoài ra vì \s là ký tự nên bạn "nhìn" thấy ký tự này trong đoạn khớp. Tức đoạn khớp không phải là "My" mà là " My ". Trong khi dùng \b thì bạn chả có thêm ký tự nào trong đoạn khớp, vì \b không là ký tự. Nó chỉ là vị trí mà người ta qui ước với nhau là cái chỗ đó chỗ đó gọi là \b. Thế thôi.

Tóm lại cứ hiểu nôm na là \s là ký tự cụ thể của một tập ký tự cụ thể, tức có thể cân, đo, đong, đếm và nhìn thấy được. Trong khi đó ^, $, \b là "khái niệm"

Thôi không quan trọng từ ngữ nữa. Dù là "khái niệm", "vị trí", "biên giới" thì cứ hiểu:
Nếu tìm thấy đoạn khớp mà:
- pattern = "\b\w..." thì trong text nguồn trước đoạn khớp đó không thể cũng là ký tự \w
- pattern = "...\w\b" thì trong text nguồn sau đoạn khớp đó không thể cũng là ký tự \w
- pattern = "\b\W..." thì trong text nguồn trước đoạn khớp đó không thể cũng là ký tự \W
- pattern = "...\W\b" thì trong text nguồn sau đoạn khớp đó không thể cũng là ký tự \W
 
Upvote 0
Web KT
Back
Top Bottom