Lấy DM duy nhất theo 2 cột = Scripting.Dictionary!
Tôi mới học từ NDU và mày mò viết thử 1 code lấy DM duy nhất theo 2 cột = Scripting.Dictionary nhưng mà không chạy được, NDU hướng dẫn giúp nhé.
Sub UniqueArray2()
Dim endR As Long 'Copy NDU
Dim Src As Variant, Arr As Variant
Dim Dic1, Dic2, Tmp
Dim Items, Keys, i As Long, j As Long, TG As DoubleTG = Timer
Set Dic1 = CreateObject("Scripting.Dictionary")
Set Dic2 = CreateObject("Scripting.Dictionary")
With Sheets("Data")
endR = .Cells(65000, 1).End(xlUp).Row
ReDim Arr(1 To endR, 1 To 2)
With Range("A2:B" & endR)
Src = .Value
End With
For i = 1 To UBound(Src)
Tmp = CStr(Src(i, 1) & Src(i, 2))
Dic1.Add i, Tmp
If Not Dic1.Exists(Tmp) Then
j = j + 1
Items = Src(i, 1)
Keys = Src(i, 2)
Dic2.Add Items, Keys
Arr(j, 1) = Items
Arr(j, 1) = Keys
End If
Next
End With
If j = 0 Then Exit Sub
Range("H2:I" & j + 1).Value = ArrMsgBox Format(Timer – TG, "0.000000000")
End SubCám ơn nhiều.
Thử vầy xem:
Sub UniqueArray2()
Dim Src, Tmp, Arr(1 To 65535, 1 To 2)
Dim i As Long, j As Long, TG As Double
TG = Timer
With Sheets("Data")
Src = .Range(., ..End(xlUp)).Resize(, 2).Value
End With
With CreateObject("Scripting.Dictionary")
For i = 1 To UBound(Src)
If Src(i, 1) <> "" Or Src(i, 2) <> "" Then
Tmp = Src(i, 1) & Src(i, 2)
If Not .Exists(Tmp) Then
j = j + 1
.Add Tmp, ""
Arr(j, 1) = Src(i, 1)
Arr(j, 2) = Src(i, 2)
End If
End If
Next
End With
If j <> 0 Then
Range("H2").Resize(j, 2).Value = Arr
MsgBox Format(Timer – TG, "0.000000000")
End If
End Sub
Đâu cần ReDim mảng chi cho mất công chứ ThuNghi —> Lọc ra đến bao nhiêu ta lấy bấy nhiêu thôi, chẳng ảnh hưởng gì đến tốc độ cả
Redim là do thói quen thôi. Cám ơn NDU nhiều.
Không hiểu phần này code trên sai chỗ nào. Về logich thấy có vẻ đúng.
If Not Dic1.Exists(Tmp) Then
j = j + 1
Items = Src(i, 1)
Keys = Src(i, 2)
Dic2.Add Items, Keys
Arr(j, 1) = Items
Arr(j, 1) = Keys
End If
đoạn này:
If Not Dic1.Exists(Tmp) Then
j = j + 1
Items = Src(i, 1)
Keys = Src(i, 2)
Dic2.Add Items, Keys
Arr(j, 1) = Items
Arr(j, 1) = Keys không hiểu
Kiểm tra sự tồn tại của Tmp nhưng lại đi Add Src(i, 1) vào Keys —> Chẳng ăn nhậu gì với Tmp cả
Giống vầy:
– Cầm tấm thẻ học sinh, tra vào danh sách lớp, nếu chưa tồn tại thì.. ghi thêm tên của thẻ ấy vào danh sách
Đàng này ThuNghi lại:
– Cầm tấm thẻ học sinh, tra vào danh sách lớp, nếu chưa tồn tại thì.. ghi thêm tên của 1 cha lạ hoắc nào đó vào —> Liên quan gỉ đến tấm thẻ đang cầm trong tay?
Kiểm tra tmp có tồn tại tại Dic1 chưa, nếu không mình add vào Dic2 mà. 2 Dic này khai báo độc lập với nhau.
Uh… hiểu rồi… nhưng cũng.. sai luôn! Vì trong nhóm Keys của Dic1 làm gì có Tmp
Đoạn trên của ThuNghi là Dic1.Add i, Tmp cơ mà —> Tức i nằm trong nhóm Keys và Tmp nằm trong nhóm Items
Hic..
Khi kiểm tra sự tồn tại của 1 phần tử trong Dictionary Object, nó sẽ dò phần tử ấy trong Keys mà thôi, chẳng để ý gì Items đâu
Câu lệnh:
Dic.Exists(gì gì đó)
Gần như tương đương với
MATCH(gì gì đó, Dic.Keys,0)
Nếu MATCH không báo lỗi và có kết quả thì xem như CÓ TỒN TẠI
———————
Thuật toán cho bài này là:
– Quét từ trên xuống
– Nối 2 cột lại thành 1 biến tạm, là Tmp
– Kiểm tra sự tồn tại của Tmp trong Dictionary Object, nếu chưa có thì Add Tmp vào… Đồng thời gán giá trị 2 cột vào mảng luôn
Vậy:
– Dictionary Object trong code này chỉ làm nhiệm vị kiểm tra sự tồn tại, không làm nhiệm vụ lấy dữ liệu
– Chỉ cần 1 biến Dic là đủ
Vậy thì phải sửa code trên theo hướng add và 1 Dic khác nữa thì làm thế nào.
Có khi mình chưa cần gán vào Array, mình để thao tác tiếp, chỉ cần gán vào Dic đã.
Arr(j, 1) = Src(i, 1)
Arr(j, 2) = Src(i, 2)
Cám ơn nhiều. Mình đang tính triển khai lọc duy nhất theo nhiều cột (>2) và sum nhiều cột, ie khoản 4 Dic.
Chưa hiểu lắm chổ này
Vậy thì phải sửa code trên theo hướng add và 1 Dic khác nữa thì làm thế nào.
Có khi mình chưa cần gán vào Array, mình để thao tác tiếp, chỉ cần gán vào Dic đã.
————————————————-
Mình đang tính triển khai lọc duy nhất theo nhiều cột (>2) và sum nhiều cột, ie khoản 4 Dic.
Gữi bạn hàm tổng quát, lọc mấy cột tùy ý:
Function UniqueArray(SrcRng As Range)
Dim Src, Tmp As String, Arr()
Dim i As Long, j As Long, n As Long
Src = SrcRng.Value
ReDim Arr(1 To UBound(Src, 1), 1 To UBound(Src, 2))
With CreateObject("Scripting.Dictionary")
For i = LBound(Src, 1) To UBound(Src, 1)
Tmp = ""
For j = LBound(Src, 2) To UBound(Src, 2)
Tmp = Tmp & Src(i, j)
Next
If Tmp <> "" Then
If Not .Exists(Tmp) Then
n = n + 1
.Add Tmp, ""
For j = LBound(Src, 2) To UBound(Src, 2)
Arr(n, j) = Src(i, j)
Next
End If
End If
Next
End With
If j <> 0 Then
UniqueArray = Arr
End If
End Function
Sub chạy thí nghiệm với dữ liệu trên:
Sub Test()
Dim Arr, TG As Double
TG = Timer
With Sheets("Data")
Arr = UniqueArray(.Range(., ..End(xlUp)).Resize(, 2))
.Range("H2").Resize(UBound(Arr, 1), UBound(Arr, 2)) = Arr
End With
MsgBox Timer – TG
End Sub
www.giaiphapexcel.com/diendan/threads/l%E1%BA%A5y-dm-duy-nh%E1%BA%A5t-theo-2-c%E1%BB%99t-scripting-dictionary.37895/post-251703
Khóa học Power PI – Ứng dung trong Nhân sự
TỔNG QUAN KHÓA HỌC: POWER BI CHO NGÀNH NHÂN SỰ Khóa học Power BI cho Nhân sự được thiết kế dành riêng cho các...
Xem khóa học
Ứng dụng trích lọc DS duy nhất từ 2 trong 3 cột thành bảng 2 chiều
Giả sử ta có bảng 1 như sau:
|
A
|
B
|
C
|
1
|
Company Name
|
services
|
price
|
2
|Company 01|Service 1|
1,01
|
3
|Company 01|Service 2|
1,02
|
4
|Company 01|Service 3|
1,03
|
5
|Company 02|Service 2|
1,04
|
6
|Company 02|Service 3|
1,05
|
7
|Company 03|Service 1|
1,06
|
8
|Company 04|Service 1|
1,07
|
9
|Company 04|Service 3|
1,08
|
Và muốn trích lọc thành bảng sau:
|
F
|
G
|
H
|
I
|
1
|
Company Name
|
Service 1
|
Service 2
|
Service 3
|
2
|Company 01|
1,01
|
1,02
|
1,03
|
3
|Company 02|
0
|
1,04
|
1,05
|
4
|Company 03|
1,06
|
0
|
0
|
5
|Company 04|
1,07
|
0
|
1,08
|
Code:
Sub Convert()
Dim vValue As Variant, vVals As Variant
Dim i As Long, EndR As Long, j As Long
Dim PriceRng As Range
Dim dArr1(), dArr2(), d1, d2, dPrice()
Dim MyDic1 As Object, MyDic2 As Object
EndR = .End(xlUp).Row
dArr1 = Sheet1.Range("A1:A" & EndR).Value
dArr2 = Sheet1.Range("b2:b" & EndR).Value
Set MyDic1 = CreateObject("scripting.dictionary")
Set MyDic2 = CreateObject("scripting.dictionary")
For Each d1 In dArr1
If d1 <> "" And Not MyDic1.exists(d1) Then
MyDic1.Add d1, ""
End If
Next d1
For Each d2 In dArr2
If d2 <> "" And Not MyDic2.exists(d2) Then
MyDic2.Add d2, ""
End If
Next d2
Sheet1..Resize(MyDic1.Count, 1).Value = Application.Transpose(MyDic1.keys)
Sheet1..Resize(1, MyDic2.Count).Value = MyDic2.keys
Set PriceRng = Sheet1..Offset(1, 1).Resize(MyDic1.Count – 1, MyDic2.Count)
ReDim dPrice(MyDic1.Count – 1, MyDic2.Count)
For i = 1 To MyDic1.Count – 1
For j = 1 To MyDic2.Count
dPrice(i, j) = Evaluate("=SumProduct((" & Range("A2:A" & EndR).Address & "=" & Sheet1.Cells(i + 1, 6).Address & ")*" _
& "(" & Range("b2:b" & EndR).Address & "=" & Sheet1.Cells(1, j + 6).Address & ")*(" _
& Range("c2:c" & EndR).Address & "))")
Next j, i
PriceRng.Value = dPrice
Set MyDic1 = Nothing
Set MyDic2 = Nothing
Set PriceRng = Nothing
Erase dArr1, dArr2, dPrice
End Sub
Ghi nhận sau khi thực hiện:
– Không cần mảng tạm cho Dictionary
– Có thể gán xuống sheet bằng Dictionary.Keys
– Mảng dArr1 và dArr2 chỉ dùng để lấy giá trị từ range sau đó gán vào Dictionary, thay vì lấy giá trị từ từng cell trên sheet.
– Mảng dPrice dùng để nhận giá trị sau đó gán xuống sheet 1 lần thay vì gán từng cell
Tốc độ rất nhanh.
Không cần nhiều vòng lập thế đâu sư phụ à! Em dùng 1 vòng lập là đủ
Sub Transfer(Src1 As Range, Src2 As Range, Src3 As Range, Target As Range)
Dim Arr(1 To 60000, 1 To 200), ScrArr1, SrcArr2, SrcArr3, Dic1, Dic2, Tmp1, Tmp2
Dim i As Long, iR As Long, iC As Long, n As Long, m As Long
Set Dic1 = CreateObject("Scripting.Dictionary")
Set Dic2 = CreateObject("Scripting.Dictionary")
ScrArr1 = Src1.Value
SrcArr2 = Src2.Value
SrcArr3 = Src3.Value
iR = 1: iC = 1
For i = 1 To UBound(ScrArr1)
If ScrArr1(i, 1) <> "" And SrcArr2(i, 1) <> "" Then
Tmp1 = ScrArr1(i, 1): Tmp2 = SrcArr2(i, 1)
If Not Dic1.Exists(Tmp1) Then
iR = iR + 1
Dic1.Add Tmp1, iR
Arr(iR, 1) = Tmp1
End If
n = WorksheetFunction.Match(Tmp1, Dic1.Keys, 0) + 1
If Not Dic2.Exists(Tmp2) Then
iC = iC + 1
Dic2.Add Tmp2, iC
Arr(1, iC) = Tmp2
End If
m = WorksheetFunction.Match(Tmp2, Dic2.Keys, 0) + 1
Arr(n, m) = Arr(n, m) + SrcArr3(i, 1)
End If
Next i
Target.Resize(iR, iC).Value = Arr
End Sub
Sub Main()
Dim Src1 As Range, Src2 As Range, Src3 As Range, Target As Range, TG As Double
TG = Timer
With Range(, .End(xlUp))
Set Src1 = .Offset(, 0)
Set Src2 = .Offset(, 1)
Set Src3 = .Offset(, 2)
End With
Set Target = Range("L1")
Transfer Src1, Src2, Src3, Target
MsgBox Format(Timer – TG, "0.000000000")
End SubVới dữ liệu 60.000 dòng thì code của em nhanh gấp đôi của sư phụ đó nha
Em nghĩ code của sư phụ bị chậm đi là do có thằng SUMPRODUCT (còn của em chỉ định vị rồi cộng dồn)
Ẹc… Ẹc…
Với file mới của sư phụ, em cải tiến code lại tí xíu, ra kết quả trong vòng chưa đầy 2s
Hồi trưa do sơ ý nên em đã dùng MATCH, giờ em sửa lại vầy:
Sub Transfer(Src1 As Range, Src2 As Range, Src3 As Range, Target As Range)
Dim Arr(1 To 60000, 1 To 200), ScrArr1, SrcArr2, SrcArr3, Dic1, Dic2, Tmp1, Tmp2
Dim i As Long, iR As Long, iC As Long
Set Dic1 = CreateObject("Scripting.Dictionary")
Set Dic2 = CreateObject("Scripting.Dictionary")
ScrArr1 = Src1.Value
SrcArr2 = Src2.Value
SrcArr3 = Src3.Value
iR = 1: iC = 1
For i = 1 To UBound(ScrArr1)
If ScrArr1(i, 1) <> "" And SrcArr2(i, 1) <> "" Then
Tmp1 = ScrArr1(i, 1): Tmp2 = SrcArr2(i, 1)
If Not Dic1.Exists(Tmp1) Then
iR = iR + 1
Dic1.Add Tmp1, iR
Arr(iR, 1) = Tmp1
End If
If Not Dic2.Exists(Tmp2) Then
iC = iC + 1
Dic2.Add Tmp2, iC
Arr(1, iC) = Tmp2
End If
Arr(Dic1.Item(Tmp1), Dic2.Item(Tmp2)) = _
Arr(Dic1.Item(Tmp1), Dic2.Item(Tmp2)) + SrcArr3(i, 1)
End If
Next i
Target.Resize(iR, iC).Value = Arr
End Sub
Sub Main()
Dim Src1 As Range, Src2 As Range, Src3 As Range, Target As Range, TG As Double
TG = Timer
With Range(, .End(xlUp))
Set Src1 = .Offset(, 0)
Set Src2 = .Offset(, 1)
Set Src3 = .Offset(, 2)
End With
Set Target = Range("L1")
Transfer Src1, Src2, Src3, Target
MsgBox Format(Timer – TG, "0.000000000")
End Sub
Xin sư phụ cho biết ý kiến
(Sư phụ có thể yên tâm về tính chính xác của kết quả, vì em đã dùng PivotTable để kiểm chứng)
Nguyên nhân vì:
– Lý ra sư phụ phải xét điều kiện <> "" cho 2 mảng trước, sau đó mới xét tính tồn tại
– Sư phụ viết vầy:
If dArr1(i, 1) <> "" And Not MyDic1.Exists(dArr1(i, 1)) Then
…
End If
If dArr2(i, 1) <> "" And Not MyDic2.Exists(dArr2(i, 1)) Then
…
End If
– Mà theo em phải vầy mới ổn
If dArr1(i, 1) <> "" And dArr2(i, 1) <> "" Then
If Not MyDic1.Exists(dArr1(i, 1)) Then
…
End If
If Not MyDic2.Exists(dArr2(i, 1)) Then
…
End If
End If
Code của bác Mỹ không cộng dồn. Bác thử copy Company 00001 xuống vài dòng sẽ thấy.
Còn của NDU có cộng dồn.
Giải thuật thì đã có rồi! Còn cách viết code theo tôi thì:
– Nên tạo 1 sub có tham số truyền, mục đích để tùy biến thoải mái khi dùng (vì dữ liệu trên từng máy đâu phải lúc nào cũng là cột A và C)
– Nên chia làm 3 mảng riêng biệt, vì thực tế đâu phải lúc nào 3 cột này cũng nằm gần nhau (ví dụ Company tại cột A, services ở cột D còn price thì nằm tận cột W)
Trong 3 yêu cầu trên, mình nghĩ tìm MAX là dễ nhất
If Arr(Dic1.Item(Tmp1), Dic2.Item(Tmp2)) < SrcArr3(i, 1) Then _
Arr(Dic1.Item(Tmp1), Dic2.Item(Tmp2)) = SrcArr3(i, 1)Còn MIN và AVERAGE thì…Ẹc… Ẹc… chẳng dễ nhai tí nào —> Có lẽ phải thêm 1 mảng phụ nữa chăng?
Hôm nay rảnh rổi ta quay lại để tài này
Thật ra cũng chỉ dợt thuật toán, chứ mấy chiêu tổng hợp này PivotTable cho tốc độ ăn đứt
Sub Transfer(Src1 As Range, Src2 As Range, Src3 As Range, Target As Range, SummaryType As String)
Dim Arr(1 To 60000, 1 To 200), ScrArr1, SrcArr2, SrcArr3, Dic1, Dic2, Tmp1, Tmp2
Dim i As Long, iR As Long, iC As Long, n As Long, m As Long
Dim TmpArr1(1 To 60000, 1 To 200), TmpArr2(1 To 60000, 1 To 200)
Set Dic1 = CreateObject("Scripting.Dictionary")
Set Dic2 = CreateObject("Scripting.Dictionary")
ScrArr1 = Src1.Value
SrcArr2 = Src2.Value
SrcArr3 = Src3.Value
iR = 1: iC = 1
For i = 1 To UBound(ScrArr1)
If ScrArr1(i, 1) <> "" And SrcArr2(i, 1) <> "" Then
Tmp1 = ScrArr1(i, 1): Tmp2 = SrcArr2(i, 1)
If Not Dic1.Exists(Tmp1) Then
iR = iR + 1
Dic1.Add Tmp1, iR
Arr(iR, 1) = Tmp1
End If
If Not Dic2.Exists(Tmp2) Then
iC = iC + 1
Dic2.Add Tmp2, iC
Arr(1, iC) = Tmp2
End If
n = Dic1.Item(Tmp1)
m = Dic2.Item(Tmp2)
Select Case SummaryType
Case Is = "Min"
If Arr(n, m) = "" Or Arr(n, m) > SrcArr3(i, 1) Then Arr(n, m) = SrcArr3(i, 1)
Case Is = "Max"
If Arr(n, m) < SrcArr3(i, 1) Then Arr(n, m) = SrcArr3(i, 1)
Case Is = "Sum"
Arr(n, m) = Arr(n, m) + SrcArr3(i, 1)
Case Is = "Average"
TmpArr1(n, m) = TmpArr1(n, m) + 1
TmpArr2(n, m) = TmpArr2(n, m) + SrcArr3(i, 1)
Arr(n, m) = TmpArr2(n, m) / TmpArr1(n, m)
End Select
End If
Next i
Target.Resize(iR, iC).Value = Arr
End Sub
Sub Main()
Dim Src1 As Range, Src2 As Range, Src3 As Range, Target As Range, TG As Double
TG = Timer
With Range(, .End(xlUp))
Set Src1 = .Offset(, 0)
Set Src2 = .Offset(, 1)
Set Src3 = .Offset(, 2)
End With
Set Target = Range("F2")
Transfer Src1, Src2, Src3, Target, .Value
MsgBox Format(Timer – TG, "0.000000000")
End SubCode này tổng hợp theo 4 kiểu: Max, Min, Sum và Average
Các bạn xem file và kiểm tra độ chính xác nhé (dùng PivotTable để kiểm tra chẳng hạn)