Cần công thức tính một dạng Tổ hợp
Chào các bạn, mình đang gặp yêu cầu như sau:
Trong 100 số từ: 00 đến 99, liệt kê những nhóm có 30 số không trùng nhau.
Ví dụ:
00 01 …… 28 29
00 01 …… 28 30
——
——
69 71 …… 98 99
70 71 …… 98 99Từng nhóm có thể ở trong 1 cell hoặc ở 30 cell.
Mong các bạn phát triển giúp công thức.
Cám ơn các bạn nhiều.
Chả cần dùng đít to đít nhỏ đít thon gì cả. Thuật toán hoàn toàn đơn giản (giải thích trong hàm). Số vòng lặp luôn bằng số phần tử cần chọn. Dĩ nhiên phần tử được chọn sẽ không được phép chọn trong các lần tiếp theo, tức phải loại bỏ. Dùng đít to đít nhỏ với Exists hoặc không (bẫy lỗi) sẽ gặp trường hợp trùng thì "phí" một vòng lặp, tức số vòng lặp có thể > số phần tử cần chọn. Hàm dưới đây không dùng thêm công cụ nào cả.
Function Draw(Arr, Amount As Long)
' hàm chọn lần lượt Amount phần tử của mảng Arr. Việc chọn phần tử của mảng Arr đồng nghĩa
với việc chọn chỉ số của phần
' tử đó trong mảng
' Ta dùng vòng lặp có Amount bước. Trong mỗi bước ta chọn 1 chỉ số, tức 1 phần tử. Ta loại phần
tử được chọn
' khỏi lần chọn tiếp theo bằng cách ghi phần tử thứ k trong mảng nguồn vào vị trí có chỉ số vừa
được chọn và tăng
' thêm 1 đơn vị cận dưới của khoảng chỉ số thao tác trong vòng lặp sau. Bằng cách này trong mỗi
vòng lặp ta chỉ
' chọn trong những phần tử chưa được chọn. Luôn có Amount vòng lặp. Dùng từ điển có thể số
vòng lặp sẽ > Amount
Dim index As Long, k As Long, d As Long, c As Long, tmpArr, original
If Amount > UBound(Arr) - LBound(Arr) + 1 Then Exit Function
original = Arr
ReDim tmpArr(1 To Amount)
d = LBound(original)
c = UBound(original)
Randomize
For k = 1 To Amount
' chọn chỉ số trong khoảng chỉ số hiện thời
index = Int(Rnd() * (c - d + 1)) + d
' ghi phần tư được chọn vào mảng kết quả
tmpArr(k) = original(index)
' loại phân tử vừa chọn bằng cách thay thế nó bằng phần tử thứ k trong mảng nguồn,
' tức phần tử có chỉ số k + LBound(Arr) - 1
original(index) = original(k + LBound(original) - 1)
' tăng cận dưới của khoảng chỉ số thao tác
d = d + 1
Next k
Draw = tmpArr
End Function
Các phần tử của mảng không nhất thiết là số. VD. đó là danh sách mã, danh sách học sinh không trùng.
Chuẩn và cho tốc độ nhanh hơn dic nhỏ dic to lun,
Thay đổi, thay đổi …… chứ đừng nghĩ là xưa cũ là hay là chuẩn, GPE đã có thêm 1 hàm UDF mới để lựa chọn sử dụng thay cái cái cũ đi
chỉ góp ý chút:
Có thể không cần
d = d + 1
chỉ cần thay d ở chỗ random theo cả Lbound và k là được// và bài này cho chọn k phần tử ngẫu nhiên trong tập n phần tử-— TUY nhiên hình như chủ topic hỏi khác (song những người trả lời trước đã lái sang vấn đề này)
Chính xác. Cái này là kiểu viết nhanh nhanh, "viết vội trên đầu gối", nghĩ tới đâu viết tới đó nên chưa trau chuốt. Cám ơn bạn đã bổ sung.
Sau một hồi suy nghĩ thì tôi thấy thế này:
Với
index = Int(Rnd() * (c - d + 1)) + d
...
d = d + 1
Ta có 1 phép nhân, 2 phép cộng trừ (c – d + 1) + 1 phép cộng ( … + d) + 1 phép cộng (d = d + 1), tổng cộng 1 phép nhân và 4 phép cộng trừ.
Nếu sửa thành
index = Int(Rnd() * (c - d - k + 2)) + d + k - 1
Thì có 1 phép nhân và 6 phép cộng trừ.
Nếu đúng thế thì để code cũ ngoài việc giảm phép cộng trừ cho bộ vi xử lý thì còn thấy rõ "ý đồ" thuật Toán hơn.
Tất nhiên đây chỉ là ý kiến chủ quan của tôi thôi.
Nhân tiện đây tôi cũng giải thích luôn:
d = LBound(original)
c = UBound(original)
Ta nên nhớ rằng đọc giá trị từ biến ra bao giờ cũng nhanh hơn là đọc từ thuộc tính của đối tượng hay gọi hàm. Vậy nếu ta cần dùng một giá trị không đổi nhiều lần thì trước tiên ta đọc vào biến sau đó nhiều lần đọc từ biến ra.
www.giaiphapexcel.com/diendan/threads/c%E1%BA%A7n-c%C3%B4ng-th%E1%BB%A9c-t%C3%ADnh-m%E1%BB%99t-d%E1%BA%A1ng-t%E1%BB%95-h%E1%BB%A3p.66810/post-408551
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
Quả đúng là 1 giải thuật rất thông minh
Đã test thử với 100000 số, lấy ra 60000 số.. so sánh với hàm UniqueRandomNum trước đây thì cái của bạn nhanh hơn ít nhất 7 lần
Tuy nhiên nếu dùng, chắc phải sửa lại đôi chút, vì:
– Biến Arr là cái ta không có sẳn
– Nếu Arr lấy từ CSDL của Excel thì Arr luôn là mảng 2 chiều
vân vân…
Nhưng nói chung cũng không quan trọng lắm —> Giải thuật vẫn thế —> Tốc độ cao mới là cái đáng để học hỏi từ code này
Ẹc… Ẹc…
Bạn thử up file bạn đã thử xem sao,
biết đâu ít ra mọi người học được cái lỗi đó
Thay sub trên thành
Sub Test1()
Dim sArr(), tArr(), i&, iSo&
sArr = Array(1, 2, 3, 4, 5, 9)
iSo = 3
tArr = Draw(sArr, iSo)
For i = 1 To iSo
Cells(i, 2) = tArr(i)
Next i
End Sub
Tại sao thế thì tôi không biết, nhưng làm theo bài thì phải vậy
Đương nhiên là sai rồi —> Đầu tiên phải lấy ngẫu nhiên không trùng cho vào 1 biến array trước, xong mới for.. next theo biến array này
Tuy nhiên, nếu là tôi thì tôi sẽ sửa lại 1 chút cho phù hợp với CSDL trên Excel (mảng 2 chiều)
Function Draw(Arr, Amount As Long)
Dim index As Long, k As Long, d As Long, c As Long, tmpArr, original
If Amount > UBound(Arr) – LBound(Arr) + 1 Then Exit Function
original = Arr
ReDim tmpArr(1 To Amount, 1 To 1)
d = LBound(original)
c = UBound(original)
Randomize
For k = 1 To Amount
index = Int(Rnd() * (c – d – k + 2)) + d + k – 1
tmpArr(k, 1) = original(index)
original(index) = original(k + LBound(original) – 1)
d = d + 1
Next k
Draw = tmpArr
End Function
Sub Test1()
Dim sArr(), Amount As Long, rArr
sArr = Array(1, 2, 3, 4, 5, 9)
Amount = 3
rArr = Draw(sArr, Amount)
Range("B1").Resize(Amount).Value = rArr
End Sub
——————
Là vầy sư phụ à:
– Ta có mảng Arr
– Duyệt từ 1 đến Amount
– Dùng Rnd để tính vị trí k
– Lấy phần từ thứ k của Arr cho vào mảng khác
– Lấy phần từ thứ nhất của Arr thế vào phần tử thứ k
===> Đó là lần quét đầu tiên
– Đến lần quét thứ 2 sẽ lấy phần tử thứ hai của Ar thế vào phần tử thứ k
vân vân…
Và chắc chắn 100% sẽ không có chuyện trùng, ngoại trừ trường hợp mảng Arr có trùng
Thí dụ Arr = (1, 2, 3, 4, 5, 6)
k= 1, index = 2, tmp(1) = 2
original trở thành: (1, 1, 3 ,4, 5, 6)
k = 2, index = 1, tmp(2) = 1
original trở thành (1, 1, 3 ,4, 5, 6) (không đổi vì thay 1 = 1)
k = 3, index = 2 tmp(3) = 1
qua 3 vòng lặp đã có 2 giá trị trùng.Dùng code đầu tiên của siwtom đi! Tôi cảm thấy không mấy tin tưởng về code sửa lại sau đó
Function Draw(Arr, Amount As Long)
Dim index As Long, k As Long, d As Long, c As Long, tmpArr, original
If Amount > UBound(Arr) – LBound(Arr) + 1 Then Exit Function
original = Arr
ReDim tmpArr(1 To Amount, 1 To 1)
d = LBound(original)
c = UBound(original)
Randomize
For k = 1 To Amount
index = Int(Rnd() * (c – d + 1)) + d
tmpArr(k, 1) = original(index)
original(index) = original(k + LBound(original) – 1)
d = d + 1
Next k
Draw = tmpArr
End Function
Không thể nào sư phụ à! Vì cái này:
Nên index của lần lập thứ 2 không thể = 1 được (số ngẫu nhiên từ 2 trở đi mà)
bạn phải lấy post gốc của siwtom ở bài [URL='https://www.giaiphapexcel.com/forum/showthread.php?66810-C%E1%BA%A7n-c%C3%B4ng-th%E1%BB%A9c-t%C3%ADnh-m%E1%BB%99t-d%E1%BA%A1ng-T%E1%BB%95-h%E1%BB%A3p&p=408551#post408551'%5D#8 hoặc như của Ndu… ở bài [URL='https://www.giaiphapexcel.com/forum/showthread.php?66810-C%E1%BA%A7n-c%C3%B4ng-th%E1%BB%A9c-t%C3%ADnh-m%E1%BB%99t-d%E1%BA%A1ng-T%E1%BB%95-h%E1%BB%A3p&p=409722#post409722'%5D#25
chú ý câu lệnh này
index = Int(Rnd() * (c – d + 1)) + d ''KHÔNG CÓ k trong đó
………
d=d+1 ''thì cần cái này
————–
trường hợp có k trong nó (câu lệnh tính index trên) như bài [URL='https://www.giaiphapexcel.com/forum/showthread.php?66810-C%E1%BA%A7n-c%C3%B4ng-th%E1%BB%A9c-t%C3%ADnh-m%E1%BB%99t-d%E1%BA%A1ng-T%E1%BB%95-h%E1%BB%A3p&p=408570#post408570'%5D#10 siwtom thì khi đó xóa d=d+1 (nhưng siwtom đã nói không nên dùng cách này)