Dùng vòng lặp để tách mỗi lần 3 giá trị của 1 mã cho đến hết

Liên hệ QC
Tôi tuân thủ nội quy khi đăng bài

Quangdz0512

Thành viên mới
Tham gia
29/7/23
Bài viết
40
Được thích
21
Hiện em có 1 tính huống, mong muốn dùng vòng lặp để tách mỗi lần 3 serial của 1 mã hàng mang đi, nếu số lượng serial không chia hết cho 3 thì lần cuối cùng sẽ lấy số còn lại.
Chi tiết như ví dụ đính kèm.
Em không chú trọng vào việc để dữ liệu vào đâu, nên việc trình bày kết quả mong muốn này tại sheet1 đó chỉ là minh họa, vì mỗi lần mang đi đó em đưa vào nhiều file khác nhau và bố trí không theo quy tắc nào cả. Chỉ mong muốn vòng lặp cho mỗi mã, hết mã này tới mã khác.
Em cảm ơn.
 

File đính kèm

  • Vong lap.xlsx
    10.2 KB · Đọc: 25
Dân GPE này có thói quen "sợ" số vòng lặp. Đọc nhiều bài thấy rõ ràng có thách thức nhau, hoặc nổ lực không ít để giảm số vòng lặp.

Theo tôi hiểu thì bài toán chỉ thâu vào "mã/serials từ dạng dòng crosstab/pivot dạng cột". Chỉ hơi đổi là thay vì mỗi mã 1 dòng thì ở đây mỗi mã được 3 dòng.
Bài này vốn dùng mảng bộ nhớ (mảng cấu trức trong bộ nhớ), truy vấn rất nhanh. Nếu không bị "mặc cảm vòng lặp" thì dùng chính sách "đọc 2 lần" sẽ giản dị hóa được rất nhiều.

' ví dụ đã nạp dữ liệu thô vào mảng a

' bắt đầu gầy mảng tách từng mã
rowsA = UBound(a)
Redim a1(1 To rowsA, 1 To 2) ' 1: dòng đầu, 2: dòng cuối của mã
curMa = ""
numMa = 0
For i = 1 to rowsA
If a(i, 1) <> "" And a(i, 1) <> curMa Then
numMa = numMa + 1
a1(numMa, 1) = i
End If
a1(numMa, 2) = i
Next i

' chép ra
Const GROUPSIZE = 3
For i = 1 To numMa
numSer = a2(i, 2) - a2(i, 1) + 1 ' tổng số serials
colsSer = (numSer + GROUPSIZE - 1) \ GROUPSIZE
Redim a3(1 To GROUPSIZE, 1 To colsSer)
For i2 = 1 To numSer
a3(((i2 -1) Mod GROUPSIZE + 1), (i2 + GROUPSIZE - 1) \ GROUPSIZE) _
= a(a2(i, 1) + i2 - 1) ' serials
Next i2
' code dọn chỗ ghi ở đây, giả sử ô ghi là RgGhi
' thớt có nói ghi vào đâu không quan trọng
' chả tội gì phải có code tìm chỗ
RgGhi = a(a2(i, 1), 1) ' mã
RgGhi.Offset(0, 1).Resize(UBound(a3,1), UBound(a3,2)).Value = a3
Next i
 
Lần chỉnh sửa cuối:
Upvote 0
Vâng, có thể do văn phong lủng củng nên em mô tả chưa rõ ạ.
Các đích đến của em có thể là nhiều file, nhiều sheet khác nhau.
Anh có cách nào cho lặp như thế này không ạ.

Lặp qua mỗi Mã,
Mã A - Đếm xem A có bao nhiêu Serial, nếu > 3 serial thì copy số lần là INT(Số lượng serial/3) +1. Trong đó các lần trước là 3 serial/lần, lần cuối cùng là số serial = Mod(Số lượng serial,3)

Ví dụ mã A có 4 mã, sẽ phải copy 2 lần. Lần 1 lấy 3 mã dán vào ô N2, lần 2 có 1 mã dán vào ô O4

Tiếp tục đến mã B và tương tự lặp đến hết các mã.
Nếu số lượng serial của 1 mã mà < 3 thì chỉ copy 1 lần dán vào ô Q3

Với trường hợp muốn kiểm tra thực tế, thì cần thử với 2 mã A và B thôi ạ, vì đích đến sẽ để ở 3 ô cho dễ hình dung theo file đính kèm ạ.
Hay kiểu bạn muốn là thế này (Chạy code abc):
Mã:
Option Explicit

Private Sub CopyItem(a(), b(), r&, Optional rs& = 3)
Dim i&, u&, ch$, txt$, id$
ch = Chr(0)
u = UBound(a)
id = a(r, 1)
For i = r To u
    If i = r + rs Or (a(i, 1) <> "" And a(i, 1) <> id) Then Exit For
    txt = txt & ch & a(i, 2)
Next
r = i
txt = Mid(txt, 2)
b = Application.Transpose(Split(txt, ch))
End Sub

Sub abc()
Dim a(), b(), r&, k&
Const rs& = 3
With Sheets("Sheet1")
    a = .Range("A4:B" & .Cells(Rows.count, "B").End(xlUp).Row).Value
    r = 1
    Do
        CopyItem a, b, r
        MsgBox "Gia tri: " & vbNewLine & Join(Application.Transpose(b), "_")
'        Ghi du lieu o dau thi tu tinh toan de ghi, duoi day la vi du
'        k = k + 3
'        .Cells(k, "M").Resize(UBound(b)).Value = b
    Loop Until r >= UBound(a)
End With
End Sub
 
Upvote 0
Bài này kéo dài thế mà chưa hiểu được ý cuối cùng của chủ bài cụ thể là như thế nào.
 
Upvote 0
Bài này kéo dài thế mà chưa hiểu được ý cuối cùng của chủ bài cụ thể là như thế nào.
Em muốn làm xong mã này rồi mới đến mã khác.
Do với mỗi mã, sau khi copy đủ số lần em còn thao tác nhiều thao tác khác xong rồi mới quay ra mã tiếp theo ạ.
 
Upvote 0
Em muốn làm xong mã này rồi mới đến mã khác.
Do với mỗi mã, sau khi copy đủ số lần em còn thao tác nhiều thao tác khác xong rồi mới quay ra mã tiếp theo ạ.
Mô tả như bạn thì có vẻ mình bạn hiểu. Thao tác khác là thao tác gì. Code trên nó liền mạch với nhau. Bạn lại muốn ngắt mạch thêm thao tác khác mà không nói cụ thể thì ai giúp được.
 
Upvote 0
Rất cảm ơn các anh đã nhiệt tình hỗ trợ em, ngày nghỉ mà không nghỉ trưa để giải giúp em tình huống này.
Sau khi tham khảo các bài giải, với nhu cầu của em thì bài số #7 của anh @Nhattanktnn là phù hợp ạ.
Tức em dùng đáp án của anh làm 1 sub con để ra bảng trung gian.

Em muốn dùng 2 vòng lặp lồng vào nhau
1. Vòng lặp cho các mã
2. Vòng lặp để chỉ ra số lần copy (chắc vẫn phải giới hạn số lượng serial của 1 mã, 3 mã 1 lần em sẽ giới hạn là 10 lần copy)

Nhưng giờ em lại vướng ạ
1. Dữ diệu ở bảng trung gian đó, các mã đang cách nhau nên không dùng Do While ... Loop được
2. Số lần tìm bằng cách CountA dòng đầu tiên của mã đó trong 10 cột giới hạn

Mong các anh giúp đỡ ạ.
Bài đã được tự động gộp:

Mô tả như bạn thì có vẻ mình bạn hiểu. Thao tác khác là thao tác gì. Code trên nó liền mạch với nhau. Bạn lại muốn ngắt mạch thêm thao tác khác mà không nói cụ thể thì ai giúp được.
Cụ thể là em phải copy số lượng serial của mỗi mã vào 1 phần mềm khác. Mà tại đó nó giới hạn có 3 dòng/lần.
Nếu các mã có số lượng serial nhiều hơn 3 thì phải copy làm nhiều lần.
Copy xong 1 mã, em phải bấm hoàn thành mã đó tại phần mềm, sau đó tại phần mềm đó mở tiếp mã B và làm tương tự ạ.
 

File đính kèm

  • Vong lap.xlsb
    22.3 KB · Đọc: 5
Upvote 0
Cụ thể là em phải copy số lượng serial của mỗi mã vào 1 phần mềm khác. Mà tại đó nó giới hạn có 3 dòng/lần.
Nếu các mã có số lượng serial nhiều hơn 3 thì phải copy làm nhiều lần.
Copy xong 1 mã, em phải bấm hoàn thành mã đó tại phần mềm, sau đó tại phần mềm đó mở tiếp mã B và làm tương tự ạ.
Coi như bạn thử hên xui. Hi vọng đúng kết quả bạn mong muốn
Mã:
Sub XYZ()
    Dim Dic As Object, sArr(), Res(), i&, iR&, Key, n&, S
    Dim sRes(), c&, ii&, k&, cMax&, R&: n = 1
    Set Dic = CreateObject("scripting.dictionary")
    With Sheets("Sheet1")
        iR = .Range("A" & Rows.count).End(3).Row
        sArr = .Range("A4:B" & iR).Value
        For i = 1 To UBound(sArr)
            If sArr(i, 1) = Empty Then sArr(i, 1) = sArr(i - 1, 1)
            Dic(sArr(i, 1)) = Dic(sArr(i, 1)) & "|" & sArr(i, 2)
        Next
        ReDim Res(1 To Dic.count * 3, 1 To 1000)
        ReDim sRes(1 To Dic.count * 3, 1 To 2)
        For Each Key In Dic.keys
            Res(n, 1) = Key: sRes(n, 1) = "So lan": c = 1
            S = Split(Dic.Item(Key), "|")
            If UBound(S) > 3 Then
                For i = 1 To UBound(S)
                    If i Mod 3 = 1 Then c = c + 1: k = 1 Else k = k + 1
                    Res(k + n - 1, c) = S(i): If R < k Then R = k
                Next
                sRes(n, 2) = c - 1
            Else: c = 2
                For i = 1 To UBound(S)
                    k = k + 1
                    Res(k + n - 1, c) = S(i)
                Next
                R = k: sRes(n, 2) = 1
            End If
            n = n + R:  R = 0: k = 0: If c > cMax Then cMax = c
        Next
        .Range("K3").Resize(10000, 1000).ClearContents
        .Range("N3").Resize(UBound(Res), cMax).Value = Res
        .Range("K3").Resize(UBound(Res), 2).Value = sRes
    End With
End Sub
1690627400229.png
 
Upvote 0
Kiểu thớt biết code mà ương ương thế này là toàn phải đoán mò mẫm à. Không gì là chắc chắn luôn. Thôi cứ thêm cách cho thớt lựa chọn anh ạ
Cái này là do cách diễn giải thôi, mỗi lần thò thêm một chút mà lại không cụ thể, không đồng bộ theo trình tự từ khi bắt đầu đến khi kết thúc nó mới rắc rối vậy.
 
Upvote 0
Coi như bạn thử hên xui. Hi vọng đúng kết quả bạn mong muốn
Mã:
Sub XYZ()
    Dim Dic As Object, sArr(), Res(), i&, iR&, Key, n&, S
    Dim sRes(), c&, ii&, k&, cMax&, R&: n = 1
    Set Dic = CreateObject("scripting.dictionary")
    With Sheets("Sheet1")
        iR = .Range("A" & Rows.count).End(3).Row
        sArr = .Range("A4:B" & iR).Value
        For i = 1 To UBound(sArr)
            If sArr(i, 1) = Empty Then sArr(i, 1) = sArr(i - 1, 1)
            Dic(sArr(i, 1)) = Dic(sArr(i, 1)) & "|" & sArr(i, 2)
        Next
        ReDim Res(1 To Dic.count * 3, 1 To 1000)
        ReDim sRes(1 To Dic.count * 3, 1 To 2)
        For Each Key In Dic.keys
            Res(n, 1) = Key: sRes(n, 1) = "So lan": c = 1
            S = Split(Dic.Item(Key), "|")
            If UBound(S) > 3 Then
                For i = 1 To UBound(S)
                    If i Mod 3 = 1 Then c = c + 1: k = 1 Else k = k + 1
                    Res(k + n - 1, c) = S(i): If R < k Then R = k
                Next
                sRes(n, 2) = c - 1
            Else: c = 2
                For i = 1 To UBound(S)
                    k = k + 1
                    Res(k + n - 1, c) = S(i)
                Next
                R = k: sRes(n, 2) = 1
            End If
            n = n + R:  R = 0: k = 0: If c > cMax Then cMax = c
        Next
        .Range("K3").Resize(10000, 1000).ClearContents
        .Range("N3").Resize(UBound(Res), cMax).Value = Res
        .Range("K3").Resize(UBound(Res), 2).Value = sRes
    End With
End Sub
View attachment 293283
Đúng ý em rồi ạ, em sẽ dùng kết quả này làm trung gian để em thực hiện 1 Thủ tục khác dưới đây

Mã:
Sub Tim_Ma()
Dim WS As Worksheet
Dim Match As Range
Dim Ma()
Dim i As Integer
Dim j As Integer
Dim Solan As Integer

Set WS = ThisWorkbook.Sheets("Sheet1")
Ma = ThisWorkbook.Sheets("Sheet1").Range("G3", [G5000].End(3)).Value

For i = 1 To UBound(Ma)
    Set Match = WS.Range("M3:M9").Find(What:=Ma(i, 1), LookIn:=xlValues, _
        LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
        MatchCase:=False, SearchFormat:=False)
        
        If Not Match Is Nothing Then
            FirstAddress = Match.Address
            Match.Activate
            Solan = ActiveCell.Offset(0, -1).Value
            MsgBox Solan 'Em de chi de kiem tra code, trong thuc te se xoa di
            
            If Solan = 1 Then
                ActiveCell.Offset(0, 1).Resize(Solan, 1).Copy Range("X1") 'Phan dich den chi la vi du
                'cac lenh khac cua phan mem
                'Dong giao dich 1 ma
                
            Else
                For j = 1 To Solan
                    ActiveCell.Offset(0, 1).Resize(1, 2).Copy Range("Y1") 'Phan dich den chi la vi du
                    'cac lenh khac cua phan mem
                    j = j + 1
                    ActiveCell.Offset(0, j).Resize(1, 2).Copy Range("Z1") 'Phan dich den chi la vi du
                    'cac lenh khac cua phan mem
                Next j
                    'Dong giao dich 1 ma
            End If
        End If  
Next
End Sub

Giờ em muốn với các mã có Số lần là 1, thì trả về kết quả tổng số Serial của mã đó ở cột K thì làm thế nào ạ?
 

File đính kèm

  • Vong lap.xlsb
    22.8 KB · Đọc: 7
Upvote 0
Mình không có ngồi máy. Bạn có thể chờ thành viên khác giúp đỡ coi
Trong code bài #28 (sub XYZ), bạn dùng khá nhiều con số 3 (tức côn số dòng quy định của thớt).
Tình trạng này dân chuyên nghiệp code gọi là "magic numbers" (trị hằng từ trên trời rớt xuống). Bạn đọc tài liệu lập trình nâng cao sẽ thấy họ luôn luôn khuyên nên dùng một hằng, như trong bài #21 tôi gọi nó là:
Const GROUPSIZE = 3 ' tôi theo trường phái tên hằng viết hoa cả cụm

Phần sau đây nếu bạn thích thì đọc, khpopng thì bỏ qua:
Nên bỏ thói quen bắt chước người ta dùng ký hiệu dấu hai chấm ":" để nối nhiều lệnh vào một dòng.
Kỹ thuật giảm số dòng không hẳn là kỹ thuật tốt.
Ngày xưa, thập niên 70-80, tài nguyên máy tính rất ít, mối dòng của Basic thường cần khoảng 12 bytes để định địa chỉ. Nối lệnh tiết kiệm được những địa chỉ này.
Nhưng đó là thời thượng cổ. Thời mà máy PDP-11 (DEC), chiếc máy kinh điển trong ngành giáo dục ĐH Mẽo, chỉ có 16 bit địa chỉ. Tức là chỉ chạy được địa chỉ xấp xỉ 32 K (có nhiêu ấy mà nó vẫn chạy được, thế mới gọi là kinh điển).
Qua thập niên 90, CPU (điển hình của Intel) trong máy PC đã 32 bit địa chỉ, tức là hơn 2 tỷ đơn vị. Việc tiết kiệm hàng chục Bytes là việc điên rổ. Hệ thống 64 bit có bao nhiêu đơn vị địa chỉ, không cần nói ra.
Basic cũng tiến hóa theo. Việc dùng số là label cho mỗi dòng lệnh không còn. Việc siwr dụng phép nối lệnh cũng không được khuyến khích. VBA giữ phép ấy là để compatible với code thượng cổ, và cacxs code từ QBasic đổi qua. Cái vụ QBasic này thì quý vị có thể kết tội lão Bill Gates. Vì đó là con cưng của lão. Khác với các tính chất của Excel quý vị thường đổ cho lão là oan.

Túm lại, gom lệnh là một việc hoàn toàn không cần thiết, mà lại làm cho đọc code không mạch lạc. Điển hình, khi đọc code và cần xem lại một vài dòng trước đó, tôi chỉ việc dò ngược trở lên. Với code có gom lệnh, còn phải dò qua bên phải, rất dễ bị sót.
 
Upvote 0
Mình vẫn xài ký tự ':' ngõ hầu đưa toàn bộ macro lên màn hình vi tính không cần con trượt nếu có thể
Mình vẫn xài đánh số dòng lệnh để nhờ hàm Erl() cho biết dòng đang lỗi khi lỗi đang sảy ra mà chưa biết do dòng nào
Đánh số dòng lệnh còn xài khi dịch dòng lệnh sang tiếng Việt để sếp hay thượng đế hiểu

Thói quen này tương đối cổ hủ, đành vậy vì chưa biết cách nào khác hơn,
 
Upvote 0
Hồi xưa, màn hình dạng windows là xa xỉ phẩm.
Có ai nhớ mấy cái màn hình thời DOS ?
Số dòng lệnh dùng để sắp xếp thứ tự lệnh. Các phiên bản Basic thời đó đều mặc định dòng N chạy trước dòng N+M, trước dòng N+M+L.
Ví dụ tôi có
100 a = b+c
200 x = a/10
Nếu tôi muốn thêm a = 2*a trước khi tính x thì sao?
Với màn hình Windows, tôi chỉ việc gọi Editor lên và gõ chen vào giữa dòng 100 và 200
Với màn hình DOS, tôi gõ
150 a = 2*a
Basic tự động chen 150 vào giữa 100 và 200

MS khi ra Visual Basic là đã muốn sử dụng Windows,

Bây giờ bẫy lỗi bằng kỹ thuật try-catch, debug bằng cửa sổ debug. Dùng dòng lệnh cổ quá rồi.

Có thể những quý vị lớn tuổi thì thích giữ đổ cổ và không cần đến đồ mới. Cái đó tôi không bàn tới.

Nhưng để cho lớp trẻ đi theo đường lối cổ là có hại cho tiền đồ của chúng. Đối với tôi, bọn trẻ chỉ cần biết qua đồ cổ thôi, chứ sử dụng thì phải luôn cầu cái mới.
 
Upvote 0
Mình thường dùng ":" để nhóm các lệnh để dể nhìn và dể kiểm soát ví dụ
Msgbox("Khong co du lieu ...") : exit sub
res(i,1)= 1 : res(i,2)="a"
ít dòng lệnh dể nhìn hơn nhiều dòng
 
Upvote 0
Mình thường dùng ":" để nhóm các lệnh để dể nhìn và dể kiểm soát ví dụ
Msgbox("Khong co du lieu ...") : exit sub
res(i,1)= 1 : res(i,2)="a"
ít dòng lệnh dể nhìn hơn nhiều dòng
Tôi có giải thích rồi.
Tôi đọc từ trên xuống, gặp Msgbox("Khong co du lieu ...") : exit sub thì tôi biết có Exit Sub giữa chừng.
Tôi đọc 20 dòng nữa, cảm thấy cần check lại cái gì đó, tôi đọc ngược trở lên.
Trong lượt đọc ngược này, nếu tôi muốn nhanh thì chỉ liếc sơ qua bên trái cùng, tìm được chỗ mình muốn check.

Gặp code có phép gán hoặc lệnh rẽ nhánh sau dấu ":", thì tôi phải đọc cả bên phải.

Ít dòng lệnh chưa chắc dễ đọc hơn.
a(1) = a(1) + 5: a(2) = a(2) + 15: a(4) = a(4) + 45
không hề dễ đọc hơn
a(1) = a(1) + 5
a(2) = a(2) + 15
a(4) = a(4) + 45
Theo tôi thì khả năng người đọc nhận ra tôi cố tình bỏ qua a(3) hơi thấp với kiểu 1.
 
Lần chỉnh sửa cuối:
Upvote 0
Coi như bạn thử hên xui. Hi vọng đúng kết quả bạn mong muốn
Mã:
Sub XYZ()
    Dim Dic As Object, sArr(), Res(), i&, iR&, Key, n&, S
    Dim sRes(), c&, ii&, k&, cMax&, R&: n = 1
    Set Dic = CreateObject("scripting.dictionary")
    With Sheets("Sheet1")
        iR = .Range("A" & Rows.count).End(3).Row
        sArr = .Range("A4:B" & iR).Value
        For i = 1 To UBound(sArr)
            If sArr(i, 1) = Empty Then sArr(i, 1) = sArr(i - 1, 1)
            Dic(sArr(i, 1)) = Dic(sArr(i, 1)) & "|" & sArr(i, 2)
        Next
        ReDim Res(1 To Dic.count * 3, 1 To 1000)
        ReDim sRes(1 To Dic.count * 3, 1 To 2)
        For Each Key In Dic.keys
            Res(n, 1) = Key: sRes(n, 1) = "So lan": c = 1
            S = Split(Dic.Item(Key), "|")
            If UBound(S) > 3 Then
                For i = 1 To UBound(S)
                    If i Mod 3 = 1 Then c = c + 1: k = 1 Else k = k + 1
                    Res(k + n - 1, c) = S(i): If R < k Then R = k
                Next
                sRes(n, 2) = c - 1
            Else: c = 2
                For i = 1 To UBound(S)
                    k = k + 1
                    Res(k + n - 1, c) = S(i)
                Next
                R = k: sRes(n, 2) = 1
            End If
            n = n + R:  R = 0: k = 0: If c > cMax Then cMax = c
        Next
        .Range("K3").Resize(10000, 1000).ClearContents
        .Range("N3").Resize(UBound(Res), cMax).Value = Res
        .Range("K3").Resize(UBound(Res), 2).Value = sRes
    End With
End Sub
View attachment 293283
Về việc ghép các lệnh trong cùng 1 dòng em không dám bàn ạ.
Nhưng các anh có thể giúp em nốt nội dung "Điền số lượng Serial của mã có Số lần = 1 vào cột K" với ạ.
Em bỏ phần diễn giải ghi chú "So lan" đi và thay bằng Số lượng Serial của mã đó nếu số lần = 1
Em cảm ơn.
 
Upvote 0
a(1) = a(1) + 5: a(2) = a(2) + 15: a(4) = a(4) + 45
không hề dễ đọc hơn
1690707912553.png

Lúc gỡ lỗi code không được thoải mái nắm á bác,và phát hiện thêm 1 điều là khi đang gỡ lỗi cumj code trên, nếu sửa code lúc đó thì vba nó bắt phải dừng code lại mới cho sửa code.
 
Upvote 0
Web KT

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

Back
Top Bottom