Tạo hàm so sánh 2 danh sách (3 người xem)

Liên hệ QC

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

anhtuan1066

Thành viên gạo cội
Tham gia
10/3/07
Bài viết
5,802
Được thích
6,912
Thấy có nhiều bạn hỏi về chủ đề so sánh 2 danh sách. Trên diễn đàn cũng đã có nhiều bài trả lời bằng nhiều thuật toán khác nhau. Hôm nay tôi viết 1 hàm tổng quát về Compare2List với 3 tùy chọn:

- Tìm các phần tử có cả trong danh sách 1 và danh sách 2
- Tìm các phần tử có trong danh sách 1 mà không có trong danh sách 2
- Tìm các phần tử có trong danh sách 2 mà không có trong danh sách 1

Code như sau:
PHP:
Function Compare2List(ByVal sArray1, ByVal sArray2, ByVal CompareMod As Long)
  Dim Dic1, Dic2, Item, Item1, Item2, TmpKeys1, TmpKeys2, Arr() as String
  Dim Tmp1 As String, Tmp2 As String, j As Long, ub As Long
  On Error Resume Next
  Set Dic1 = CreateObject("Scripting.Dictionary")
  Set Dic2 = CreateObject("Scripting.Dictionary")
  For Each Item1 In sArray1
    If CStr(Item1) <> "" Then
      Tmp1 = CStr(Item1)
      If Not Dic1.Exists(Tmp1) Then Dic1.Add Tmp1, ""
    End If
  Next
  TmpKeys1 = Dic1.Keys
  For Each Item2 In sArray2
    If CStr(Item2) <> "" Then
      Tmp2 = CStr(Item2)
      If Not Dic2.Exists(Tmp2) Then Dic2.Add Tmp2, ""
    End If
  Next
  TmpKeys2 = Dic2.Keys
  ub = IIf(Dic1.Count > Dic2.Count, Dic1.Count, Dic2.Count)
  ReDim Arr(1 To ub, 1 To 1)
  Select Case CompareMod
    Case 1
      For Each Item In TmpKeys1
        If Dic2.Exists(CStr(Item)) Then
          j = j + 1
          Arr(j, 1) = CStr(Item)
        End If
      Next
    Case 2
      For Each Item In TmpKeys1
        If Not Dic2.Exists(CStr(Item)) Then
          j = j + 1
          Arr(j, 1) = CStr(Item)
        End If
      Next
    Case 3
      For Each Item In TmpKeys2
        If Not Dic1.Exists(CStr(Item)) Then
          j = j + 1
          Arr(j, 1) = CStr(Item)
        End If
      Next
  End Select
  Compare2List = Arr
End Function
Áp dụng Ví dụ bạn có 2 danh sách: DS1 nằm ở A2:A60000 và DS2 nằm ở B2:B60000
1> Để tìm các phần tử có trong 2 danh sách
PHP:
Sub Main1()
  Dim sArray1, sArray2, Arr, TG As Double
  On Error Resume Next
  TG = Timer
  sArray1 = Range("A2:A65536").Value
  sArray2 = Range("B2:B65536").Value
  Arr = Compare2List(sArray1, sArray2, 1)
  Range("D2").Resize(UBound(Arr, 1)).Value = Arr
  MsgBox Timer - TG
End Sub
2> Để tìm các phần tử có trong danh sách 1 mà không có trong danh sách 2
PHP:
Sub Main2()
  Dim sArray1, sArray2, Arr, TG As Double
  On Error Resume Next
  TG = Timer
  sArray1 = Range("A2:A65536").Value
  sArray2 = Range("B2:B65536").Value
  Arr = Compare2List(sArray1, sArray2, 2)
  Range("E2").Resize(UBound(Arr, 1)).Value = Arr
  MsgBox Timer - TG
End Sub
3> Để tìm các phần tử có trong danh sách 2 mà không có trong danh sách 1
PHP:
Sub Main3()
  On Error Resume Next
  Dim sArray1, sArray2, Arr, TG As Double
  TG = Timer
  sArray1 = Range("A2:A65536").Value
  sArray2 = Range("B2:B65536").Value
  Arr = Compare2List(sArray1, sArray2, 3)
  Range("F2").Resize(UBound(Arr, 1)).Value = Arr
  MsgBox Timer - TG
End Sub
Code này dùng phương pháp xử lý mảng nên tốc độ rất cao. Thử nghiệm với 2 danh sách dài 65000 dòng, ra kết quả trong vòng 4 giây
Mời xem file và kiểm tra
 

File đính kèm

Tôi không hiểu lắm về mã nguồn của anhtuan1066, nhưng xin được cải tiến một chút để linh hoạt hơn khi quy định các vùng dữ liệu đầu vào và vùng dữ liệu đầu ra.

Cụ thể được ghi trong file đính kèm. Để tiện, tôi xoá một số macro của anhtuan66 (chỉ giữ lại 1) và đưa thêm macro có tên Run1, có dùng phím nóng là Ctrl - R.

Cũng xin nói thêm: Việc đưa tham số của Macro ngay trên bảng tính có thể là giải pháp tốt cho nhiều trường hợp khác, và đã lâu, tôi muốn thể hiện nó.
Nay nhờ bài của anhtuan, tôi có cơ hội. Một lần nữa xin cảm ơn.
 

File đính kèm

Upvote 0
Tôi không hiểu lắm về mã nguồn của anhtuan1066, nhưng xin được cải tiến một chút để linh hoạt hơn khi quy định các vùng dữ liệu đầu vào và vùng dữ liệu đầu ra.
Cụ thể được ghi trong file đính kèm. Để tiện, tôi xoá một số macro của anhtuan66 (chỉ giữ lại 1) và đưa thêm macro có tên Run1, có dùng phím nóng là Ctrl - R.
Cũng xin nói thêm: Việc đưa tham số của Macro ngay trên bảng tính có thể là giải pháp tốt cho nhiều trường hợp khác, và đã lâu, tôi muốn thể hiện nó.
Nay nhờ bài của anhtuan, tôi có cơ hội. Một lần nữa xin cảm ơn.
Ai lại làm thế! Cái bạn làm có nghĩa là THỰC THI LỆNH TỪ 1 CHUỔI CHỨA CÂU LỆNH
Vậy phải xem bài này:
http://www.giaiphapexcel.com/forum/showthread.php?34190-L%C3%A0m-sao-%C4%91%E1%BB%83-ch%E1%BA%A1y-1-l%E1%BB%87nh-t%E1%BB%AB-1-chu%E1%BB%95i-%28c%C3%B3-c%C3%BA-ph%C3%A1p-c%E1%BB%A7a-1-l%E1%BB%87nh%29
Và code của bạn tôi sửa lại thành:
PHP:
Sub ExecuteCommand()
  If ActiveCell = "" Then Exit Sub
  With ThisWorkbook.VBProject
    .VBComponents.Add(1).Name = String(30, "z")
    With .VBComponents(String(30, "z")).CodeModule
      .InsertLines .CountOfLines + 1, _
       "Sub Main()" & vbLf & _
         "Dim Arr" & vbLf & _
         "On Error Resume Next" & vbLf & _
         "Arr = Compare2List(" & ActiveCell.Value & ")" & vbLf & _
         "ActiveCell.Offset(1).Resize(Ubound(Arr,1)).Value = Arr" & vbLf & _
       "End Sub"
    End With
    Application.Run "Main"
    .VBComponents.Remove .VBComponents(String(30, "z"))
  End With
End Sub
PHP:
Sub Run1()
  Dim TG As Double
  TG = Timer
  ExecuteCommand
  MsgBox Timer - TG
End Sub
Cái chiêu ExecuteCommand này có thể dùng cho bất kỳ trường hợp nào, không riêng gì bài này đâu (đấy gọi là TỔNG QUÁT)
Lưu ý chổ này khi chạy code:
- Đối với những code có can thiệp vào việc THÊM CODE, XÓA CODE, CHỈNH CODE thì ta phải vào menu Tools\Macro\Security, chuyển sang tab Trusted Publishers và check vào "Trust access... " mới dùng được
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Tuy chưa làm thử nhưng tôi có hướng như sau:
1. Để nguyên Function Compare2List(ByVal sArray1, ByVal sArray2, ByVal CompareMod As Long)
2. Dùng 1 sub main duy nhất, trong đó:
a. Dùng 4 inputbox để nhập:​

  • Vùng DL1
  • Vùng DL2
  • CompareMod
  • Vị trí cần dán kết quả
b. Chạy code: (đại khái)
PHP:
sArray1 = Inputbox1
sArray2 = Inputbox2
CompareMod = Inputbox3
Dich = Inputbox4
ArrKQ = Compare2List(sArray1, sArray2, CompareMod)
Dich.Resize(...) = ArrKQ
Vì nhận thấy cách xử lý như haonlh vẫn phải gõ 2 vùng cần so sánh xuống 1 ô nào đó, và khả năng gõ sai là hiển nhiên.
 
Upvote 0
Đã làm xong:
PHP:
Sub Main()
  Dim sArray1, sArray2, Arr, TG As Double, Destination As Range, CompareMod As Long
  sArray1 = Application.InputBox("Select List 1", "List 1", , , , , , 8)
  sArray2 = Application.InputBox("Select list 2", "List 2", , , , , , 8)
  CompareMod = Application.InputBox("Select how to compare:" & Chr(10) & _
  "1: Available in 2 lists" & Chr(10) & _
  "2: Available in List 1, Not available in list 2" & Chr(10) & _
  "3: Available in List 2, Not available in list 1", "Select one", , , , , 1)
  Set Destination = Application.InputBox("Copy to:", "Select destination", , , , , , 8)
  TG = Timer
  
Arr = Compare2List(sArray1, sArray2, CompareMod)
Destination.Resize(UBound(Arr, 1)).Value = Arr
  MsgBox Timer - TG
End Sub
Tiện lợi:
- Có thể dùng chuột chọn 2 list ở 2 nơi khác nhau
- Đọc hướng dẫn sử dụng tham số CompareMod trước khi nhập, khỏi sợ quên hoặc nhầm
- Có thể dùng chuột chọn vị trí cần dán kết quả.
- 2 Lists và nơi dán kết quả có thể khác sheet.
 
Lần chỉnh sửa cuối:
Upvote 0
Tôi nghĩ thế này:

Bài của anhtuan là 1 demo. Khi dùng, ta chép dữ liệu vào 2 cột A, B của Sheet của file của anhtuan cấp. Thế là xong. Và cần kết quả nào thì chép về.

Cách của tôi là để không phải chép dữ liệu đi và về. Tất nhiên phải mở sẵn file của anhtuan lên. 3 tham số của đầu cột kết quả là giống như 1 tiêu đề gợi nhớ đến ý nghĩa của kết quả.

Tôi cho rằng cách của ptm0412 là cũng tốt. Nhưng giao tiếp với nhiều hộp thoại thì nhiều khi cảm thấy chậm. Nếu anh sửa lại chỉ cần 1 hộp thoại mà có cả các tham số thì tốt hơn.

Như vậy tuỳ ý thích, sự cảm nhận thuận tiện mà người dùng sử dụng.

Còn ý của anh ndu là đưa cả tên macro vào ô (có phải không anh?). Mục này chưa dám đọc kỹ (dạo này đang bận quá). Xin lỗi.
 
Upvote 0
Tôi nghĩ thế này:

Tôi cho rằng cách của ptm0412 là cũng tốt. Nhưng giao tiếp với nhiều hộp thoại thì nhiều khi cảm thấy chậm. Nếu anh sửa lại chỉ cần 1 hộp thoại mà có cả các tham số thì tốt hơn.
Thời gian giao tiếp nhiều hộp thoại so với thời gian gõ địa chỉ vùng vào 1 cell nào đó chưa biết cái nào chậm hơn.
Tôi làm theo ý tưởng của tôi và theo cách mà tôi cho là tạo thuận tiện cho người dùng.
Tất nhiên là cũng sẽ có người có ý kiến khác và cho rằng như vậy là bất tiện.

Nếu muốn sửa thành 1 hộp thoại duy nhất thì được ngay: Tạo 1 Userform 4 ô nhập liệu, 1 số label ghi chú này nọ, 2 command button, là xong.
 
Upvote 0
Tôi nghĩ thế này:

Bài của anhtuan là 1 demo. Khi dùng, ta chép dữ liệu vào 2 cột A, B của Sheet của file của anhtuan cấp. Thế là xong.
Bạn đâu có nhất thiết phải chép dữ liệu vào cột A, B chứ ---> Cái Sub Main ấy, bạn sửa thế nào là tùy ý, miễn sao phù hợp với dữ liệu thật của bạn thì thôi
Tôi lấy ví dụ công cụ Advanced Filter của Excel. Nó là thứ có sẳn, tuy nhiên khi dùng thì bạn vẫn phải tự mình chọn vùng, check các tùy chọn bằng tay cơ mà
 
Upvote 0
Nguyên văn bởi ptm0412
Tiện lợi:
- Đọc hướng dẫn sử dụng tham số CompareMod trước khi nhập, khỏi sợ quên hoặc nhầm
Có thể sửa lại như thế này không anh:
- Đọc hướng dẫn sử dụng tham số CompareMod trước khi Doubleclick để nhận tham số. Cám ơn
 
Upvote 0
Tâm đang nói double click vào đâu? file nào? của ai?

Code của ptm khi chạy nó ra cái này để nhập nè:

CompareMode.jpg
 
Upvote 0
Tâm đang nói double click vào đâu? file nào? của ai?

Code của ptm khi chạy nó ra cái này để nhập nè:

View attachment 62339
---
Em xin lỗi,vì em diển đạt không hết ý, ý em muốn tiện lợi là ra luôn 1 bảng khác na ná như bảng trên nhưng có thể double click để truyền không cần phải nhập bằng tay nữa đó anh ( giống như Listbox trong userform vậy).
* Cho em thời gian test
 
Lần chỉnh sửa cuối:
Upvote 0
Chọn 1 trong 3 cái hiển thị, khỏi gõ, đó là Listbox hoặc combobox, thế là phải tạo form rồi:

3 cái RefEdit
1 cái Combobox
2 CommandButton

Code của Command Button:

PHP:
Private Sub Extract_Click()
On Error GoTo Loi1
Dim sArray1, sArray2, Arr, TG As Double, Destination As Range, CompareMode As Long
  sArray1 = Range(REdit1).Value
  sArray2 = Range(REdit2).Value
  CompareMode = Val(Cbbox1)
  Set Destination = Range(REdit3)
  
  TG = Timer
Arr = Compare2List(sArray1, sArray2, CompareMode)
Destination.Resize(UBound(Arr, 1)).Value = Arr
  msgbox Timer - TG
  Unload Me: Exit Sub
Loi1:
msgbox "Try again, or hit cancel!"
Exit Sub
End Sub

*test xong chưa không biết, đành đoạn ra đi, không nhấn tks 1 cái!
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Chọn 1 trong 3 cái hiển thị, khỏi gõ, đó là Listbox hoặc combobox, thế là phải tạo form rồi:

3 cái RefEdit
1 cái Combobox
2 CommandButton

Code của Command Button:

PHP:
Private Sub Extract_Click()
On Error GoTo Loi1
Dim sArray1, sArray2, Arr, TG As Double, Destination As Range, CompareMode As Long
  sArray1 = Range(REdit1).Value
  sArray2 = Range(REdit2).Value
  CompareMode = Val(Cbbox1)
  Set Destination = Range(REdit3)
  
  TG = Timer
Arr = Compare2List(sArray1, sArray2, CompareMode)
Destination.Resize(UBound(Arr, 1)).Value = Arr
  msgbox Timer - TG
  Unload Me: Exit Sub
Loi1:
msgbox "Try again, or hit cancel!"
Exit Sub
End Sub

*test xong chưa không biết, đành đoạn ra đi, không nhấn tks 1 cái!
Cái chổ CompareMod em nghĩ sư phụ cho vào 3 cái OptionButton (OptionButton đặt trong 1 Frame) sẽ hay hơn ---> Khởi động UserForm thì mặc định sẽ check sẳn 1 cái. Khi người dùng xài, chắc chắn 1 trong 3 OptionButton này đã được check <===> CompareMod sẽ là 1 hoặc 2 hoặc 3, khỏi sợ người ta gõ bừa
 
Upvote 0
Chỉ cần có ý tưởng khả thi là làm được thôi. Code cho Option Button cũng dễ, không khó gì đâu, Tâm làm dư sức.
Hay là Tam8678 làm 1 phát biểu diễn đi?
 
Lần chỉnh sửa cuối:
Upvote 0
Chọn 1 trong 3 cái hiển thị, khỏi gõ, đó là Listbox hoặc combobox, thế là phải tạo form rồi:

3 cái RefEdit
1 cái Combobox
2 CommandButton

Code của Command Button:

PHP:
Private Sub Extract_Click()
On Error GoTo Loi1
Dim sArray1, sArray2, Arr, TG As Double, Destination As Range, CompareMode As Long
sArray1 = Range(REdit1).Value
sArray2 = Range(REdit2).Value
CompareMode = Val(Cbbox1)
Set Destination = Range(REdit3)
 
TG = Timer
Arr = Compare2List(sArray1, sArray2, CompareMode)
Destination.Resize(UBound(Arr, 1)).Value = Arr
msgbox Timer - TG
Unload Me: Exit Sub
Loi1:
msgbox "Try again, or hit cancel!"
Exit Sub
End Sub

*test xong chưa không biết, đành đoạn ra đi, không nhấn tks 1 cái!
---
Đáng nể ( thêm chút nữa thì thêm dấu- thêm rồi). Trong trường hợp 2 list giống nhau, thì khi chọn điều kiện 2 hoặc 3 thì có nên ra 1 thông báo là không tìm thấy không anh? (Em viết thì không nổi nhưng đòi hỏi "tiện lợi" thì hay lắm %#^#$).
*Ý code cho option Button là của ndu
** Buộc lòng phải thêm dấu thôi __--__
 
Lần chỉnh sửa cuối:
Upvote 0
Như vầy không biết đã được chưa?
Ngày xưa làm cái trúc xanh, có 1 tên chotter (chọt sĩ), chọt hết cái này đến cái khác, nhờ vậy mà hoàn thiện.
Lần này cũng lại phải cám ơn chọt sĩ nữa rồi.

CompareForm.gif
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Upvote 0
Cám ơn Bác, hay quá. Nhưng cho em xin PM để giải nén file trên.
Gì mà PM trời? File RAR thôi mà
Giải nén luôn cho bác đây
Lưu ý thêm:
- Trong hàm Compare2List có đoạn:
PHP:
ub = IIf(Dic1.Count > Dic2.Count, Dic1.Count, Dic2.Count)
- Nên thêm như vầy mới chắc:
PHP:
ub = IIf(Dic1.Count > Dic2.Count, Dic1.Count, Dic2.Count)
Ìf ub = 0 then ub = 1
Tránh trường hợp ub = 0 vì vùng dữ liệu rổng
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Gì mà PM trời? File RAR thôi mà
Giải nén luôn cho bác đây
Thú thật, chả biết thế nào mà down về mấy lần mà giải nén không được. Kg biết file trên bị lỗi hay là máy mình bị lỗi. Các file rar khác vẫn unzip OK.
Cám ơn Bác.
File ex bị gì mà mở ra thì như sau.
 

File đính kèm

  • LoiGPE.JPG
    LoiGPE.JPG
    113.2 KB · Đọc: 146
Lần chỉnh sửa cuối:
Upvote 0
Lần chỉnh sửa cuối:
Upvote 0
Web KT

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

Back
Top Bottom