Học Dictionary qua các ví dụ đơn giản!

Liên hệ QC

chuot0106

Thành viên gắn bó
Tham gia
20/1/13
Bài viết
2,567
Được thích
1,670
Thực sự thì mình cũng chưa biết tên topic như vậy có hợp lí không(Nếu chưa hợp lí mong BQT sửa giúp), mình nêu mục đích của topic này luôn.
Bởi vì trên GPE đã có topic về vấn đề này rồi tuy nhiên các topic đó cũng chưa đi sâu lắm về "Dic" bản thân mình rất khó tiếp thu(cá nhân mình thôi). bởi vậy mình xin phép BQT được lập Topic mới này giành cho những người mới chập chững nghiên cứu về "Dic" như mình với mục đích chính như sau:
+ Mong các bạn có kinh nghiệm về "Dic" vào chia sẻ kinh nghiệm của bả n thân về việc học "Dic".
+ Các bạn có kinh nghiệm về "Dic" đưa ra các bài tập từ cơ bản đến đến nâng cao để các thành viên mới thực hành.
+ Các thành viên mới có thể đưa ra các câu hỏi cũng như bài tập liên quan để các thành viên có kinh nghiệm giúp đỡ.

Tất cả các mục đích này dựa trên tinh thần chia sẻ, giao lưu, học hỏi.
Rất mong nhận được sự chia sẻ của các bạn!
 
Thầy có thể cho em xin dòng code đọc ra những key có item =0 được không ạ!

Thế bạn đọc, phân tích từng dòng code của bạn Hau151978 mà bạn không hiểu à?
Mã:
For Each j In dic
    If Not (dic.Item(j)) Then
        Cells(i, 3) = j
        i = i + 1
    End If
Next

Bạn thử giải nghĩa cho tôi code trên làm gì.
Bạn cứ đi từng dòng một và tìm hiểu câu lệnh. Nếu bạn đọc code mà không hiểu được gì thì cũng có nghĩa bạn không học được gì. Cho dù bạn có học thuộc lòng code thì chỉ cần thay đổi bài toán chút ít thì bạn sẽ bó tay.

Chuyện đập xuống sheet trong mỗi vòng lặp hay đập vào mảng rồi sau vòng lặp mới đập xuống sheet chỉ là chuyện phụ. Code định làm cái gì? Lấy cái gì, từ đâu?. Nhìn thấy IF thì chắc bạn biết là việc "lấy" kia là có điều kiện, không phải lấy bừa, lấy tất tần tật.

Lập trình cũng như Toán. Phải hiểu bằng được.
 
Upvote 0
tôi tìm đúng, tức C1 thế nào thì tìm đúng, do vậy không dùng LIKE.
Do trong đề bài có ghi "Hãy liệt kê (từ C2) những thành phố chưa từng đến bởi các vị ( không phân biệt Nam béo hay Nam lác. Cứ Nam là coi là 1) có tên nhập vào C1", em hiểu là nếu dữ liệu có 2 người tên bắt đầu là Nam (ví dụ Nam Anh và Nam Em) thì đều coi là 1 nên em dùng LIKE. Code của em vẫn còn lỗi nếu dữ liệu có Nam và Namm đều coi là Nam. Để sửa lại chắc phải thay so sánh B2 với C1 B2 like (C1 & "*") ở code trên bằng B2=C1 or (B2 like (C1 & " *")). (Trước dấu * có dấu cách)
 
Upvote 0
Key là các thành phố mà bạn.

Sub cau5()
Dim r As Range
Dim dic As Scripting.Dictionary
Dim s As String, k As String
Dim i As Integer
Dim j
s = Range("c1").Text
Set dic = New Scripting.Dictionary
Set r = Range("a2", Range("b100").End(xlUp))
For i = 1 To r.Rows.Count
k = r.Cells(i, 1).Text
If Not (dic.Exists(k)) Then dic.Add k, False
If r.Cells(i, 2).Text Like (s & "*") Then dic.Item(k) = True
Next
i = 2
Range("c2:c100").ClearContents
For Each j In dic
If Not (dic.Item(j)) Then
Cells(i, 3) = j
i = i + 1
End If
Next
End Sub
Nhờ sự chỉ bảo, gợi ý của bạn Hau151978, thầy Siwtom, cùng anh HoangTrongNghia(đặc biệt là bạn Hau151978 đã rất nhiệt tình giải thích cặn kẽ) em đã hiểu được rất nhiều điều và em cũng đã hiểu được code của bạn Hau151978 và từ đó vận dụng em thử viết code nhưng dùng mảng mong các anh chị và các bạn nhận xét, góp ý!
Mã:
Public Sub cau5()
Dim arr(), kq(), i As Long, j As Variant, dic As Object
Set dic = CreateObject("Scripting.Dictionary")
arr = Sheet1.Range("A2:B8")
ReDim kq(1 To UBound(arr, 1), 1 To 1)


    For i = 1 To UBound(arr, 1)
        If Not dic.Exists(arr(i, 1)) Then dic.Add arr(i, 1), 1
            If arr(i, 2) = [C1] Then dic.Item(arr(i, 1)) = 0
    Next
       i = 1
            Range("C2:C100").ClearContents
        For Each j In dic
            If dic.Item(j) Then
                kq(i, 1) = j
                i = i + 1
            End If
        Next
        [C2].Resize(i, 1) = kq
End Sub
 
Upvote 0
"For Each j In dic" :

Theo lệ thuờng (lệ chứ không phải là luật, không bắt buộc) người ta chỉ dùng các tên i, j, k, ... cho số nguyên
Nếu bạn duyệt dict thì dùng biến tên khoa hay tenNguoi, tenTinh gì đó cho dễ đọc code.
 
Upvote 0
Về mặt thuật toán trong bài này, mình thấy khó nhất khi duyệt bảng để lấy dữ liệu vào dic. Khi duyệt đến một hàng, ví dụ A3:B3, có các trường hợp xảy ra:
- Đã có key = A3 và B3=C1 ----> gán item =1
- Đã có key = A3 và B3<>C1 -----> không làm gì.
- Chưa có key = A3 và B3=C1 ------>tạo key A3, item=1
- Chưa có key =A3 và B3<>C1----->tạo key A3, item=0
Lợi dụng chức năng của dic là nếu truy xuất 1 key không tồn tại thì dic sẽ add key với item rỗng. Vậy có thể rút gọn 2 lệnh: if not(dic.exists(k))... và If B3=C2 ở bài của mình và lệnh if lồng của bác Siwtom thành 1 lệnh:
If (dic.item(k)<>1) and (r.Cells(i, 2).Text = s) then dic.item(k)=1
Mình không dùng dic.item(k)=0 vì có khả năng dic.item(k)="". Như vậy câu lệnh sẽ ngắn gọn hơn mặc dù khó hiểu hơn. Câu lệnh kiểm tra ở vòng lặp For each sửa thành if dic.item(j)<>1 then...
 
Lần chỉnh sửa cuối:
Upvote 0
Đi xa mục tiêu đề bài. Xin phép xoá để khỏi làm loãng thớt. Xin lỗi.
 
Lần chỉnh sửa cuối:
Upvote 0
Ở phần trọng tâm thì tôi làm giống bạn Hau151978, chỉ khác về chi tiết. Tức như đã nói tôi cho Item = 0/1. Tôi dùng IF ... Else thay cho 2 IF. Và tôi tìm đúng, tức C1 thế nào thì tìm đúng, do vậy không dùng LIKE.
Mã:
name = LCase([C1].Value)
...
Set dic = CreateObject("Scripting.Dictionary")
For k = 1 To UBound(Arr)
    If LCase(Arr(k, 2)) <> name Then
        If Not dic.exists(Arr(k, 1)) Then dic.Add Arr(k, 1), 0    ' <-- A
    Else
        dic.Item(Arr(k, 1)) = 1                ' <-- B
    End If
Next

Ngoài ra còn: xóa dữ liệu cũ, nếu cột A không có dữ liệu thì không làm gì cả. Nhưng những cái này không thuộc trọng tâm bài Toán, tôi chỉ ra bài về dic thôi.

Đã có lời giải nhưng không phải của bạn chuot0106, vậy thì những câu hỏi sau đây là dành cho bạn chuot0106. Yêu cầu người khác không trả lời, vì thực ra câu hỏi dễ, chỉ dành cho người đang "vọc" thôi.

1. Tại sao tôi không dùng ở A cấu trúc giống như ở B, tức
Mã:
dic.Item(Arr(k, 1)) = 0

2. Tại sao tại A và B tôi không dùng cấu trúc
Tại A
Mã:
If Not dic.exists(Arr(k, 1)) Then 
    dic.Item(Arr(k, 1)) =[B] [COLOR=#ff0000]0[/COLOR][/B]
else
    dic.Item(Arr(k, 1)) =[B] [COLOR=#ff0000]0[/COLOR][/B]
end if

Tại B
Mã:
If Not dic.exists(Arr(k, 1)) Then 
    dic.Item(Arr(k, 1)) = [COLOR=#ff0000][B]1[/B][/COLOR]
else
    dic.Item(Arr(k, 1)) = [COLOR=#ff0000][B]1[/B][/COLOR]
end if
Dùng tại A như trên được không? Dùng tại B như trên được không? Nếu được/không được (cùng lắm thì chạy code là biết) thì giải thích tại sao được/không được.
Em xin trả lời câu hỏi của thầy theo ý hiểu của em:
(1) Không thể dùng cấu trúc ở A giống ở B, vì ở A là gán item bằng 0 cho các key, còn cấu trúc ở B là em nghĩ là để thay đổi item (=1) cho các key thỏa điều kiện nào đó.
(2) thì không biết thầy có gõ nhầm không ạ? Em thấy trước và sau ELSE em thấy đều cùng là 1 số (0,1).
 
Upvote 0
Em xin trả lời câu hỏi của thầy theo ý hiểu của em:
(1) Không thể dùng cấu trúc ở A giống ở B, vì ở A là gán item bằng 0 cho các key, còn cấu trúc ở B là em nghĩ là để thay đổi item (=1) cho các key thỏa điều kiện nào đó.
(2) thì không biết thầy có gõ nhầm không ạ? Em thấy trước và sau ELSE em thấy đều cùng là 1 số (0,1).
(1) không được vì nếu key đã có, item bằng 1 từ trước thì lệnh gán item=0 sẽ sai.
(2) tại A nếu thay đoạn mã như vậy thì như tình huống ở (1) lệnh gán item=0 ở Else sẽ sai; nếu sửa thành item=1 vẫn sai trong trường hợp key đã có từ trước và item cũ =0
tại B viết như vậy cũng được nhưng thừa vì cả 2 lệnh ở If và Else giống nhau nên có thể bỏ If.
Đoạn

If LCase(Arr(k, 2)) <> name Then
If Not dic.exists(Arr(k, 1)) Then dic.Add Arr(k, 1), 0 ' <-- A
Else
dic.Item(Arr(k, 1)) = 1 ' <-- B
End If

có thể sửa thành if (dic.item(arr(k,1)) <>1) and (Lcase(arr(k,2))=name) then dic.item(arr(k,1))=1
 
Lần chỉnh sửa cuối:
Upvote 0
(1) không được vì nếu key đã có, item bằng 1 từ trước thì lệnh gán item=0 sẽ sai.
(2) tại A nếu thay đoạn mã như vậy thì như tình huống ở (1) lệnh gán item=0 ở Else sẽ sai; nếu sửa thành item=1 vẫn sai trong trường hợp key đã có từ trước và item cũ =0
tại B viết như vậy cũng được nhưng thừa vì cả 2 lệnh ở If và Else giống nhau nên có thể bỏ If.

1. Đúng. Phần else sẽ làm sai kết quả
2. Chết thật, he he. Tôi viết nhầm (cứ kiểu copy/paste nên nhầm)

Phải là.
Tại A
Mã:
If Not dic.exists(Arr(k, 1)) Then 
    dic.Add Arr(k, 1), 0
else
    dic.Item(Arr(k, 1)) = 0
end if

Tại B
Mã:
If Not dic.exists(Arr(k, 1)) Then 
    dic.Add Arr(k, 1), 1
else
    dic.Item(Arr(k, 1)) = 1
end if

Tôi biết là câu hỏi dễ nên chỉ hỏi bạn chuot0106 thôi mà. Nhưng bạn đã trả lời thì làm nốt cho bạn chuot0106 học tập.

Đoạn

If LCase(Arr(k, 2)) <> name Then
If Not dic.exists(Arr(k, 1)) Then dic.Add Arr(k, 1), 0 ' <-- A
Else
dic.Item(Arr(k, 1)) = 1 ' <-- B
End If

có thể sửa thành if (dic.item(arr(k,1)) <>1) and (Lcase(arr(k,2))=name) then dic.item(arr(k,1))=1


Nhiều khi biết là gộp được nhưng không gộp vì có thể nhìn code tối hơn, phải chú ý cẩn thận vì dễ sai hơn. Nhưng đây là tùy mỗi người. Thói quen thôi.

Ngoài ra Excel tính biểu thức lôgíc theo kiểu tính tất cả các biểu thức thành phần.

Mã:
IF a then
    if b then
        if c then
             bla
        end if
    end if
End if

và
Mã:
If a and b and c then
    ' bla
end If

bla ở trên chỉ chạy khi a, b, c đều trả về TRUE. Nhưng nếu a = FALSE thì code sẽ không làm gì, bất luận b, c thế nào. Ở code1 thì chỉ đk a được tính (khi a = FALSE). Ở code2 thì khi tính xong biết a = FALSE nhưng Excel vẫn tính tiếp biểu thức lôgíc b và c.

Bạn có thể mục sở thị. Hãy chạy 2 code
Mã:
Sub he1()
Dim dic As Object
    If Not dic Is Nothing Then
        If dic.exists("he he") Then
            dic.Item("he he") = "bla"
        End If
    End If
End Sub

Sub he2()
Dim dic As Object
    If Not dic Is Nothing And dic.exists("he he") Then
        dic.Item("he he") = "bla"
    End If
End Sub

code he1 chạy không lỗi vì sau khi kiểm tra đk 1 thì code không làm gì cả. code he2 chạy có lỗi vì VBA tính tất cả 2 đk. Vậy khi dic = Nothing thì trong quá trình kiểm tra đk 2 sẽ có lỗi.
--------------
Delphi chẳng hạn chỉ tính đk tới khi đã biết kết quả của biểu thức lôgíc.
vd. A = a and b and c and d and e
Khi Delphi tính và thấy a = FALSE thì cũng biết là A = FALSE vậy b, c, d không được tính nữa.

vd. A = a or b or c or d
Nếu a = FALSE, b = FALSE, c = TRUE thì Delphi không tính d mà trả về luôn A = TRUE.

Cách tính của VBA như thế là không tối ưu. Và với cách tính đó bạn không viết gọn được như ở he2
 
Lần chỉnh sửa cuối:
Upvote 0
Em xin trả lời câu hỏi của thầy theo ý hiểu của em:
(1) Không thể dùng cấu trúc ở A giống ở B, vì ở A là gán item bằng 0 cho các key, còn cấu trúc ở B là em nghĩ là để thay đổi item (=1) cho các key thỏa điều kiện nào đó.
Ý là nếu bạn cố tình viết như thế thì có được không. Nếu được/không được thì tại sao. Phải trả lời như bạn Hau151978

(2) thì không biết thầy có gõ nhầm không ạ? Em thấy trước và sau ELSE em thấy đều cùng là 1 số (0,1).

Đúng là nhầm. Đọc bài #109 nhé
 
Upvote 0
Đọc qua các bài trên em chỉ hiểu sơ lược
Để em có thể áp dụng vào kế toán thì em nhờ các anh chị viết code về Dic để em học hỏi thêm
Trong file của em có 2 bài tập
Dựa vào sổ Nhật ký để tổng hợp ra bảng cân đối
Nếu được thì có thể giải thích sơ lược cho em các code mà anh /chị đã viết
BaiTap_1: Tổng hợp không cần ngày tháng
BaiTap_2: Tổng hợp Theo tháng
Em cảm ơn
 

File đính kèm

  • BaiTap.Dic.xlsx
    10.3 KB · Đọc: 17
Upvote 0
Muốn học thì nên tự làm đi, mắc ở đâu đưa lên. Còn đưa file xlsx nghĩa là nhờ mọi người làm tất?
 
Upvote 0
Đọc qua các bài trên em chỉ hiểu sơ lược
Để em có thể áp dụng vào kế toán thì em nhờ các anh chị viết code về Dic để em học hỏi thêm
Trong file của em có 2 bài tập
Dựa vào sổ Nhật ký để tổng hợp ra bảng cân đối
Nếu được thì có thể giải thích sơ lược cho em các code mà anh /chị đã viết
BaiTap_1: Tổng hợp không cần ngày tháng
BaiTap_2: Tổng hợp Theo tháng
Em cảm ơn
Thử Bài tập 2.
Mã:
Sub baitap2()
    Dim arr, i As Long, lr As Long, a As Long, kq, dic As Object, dk As String, b As Long
    Set dic = CreateObject("scripting.dictionary")
    With Sheets("Baitap_2")
         lr = .Range("A" & Rows.Count).End(xlUp).Row
         arr = .Range("A3:E" & lr).Value
         ReDim kq(1 To UBound(arr), 1 To 3)
         For i = 1 To UBound(arr)
           If Month(arr(i, 1)) = 2 Then
              dk = arr(i, 3)
              If Not dic.exists(dk) Then
                 a = a + 1
                 dic.Add dk, a
                 kq(a, 1) = dk
              End If
              b = dic.Item(dk)
              kq(b, 2) = kq(b, 2) + arr(i, 5)
              dk = arr(i, 4)
              If Not dic.exists(dk) Then
                 a = a + 1
                 dic.Add dk, a
                 kq(a, 1) = dk
              End If
              b = dic.Item(dk)
              kq(b, 3) = kq(b, 3) + arr(i, 5)
           End If
         Next i
         .Range("m3:O1000").ClearContents
        If a Then .Range("m3:o3").Resize(a).Value = kq
   End With
End Sub
 
Upvote 0
Cảm ơn bạn
Code của bạn tạo tất cả từ số Tài khoản đến số tiền
Ở đây bảng cân đối là "cố định" và có sẵn cột từ I3 đến I6
Nếu tài khoản nào không phát sinh số tiền thì để trống
Mình cũng không biết phải tạo Dic như thế nào và gián kết quả vào J3:K6
 
Upvote 0
Cảm ơn bạn
Code của bạn tạo tất cả từ số Tài khoản đến số tiền
Ở đây bảng cân đối là "cố định" và có sẵn cột từ I3 đến I6
Nếu tài khoản nào không phát sinh số tiền thì để trống
Mình cũng không biết phải tạo Dic như thế nào và gián kết quả vào J3:K6
Bạn muốn học về dic hay là muốn lấy kết quả như vậy.
 
Upvote 0
Vừa muốn vọc mà cũng muốn kế quả như vậy
Vì thực tế là 1 sẵn, họ yêu cầu mình điền vào
Vậy mình nói ý tưởng bạn thực hiện nhé.
Đầu tiên là duyệt qua các phần tử của mảng kết quả có sẵn lấy vị trí của nó trong mảng đó bằng dictionary.Xong tiếp đến ta duyệt các phần tử ở data đã cho.Xem cái nào có ở bên vị trí mảng kết quả bằng cách kiểm tra dic.Rồi công các vị trí đó lại với nhau là xong.Bạn làm thử nếu trong ngày mai chưa xong mình viết code nhé.
 
Upvote 0
Thế bạn đọc, phân tích từng dòng code của bạn Hau151978 mà bạn không hiểu à?
Mã:
For Each j In dic
    If Not (dic.Item(j)) Then
        Cells(i, 3) = j
        i = i + 1
    End If
Next

Bạn thử giải nghĩa cho tôi code trên làm gì.
Bạn cứ đi từng dòng một và tìm hiểu câu lệnh. Nếu bạn đọc code mà không hiểu được gì thì cũng có nghĩa bạn không học được gì. Cho dù bạn có học thuộc lòng code thì chỉ cần thay đổi bài toán chút ít thì bạn sẽ bó tay.

Chuyện đập xuống sheet trong mỗi vòng lặp hay đập vào mảng rồi sau vòng lặp mới đập xuống sheet chỉ là chuyện phụ. Code định làm cái gì? Lấy cái gì, từ đâu?. Nhìn thấy IF thì chắc bạn biết là việc "lấy" kia là có điều kiện, không phải lấy bừa, lấy tất tần tật.

Lập trình cũng như Toán. Phải hiểu bằng được.
Một ông chuyên về lập trình, 1 ông chuyên về cái khác, e rằng sẽ rất khó, nó sẽ chỉ là học lỏm thôi
 
Upvote 0
Cảm ơn bạn
Code của bạn tạo tất cả từ số Tài khoản đến số tiền
Ở đây bảng cân đối là "cố định" và có sẵn cột từ I3 đến I6
Nếu tài khoản nào không phát sinh số tiền thì để trống
Mình cũng không biết phải tạo Dic như thế nào và gián kết quả vào J3:K6
Bạn thử nhé.
Mã:
Sub baitap2()
    Dim arr, i As Long, lr As Long, a As Long, data, dic As Object, dk As String, b As Long, lr1 As Long
    Set dic = CreateObject("scripting.dictionary")
    With Sheets("Baitap_2")
         lr = .Range("I" & Rows.Count).End(xlUp).Row
         If lr < 3 Then Exit Sub
         .Range("J3:K" & lr).ClearContents
         arr = .Range("I3:K" & lr).Value
         For i = 1 To UBound(arr)
             dk = arr(i, 1)
             dic.Item(dk) = i
         Next i
         lr1 = .Range("I" & Rows.Count).End(xlUp).Row
         data = .Range("A3:E" & lr1).Value
         For i = 1 To UBound(data)
             If Month(data(i, 1)) = 2 Then
                dk = data(i, 3)
                a = dic.Item(dk)
                If a Then
                   arr(a, 2) = arr(a, 2) + data(i, 5)
                End If
                dk = data(i, 4)
                a = dic.Item(dk)
                If a Then
                   arr(a, 3) = arr(a, 3) + data(i, 5)
                End If
             End If
        Next i
        .Range("I3:K" & lr).Value = arr
   End With
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Cảm ơn bạn rất nhiều, để mình nghiên cứu cái gì chưa hiểu thì sẽ hỏi
 
Upvote 0
Web KT
Back
Top Bottom