Hàm mảng động dynamic array và ứng dụng trong UDF trả về nhiều kết quả. (1 người xem)

Liên hệ QC

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

Hau151978

Thành viên tích cực
Tham gia
19/10/11
Bài viết
1,477
Được thích
1,440
Chào các bạn, mình tham gia diễn đàn đã lâu, học hỏi được nhiều thứ từ các anh chị và các bạn. Hôm nay nhân lúc uống tý rượu, mình xin chia sẻ những tìm hiểu của mình về mảng động, một tính năng sắp áp dụng trên excel. Các công thức mới có thể dễ dàng tìm trên Google nên mình chỉ viết về hàm tự tạo (UDF) trong VBA.
Như chúng ta đã biết, khi viết hàm tự tạo và sử dụng trên sheet, Excel từ trước tới nay không cho phép thay đổi giá trị của các ô trên sheet trừ ô gọi hàm (không tính cách sử dụng công thức mảng, công thức mảng cũng không linh hoạt vì ta không biết có bao nhiêu giá trị trả về). Để “lách luật”, có lẽ cách đơn giản nhất là sử dụng Evaluate, hàm này sẽ gọi một thủ tục phụ để trả về giá trị. Ví dụ sau đây là hàm MultiVlookup tương tự Vlookup nhưng trả về nhiều giá trị nếu trùng khớp, hàm nhận đối số thứ nhất là giá trị dò, đối số thứ 2 là một cột, đối số thứ 3 có thể âm hay dương là vị trí tương đối của cột kết quả so với cột dò tìm tương tự như tham số của OFFSET, áp dụng cho mọi phiên bản Excel (hàm này mình tự viết chỉ để làm ví dụ nên không test kỹ, có thể còn một số lỗi, cái mà các bác hay gọi là "hàm co giãn" cũng tương tự):
Mã:
Option Explicit
Private KQ()
Function MultiVlookup(LookupValue, LookupCol As Range, Col&)
    Dim arr1(), arr2(), i&, k&, n&
    n = LookupCol.Rows.Count
    arr1 = LookupCol.Value
    arr2 = LookupCol.Offset(, Col).Value
    ReDim KQ(1 To n, 1 To 1)
    Application.Volatile
    For i = 1 To n
        If arr1(i, 1) = LookupValue Then
            If k = 0 Then
                MultiVlookup = arr2(i, 1)
            Else
                KQ(k, 1) = arr2(i, 1)
            End If
            k = k + 1
        End If
    Next
    If k > 1 Then Evaluate "xxx(" & Application.Caller.Offset(1).Resize(k - 1).Address(0, 0) & ")"
End Function
Sub xxx(Cl As Range)
    Cl = KQ
End Sub
Ta thấy xây dựng hàm trên khá phức tạp, mặc dù nếu là Sub thì quá đơn giản. Tuy nhiên tới đây, chức năng mảng động sẽ thay đổi căn bản các công thức Excel, 1 hàm có sẵn hoặc UDF có thể trả về mảng mà không cần dùng công thức mảng. Nếu là UDF thì chỉ việc gán kết quả là mảng, Excel sẽ xuất toàn bộ kết quả. Hiện tính năng này mới áp dụng cho Excel 365 đã đăng ký Office insider, bạn nào đang sử dụng phiên bản này có thể thử ví dụ sau đây MultiVlookup365 đơn giản hơn rất nhiều:
Mã:
Function MultiVlookup365(LookupValue, LookupCol As Range, Col&)
    Dim arr1(), arr2(), arrKQ(), i&, k&, n&
    n = LookupCol.Rows.Count
    arr1 = LookupCol.Value
    arr2 = LookupCol.Offset(, Col).Value
    ReDim arrKQ(1 To n)
    For i = 1 To n
        If arr1(i, 1) = LookupValue Then
            k = k + 1
            arrKQ(k) = arr2(i, 1)
        End If
    Next
    If k = 0 Then Exit Function
    ReDim Preserve arrKQ(1 To k)
    MultiVlookup365 = Application.WorksheetFunction.Transpose(arrKQ)
End Function
Mong nhận được ý kiến đóng góp của các bạn.
 

File đính kèm

Lần chỉnh sửa cuối:
Ví dụ 1 - mắc lỗi:
Soát lỗi bằng cách gọi Sub sau ở một Trang tính khác:
Sub Test()
Sheet1.[C3] = 6
End Sub

Sửa:
Col& -> Col%
-----------------------------
Evaluate "xxx(" & Application.Caller.Offset(1).Resize(k - 1).Address(0, 0) & ")"

thành:

With Application
.Evaluate "xxx('[" & _​
.Caller.Parent.Parent.Name & "]" & _​
.Caller.Parent.Name & "'!" & _​
.Caller.Offset(1).Resize(k - 1).Address(0, 0) & ")"​
End With

-----------------------------
MultiVLookup tạo một cuộc gọi khác nên
Code trong hàm MultiVLookup nên nằm trong sub xxx
Lúc này không cần đến Application.Volatile

Viết như vậy ta có thể tận dụng được kĩ thuật code đa luồng
 
Upvote 0
Cái đó thấy nhiều người bị mà không biết tại sao .... bản thân mình cũng bị

Tiện có bài này mong bạn nào hiểu rõ bản chất của nó cho xin 1 bài giải thích để hiểu sâu thêm một chút nhỉ ??!!
 
Lần chỉnh sửa cuối:
Upvote 0
Cảm ơn các bạn, đúng là mình quên không xét đến trường hợp gọi hàm từ nơi khác. Tuy nhiên lệnh volatile để tính lại hàm khi vùng kết quả bị thay đổi do các ô kết quả ở dưới không có liên hệ gì với ô gọi hàm. Còn sửa Col& thành Col% mình nghĩ không quan trọng, đây là việc bẫy lỗi mà mình thì ngại, nếu ai cố tình tham chiếu ra ngoài giới hạn của Excel sẽ bị lỗi.
 
Lần chỉnh sửa cuối:
Upvote 0
Đợt trước em cũng mò theo cách này mà không ổn đâu
 
Upvote 0
Biết ,,,, nhưng muốn hiểu bản chất sâu xa của nó là tại sao khai báo như thế trong trường hợp đó nó lại hiểu sai
ý muốn hỏi là thế ( Sâu thật sâu bản chất của vấn đề ấy )
Ý bạn là gì nhỉ, tại sao cần khai báo integer mà không phải long à?
 
Upvote 0
Cảm ơn các bạn, đúng là mình quên không xét đến trường hợp gọi hàm từ nơi khác. Tuy nhiên lệnh volatile để tính lại hàm khi vùng kết quả bị thay đổi do các ô kết quả ở dưới không có liên hệ gì với ô gọi hàm.
Bác hiểu lệnh volatile theo hướng nào
Khi nào cần gọi volatile và khi nào không.

Tất cả các hàm UDF đều phải gọi volatile?

Tham số LookupCol đã thay cho volatile rồi, vì vậy không cần đến volatile.

Gọi volatile khi vùng tham chiếu nằm ngoài vùng được gọi:

Ví dụ:
Function A(Byval B As Range)
A = B.Value + B(2,1).Value
End Function

A1 = A(A2)
A2 là vùng tham chiếu
Giá trị ô A2 đổi A1 đổi
Giá trị ô A3 Và Vùng khác A2 đổi A1 không đổi

Nếu có volatile, bất kì ô nào thay đổi thì A1 đều phải tính toán lại.

Hãy thử cho Debug.Print vào Sub xxx và thay đổi giá trị ở Ô A1000000
Bài đã được tự động gộp:

Biết ,,,, nhưng muốn hiểu bản chất sâu xa của nó là tại sao khai báo như thế trong trường hợp đó nó lại hiểu sai
ý muốn hỏi là thế ( Sâu thật sâu bản chất của vấn đề ấy )
Vấn đề này liên quan đến tiết kiệm phần cứng của máy tính.

Khai báo giá trị là Long sẽ tiêu hao vùng nhớ hơn so với Integer.

Vì Max Column của Excel chỉ có 16384 Cột

Giả sử bác gọi 1 triệu Hàm, thì mức tiêu hao sẽ tăng theo 1 triệu, quá phí phải không
 
Lần chỉnh sửa cuối:
Upvote 0
Ví dụ trên file của mình, hàm trả về kết quả ở F2:F4 nhưng đối với Excel chỉ biết kết quả ở F2 là ô gọi hàm, không giống như công thức mảng hoặc hàm mảng động. Vì thế nếu không có Volatile, nếu xóa ô F3 thì hàm cũng không tính toán lại, điều này có thể dẫn đến kết quả sai.
 
Upvote 0
Ý HỎI LÀ TẠI SAO có trường khai báo Col& thì chạy ok mà có trường hợp phải khai báo là Col as long mới chạy ok
Mình chưa thấy bao giờ, bạn thử cho ví dụ xem sao?
Bài đã được tự động gộp:

Trước kia mình vẫn nhớ là VBA tự động chuyển đổi các số integer thành long trừ khi bắt buộc phải sử dụng integer trong một số API chẳng hạn. Vì thế hầu hết các trường hợp khi khai báo integer ta sẽ không tiết kiệm được bộ nhớ mà lại tốn thêm thời gian chuyển đổi. Tuy nhiên khi thử lại thì thấy đúng là Long tốn nhiều bộ nhớ hơn thật, xin lỗi bạn @HeSanbi .
 
Lần chỉnh sửa cuối:
Upvote 0
Ý HỎI LÀ TẠI SAO có trường khai báo Col& thì chạy ok mà có trường hợp phải khai báo là Col as long mới chạy ok

Ý của bác kieumanh là:

Type ABCD
EF As Long 'được EF& thì không.
SF As String 'được SF$ thì không.
End Type

Rất dễ hiểu thôi:

Dim A As ABCD, C&

Không thể nào như thế này được:
A.EF& + C&

Trình dịch VBE sẽ không hiểu đoạn trên viết gì.

Cách viết C&, khi hàm quá nhiều dòng, nhiều đến nổi bác không thể biết thằng C được khai báo là Long

Hoặc Quá nhiều biến A - ZZZZZZ

Ta viết: A + B + C + D + E + F sau này sẽ mất thời gian trong việc bảo trì và phát triển thêm code.

và: A& + B% + C^ + D# + E@ + F!
Để sau này bác đọc lại code dễ dàng hơn. Nhưng nhọc nhằn trong việc viết
 
Upvote 0
Cho mình góp vui 1 hàm mảng người dùng & chúc các bạn vui nhân ngày cuối tuần!
Hàm của bác Sa trả về mảng nhưng trên Excel 2003 phải thông qua công thức mảng và dẫn tới nhiều ô thừa vì ta không biết có bao nhiêu ô kết quả khi đặt công thức. Tới đây việc này đơn giản hơn rất nhiều vì chỉ cần gán mảng vào udf, mọi việc còn lại Excel sẽ tự lo.
 
Upvote 0
Tính năng co giãn hàm dạng này, thì ở Google SpreadSheet dùng tốt hơn cả,
Chắc phiên bản excel mới hơn sẽ sớm cập nhật, khi đó dùng tiện lợi hơn
Nói cách khác là hoặc đợi Microsoft cập nhật, hoặc phải can thiệp vào cốt lõi Excel thì mới khả thi hơn cả, còn không thì chỉ là giải pháp tàm tạm ngay cả tool gì đó cũng sẽ phải can thiệp quản lý toàn sheet (workbook) khi đó hạn chế các tính năng khác
 
Upvote 0
Mấy cái ký tự &,%,^,!... linh tinh kia là đồ cổ, không nên dùng, tránh xa ra.
Thời Bill Gates đưa ra ngôn ngữ Basic, lúc đó chương trình ghi trên băng đục lỗ, băng từ, đĩa mềm nhỏ, người ta mới dùng tới mấy ký tự đó để tiết kiệm kích thước mã nguồn chương trình lưu.
Giờ sau nay, theo coding standard thống nhất cho VB (và các ngôn ngữ khác), người ta đã bỏ và không cho dùng kiểu khai báo đó rồi, ở đây còn cố dùng, nhìn rối rắm,càng làm thêm phức tạp, hổ lốn....
Nguyên tắc viết code thì có 2 điểm quan trọng nhất sau:
1. Trong sáng, đơn giản nhất tới hết mức có thể
2. Ai đọc cũng hiểu, ai cũng có thể bảo trì nâng cấp sau này, không thì nó ngồi vừa fix vừa chửi cho nghe thúi đầu.
Ba cái goto, gosub, go nhảy tá lã lắc cái đầu, nhảy con mắt mệt nghĩ cũng tránh xa ra, dẹp đi. Viết sao cho người đọc đọc hiểu luồng thực thi hàm của mình chỉ 1 chiều từ trên xuống tới hết.
Tâm lý mấy anh mới học code hay code biết sơ sơ thì càng cố thích làm cho code mình "bờ rồ", rối rắm, phức tạp, như 1 đống bùi nhùi, chả ai hiểu gì.
Càng sau này thì người ta sẽ càng code trong sáng, rõ ràng hơn thôi.
Nguyên tắc khai báo biến của VB/VBA:
1. Khai báo rõ ràng ra kiểu của biến: vd As Long, As String, As Integer rõ ràng ra. Variant thì cũng phải là As Variant
2. Các biến cũng kiểu thì nên group chung nhóm lại. Nếu được thì 1 biến trên 1 hàng, tab vào.
3. Dùng các tiền tố (prefix) thống nhất cho các kiểu của biến, vd: s, str cho String, i, n cho Integer, l, lng cho Long, var cho Variant.
Chịu khó tìm đọc "Hungarian notation". Để có ở đâu cũng biết được biến đó loại gì.
4. Nếu tên biến là tiếng Anh thì nên là danh từ
5. Biến toàn cục trong module thì m_ trước, toàn cục toàn chương trình thì g_ trước.
.... vân vân và vân vân, còn nhiều, lười gõ.
Ai muốn làm theo thì làm, không thì thôi,
 
Lần chỉnh sửa cuối:
Upvote 0
Đây là help của MS viết năm 2018, trong đó ghi rõ "The shorthand for the types is: % -integer; & -long; @ -currency; # -double; ! -single; $ -string" chứ không phải chuyện thể hiện gì ở đây cả. Đặt tên biến kiểu Hungary cũng là một cách, mình thì không dùng kiểu này mà thường viết kiểu hỗn hợp chữ hoa và chữ thường, về sau mới thấy gọi là kiểu lạc đà.
 
Upvote 0
Nhà có 2 cái ghế, ghế cổ và ghế 4.0, 2 khách đến chơi, kêu khách đừng có ngồi ghế cổ, 2 người cùng ngồi một ghế 4.0 cho hiện đại.
 
Upvote 0
Tính năng co giãn hàm dạng này, thì ở Google SpreadSheet dùng tốt hơn cả,
Chắc phiên bản excel mới hơn sẽ sớm cập nhật, khi đó dùng tiện lợi hơn
Nói cách khác là hoặc đợi Microsoft cập nhật, hoặc phải can thiệp vào cốt lõi Excel thì mới khả thi hơn cả, còn không thì chỉ là giải pháp tàm tạm ngay cả tool gì đó cũng sẽ phải can thiệp quản lý toàn sheet (workbook) khi đó hạn chế các tính năng khác
Tới đây Excel sẽ cập nhật tính năng này, về chức năng hàm MultiVlookup ở trên thì công thức Filter làm tốt, mục đích của topic này là mình muốn giới thiệu tính năng đó trong công thức và hàm tự tạo chứ không phải code.
 
Upvote 0
Web KT

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

Back
Top Bottom