để liệt kê tổ hợp, chỉnh hợp. Khi chạy trên bảng tính Excel thì số phần tử trả về của mảng bị giới hạn do số dòng của sheet. Khi số phần tử mả trên 1048576 dòng bạn nên lập trình với hàm API BS_COMBINLIST của Add-in A-Tools để nhận hàng tỷ giá trị.
Bài viết này tôi hướng dẫn các bạn lập trình liệt kê tổ hợp, chỉnh hợp lặp, chỉnh hợp không lặp bằng 4 ngôn ngữ lập trình: VBA, Delphi, C#, VB.NET với việc dùng hàm API BS_COMBINLIST của Add-in A-Tools. Lưu ý là bạn cần cài đặt
Đầu tiên chúng ta cần khai báo hàm API - import hàm. BS_COMBINLIST trong AddinATools.dll
Declare PtrSafe Function CombinListAPI Lib "AddinATools.dll" Alias "BS_COMBINLIST" ( _
Const MaxRows = 1048576 'in sheet 2007
Const MaxCols = 16384 'in sheet 2007
Dim I&, J&, t
Sub TestComBin()
Const s = "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z"
Const number_chosen As Long = 6
Const Options = "" '"FUNC=PERMUT;" , "FUNC=PERMUTA;"; "" -> COMBIN
Dim arr, res, result
arr = Split(s, ",")
I = 0: J = 1
t = Timer
res = False 'True: receive array of values; False: do not receive
' Call the CombinListAPI function
result = CombinListAPI(arr, number_chosen, 1, Options, AddressOf TestValueCbk, res)
Debug.Print "A-Tools:", "Count: " & result, "Time: " & (Timer - t) & " seconds."
End Sub
' Callback function implementation
#If VBA7 Then
Function TestValueCbk(AValue As Variant, _
ByVal Count As LongPtr) As Boolean
#Else
Function TestValueCbk(AValue As Variant, ByVal Count As Long) As Boolean
#End If
TestValueCbk = (Count < 1000000) 'TRUE: Continue; FALSE: Stop
I = I + 1
If I > MaxRows Then
I = 1
J = J + 1
'Debug.Print "Column " & J - 1 & " Values: " & Format(Count, "#,##0"), "Time: " & (Timer - t)
DoEvents
End If
If J > MaxCols Then
TestValueCbk = False 'Stop finding.
Debug.Print "Overflow column index."
End If
If Count <= 1000 Then 'Limit 1000 for testing only
'Cells(I, J).Value = AValue
Debug.Print "Item: " & Count & " Value: " & AValue
End If
End Function
'END COPY -----------------------------------------------------
Bây giờ bạn hãy chạy thủ tục
TestComBin để xem kết quả trả về ở cửa sổ Immediate
Giải thích hàm và các tham số
MỤC ĐÍCH
Hàm trả về danh sách các giá trị kết hợp hoặc hoán vị, các hoán vị lặp lại. Mỗi cặp giá trị được truy xuất là một mảng các phần tử hoặc phép nối. Nếu result_type = -1, hàm trả về số lượng các tổ hợp hoặc chỉnh hợp.
CÁC THAM SỐ
- SrcArr:
Là một mảng các giá trị được sử dụng để lấy kết hợp hoặc hoán vị. Hàm trả về một số nguyên là số trường hợp được tính toán.
- number_chosen:
Là số giá trị cần kết hợp. number_chosen <= số phần tử mảng của SrcArr
+ Nếu giá trị của tham số này là: < 0 hoặc lớn hơn số phần tử của nguồn, lỗi #NUM! sẽ được trả về.
+ Nếu giá trị tham số này là 0, hàm trả về trường hợp mà các phần tử cấu thành là các phần tử của mảng nguồn.
- result_type:
nếu là 0 - Các phần tử được kết hợp để tạo thành một mảng (mặc định); Nếu là 1 thì các phần tử được nối lại, phân tách bằng dấu phẩy (,); Nếu là -1 thì hàm trả về giá trị kết hợp hoặc giá trị chỉnh hợp.
- options:
Tham số này là kiểu chuỗi, có thể bỏ qua. Sử dụng thuộc tính:
+ Nếu bạn muốn thay đổi ký tự để kết hợp giá trị (khi tham số result_type=1), hãy nhập "SEP=Delimiter;". Delimiter là bất kỳ ký tự nào, nếu là NULL, hàm không sử dụng ký tự đó để kết hợp.
+ Chỉ định thời gian chạy: "TIMEOUT=m;" m là số phút tối đa để chạy hàm. Nếu hàm chạy lâu hơn thời gian TIMEOUT, hàm sẽ dừng. Nếu không được khai báo, hàm sẽ chạy cho đến khi hoàn thành.
+ Chỉ định số lượng giá trị: "TOP=n;" n là số lượng giá trị tối đa ??được truy xuất. Nếu không được khai báo, hàm sẽ truy xuất tất cả các trường hợp.
+ Hàm "FUNC=PERMUTA;" hoặc "FUNC=1;" để tính toán chỉnh hợp lặp lại
+ "FUNC=PERMUT;" hoặc "FUNC=2;" để tính toán chỉnh hợp không lặp lại
+ KHÔNG KHAI BÁO "FUNC" (bỏ trống). Hàm này tính toán tổ hợp.
- pProcCallback:
Là con trỏ đến hàm callback để kiểm tra mỗi lần lấy được giá trị. Cấu trúc hàm callback
FunctionCallback(AValue As Variant, _
ByVal Count As LongPtr) As Boolean
Hàm Callback trả về True hàm API BS_COMBINLIST sẽ tiếp tục tìm giá trị; False hàm sẽ dừng lại.
- pRes:
Nếu không được khai báo hoặc gán giá trị FALSE thì tham số này sẽ không nhận được giá trị. Nếu là địa chỉ biến có kiểu VARIANT thì nó sẽ nhận được mảng các giá trị kết hợp hoặc hoán vị.
RETURN
+ Hàm trả về một số nguyên là số trường hợp được tính toán.
+ Tham số pRes nhận mảng các giá trị kết hợp hoặc tổ hợp. Nếu đưa biến kiểu VARIANT vào tham số pRes và giá trị của nó không được khác là False.
2. Lập trình bằng Delphi
(Liệt kê tôt hợp, chỉnh hợp băng Delphi với hàm API BS_COMBINLIST)
- Bước 1: Tạo dự án, đặt tên như là "CombinListAPI"
- Bước 2: Tạo unit và đặt tên như là "uCombinListAPI" (File -> New-> Unit Delphi)
- Bước 3: Dán code Delphi dưới đây thay thế toàn bộ mã trong uCombinListAPI:
//BEGIN COPY---------------------------------------------
unit uCombinListAPI;
//Author: Nguyen Duy Tuan - duytuan@bluesofts.net
interface
uses
SysUtils, Variants, Windows;
function CombinListAPI(SrcArr: OleVariant;
const number_chosen: Integer;
const result_type: Integer;
Options: OleVariant;
const pFuncCallback: Pointer;
const pResVariant: POleVariant): NativeUInt; stdcall;
procedure TestCombinList;
implementation
// Import the CombinListAPI function from AddinATools.dll
function CombinListAPI; external 'AddinATools.dll' name 'BS_COMBINLIST';
// Callback function implementation
function TestValueCbk(const AValue: OleVariant; const Count: NativeUInt): Wordbool; stdcall;
begin
Writeln(Format('Item: %d Value: %s', [Count, string(AValue)]));
Result := (Count < 10000); //True: Continue; False: Stop
end;
procedure TestCombinList;
const
s = 'A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z';
number_chosen = 6;
Options: WideString = ''; //'FUNC=PERMUT;' , 'FUNC=PERMUTA;' ; '' -> COMBIN
var
res: OleVariant;
result: NativeUInt;
t: Cardinal;
arr: TArray<string>;
begin
arr := s.Split([',']);
{
arr := VarArrayof(['A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z']);
}
t := GetTickCount;
res := True; //True: receive array of values; False: do not receive
// Call the CombinListAPI function
result := CombinListAPI(arr, number_chosen, 1, Options, @TestValueCbk, @res);
Writeln(Format('A-Tools: Count: %d, Time: %f seconds', [result, (GetTickCount - t)/1000]));
Res := Unassigned;
end;
end.
//END COPY ------------------------------------------------------
- Bước 4: Mở mã nguồn dữ án "CombinListAPI", dán toàn bộ mã nguồn dưới dây:
//BEGIN COPY---------------------------------------------
program CombinListAPI;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Winapi.Windows,
uCombinListAPI in 'uCombinListAPI.pas';
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
TestCombinList;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
MessageBox(GetActiveWindow, 'Completed.','CombinList API', MB_ICONINFORMATION);
end.
//END COPY ------------------------------------------------------
Bây giờ bạn hãy chạy dự án để xem kết quả liệt kê tổ hợp hoặc chỉnh hợp trên mà hình Console.
3. Lập trình bằng ngôn ngữ C#
(Liệt kê tổ hợp, chỉnh hợp bằng C# với hàm API BS_COMBINLIST)
- Bước 1: Tạo Solution C# kiểu ứng dụng Console App
- Bước 2: Mở file Program.cs dán toàn bộ mã nguồn dưới đây:
//BEGIN COPY -----------------------------------------------------
//Author: Nguyen Duy Tuan - duytuan@bluesofts.net
using System;
using System.Runtime.InteropServices;
namespace Using_BS_CombinList_API
{
class Program
{
// Define the delegate for the callback function
public delegate bool CallBack(ref object AValue, UIntPtr Count);
// Import the CombinListAPI function from AddinATools.dll
[DllImport("AddinATools.dll", EntryPoint = "BS_COMBINLIST")]
public static extern UIntPtr CombinListAPI(object SrcArr,
int number_chosen,
int result_type,
object Options,
CallBack pFuncCallback,
ref object pRes);
// Callback function implementation
public static bool TestValueCbk(ref object AValue, UIntPtr Count)
{
Console.WriteLine("Item: {0} Value: {1}", Count, AValue);
if ((UInt64)Count >= 10000) //for testing only
return false; //Stop
else
return true; //true: Continue; false: Stop
}
static void Main(string[] args)
{
string s = "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z";
int number_chosen = 6;
object Options = ""; //"FUNC=PERMUT;" , "FUNC=PERMUTA;" ; "" -> COMBIN
object res = false; //true: receive array of values; false: do not receive
object arr = (object)s.Split(',');
CallBack myCallBack = new CallBack(TestValueCbk);
DateTime t = DateTime.Now;
// Call the CombinListAPI function
UIntPtr result = CombinListAPI(arr, number_chosen, 1, Options, myCallBack, ref res);
Console.WriteLine($"A-Tools: Count: {result}, Time: {(DateTime.Now - t).TotalSeconds} seconds.");
Console.ReadLine();
}
}
}
//END COPY --------------------------------------------------------
4. Lập trình bằng ngôn ngữ VB.net
(Liệt kê tổ hợp, chỉnh hợp bằng vb.net với hàm API BS_COMBINLIST)
- Bước 1: Tạo Solution vb.net kiểu ứng dụng Console App
- Bước 2: Mở file Module1.vb dán toàn bộ mã nguồn dưới đây:
//BEGIN COPY -----------------------------------------------------
' Author: Nguyen Duy Tuan - duytuan@bluesofts.net
Imports System.Runtime.InteropServices
Module Module1
' Define the delegate for the callback function
Public Delegate Function CallBack(ByRef AValue As Object, ByVal Count As UIntPtr) As Boolean
' Import the CombinListAPI function from AddinATools.dll
<DllImport("AddinATools.dll", EntryPoint:="BS_COMBINLIST")>
Public Function CombinListAPI(ByVal SrcArr As Object,
ByVal number_chosen As Integer,
ByVal result_type As Integer,
ByVal Options As Object,
ByVal pFuncCallback As CallBack,
ByRef pRes As Object) As UIntPtr
End Function
' Callback function implementation
Public Function TestValueCbk(ByRef AValue As Object, ByVal Count As UIntPtr) As Boolean
Console.WriteLine("Item: {0} Value: {1}", Count, AValue)
If Count.ToUInt64 >= 10000 Then ' for testing only
Return False ' Stop
Else
Return True ' True: Continue; False: Stop
End If
End Function
Sub Main()
Dim s As String = "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z"
Dim number_chosen As Integer = 6
Dim Options As Object = "" ' "FUNC=PERMUT;" , "FUNC=PERMUTA;" ; "" -> COMBIN
Dim res As Object = False ' True: receive array of values; False: do not receive
Dim arr As Object = s.Split(","c)
Dim myCallBack As CallBack = New CallBack(AddressOf TestValueCbk)
Dim t As DateTime = DateTime.Now
' Call the CombinListAPI function
Dim result As UIntPtr = CombinListAPI(arr, number_chosen, 1, Options, myCallBack, res)
Console.WriteLine($"A-Tools: Count: {result}, Time: {(DateTime.Now - t).TotalSeconds} seconds.")
Console.ReadLine()
End Sub
End Module
//END COPY --------------------------------------------------------
5. Lập trình bằng ngôn ngữ C++
(Liệt kê tổ hợp, chỉnh hợp bằng VC++ với hàm API BS_COMBINLIST)
//BEGIN COPY---------------------------------------------
#include <iostream>
#include <windows.h>
#include <string>
#include <vector>
#include <ctime>
#include <comutil.h>
#include <atlbase.h>
// Định nghĩa kiểu callback
typedef BOOL(WINAPI* CallBack)(VARIANT* AValue, size_t Count);
// Khai báo hàm CombinListAPI từ DLL
typedef size_t(WINAPI* CombinListAPIFunc)(VARIANT SrcArr,
int number_chosen,
int result_type,
VARIANT Options,
CallBack pFuncCallback,
VARIANT* pRes);
// Hàm callback
BOOL WINAPI TestValueCbk(VARIANT* AValue, size_t Count)
{
// Giả sử AValue là chuỗi ký tự (BSTR), cần chuyển đổi sang std::wstring
if (AValue->vt == VT_BSTR)
{
std::wstring value(AValue->bstrVal, SysStringLen(AValue->bstrVal));
std::wcout << L"Item: " << Count << L" Value: " << value << std::endl;
}
return Count < 1000; // TRUE: Continue, FALSE: Stop
}
int main()
{
// Load DLL
HINSTANCE hInstLib = LoadLibrary(TEXT("AddinATools.dll"));
if (hInstLib == nullptr)
{
std::cerr << "Unable to load DLL!" << std::endl;
return 1;
}
// Lấy địa chỉ hàm CombinListAPI
CombinListAPIFunc CombinListAPI = (CombinListAPIFunc)GetProcAddress(hInstLib, "BS_COMBINLIST");
if (CombinListAPI == nullptr)
{
std::cerr << "Unable to find BS_COMBINLIST function in DLL!" << std::endl;
FreeLibrary(hInstLib);
return 1;
}
// Chuẩn bị dữ liệu đầu vào
std::wstring s = L"A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z";
int number_chosen = 6;
CComVariant options(L""); // "FUNC=PERMUT;", "FUNC=PERMUTA;", hoặc "" -> COMBIN
// Tách chuỗi thành mảng VARIANT
std::vector<CComVariant> arr;
size_t start = 0, end = s.find(L',');
while (end != std::wstring::npos)
{
arr.push_back(CComVariant(s.substr(start, end - start).c_str()));
start = end + 1;
end = s.find(L',', start);
}
arr.push_back(CComVariant(s.substr(start).c_str())); // Thêm phần tử cuối cùng
// Tạo mảng VARIANT (SAFEARRAY)
SAFEARRAYBOUND bounds[1];
bounds[0].lLbound = 0;
bounds[0].cElements = ULONG(arr.size());
SAFEARRAY* psa = SafeArrayCreate(VT_VARIANT, 1, bounds);
for (LONG i = 0; i < LONG(arr.size()); ++i)
{
SafeArrayPutElement(psa, &i, &arr[i]);
}
VARIANT varArr;
varArr.vt = VT_ARRAY | VT_VARIANT;
varArr.parray = psa;
VARIANT res = CComVariant(true); //true nhận mảng giá trị; false không nhận
clock_t t = clock();
size_t result = CombinListAPI(varArr, number_chosen, 1, options, TestValueCbk, &res);
// Hiển thị kết quả
std::wcout << L"A-Tools: Count: " << result << L", Time: " << (double)(clock() - t) / CLOCKS_PER_SEC << L" seconds." << std::endl;
// Giải phóng bộ nhớ
VariantClear(&varArr);
VariantClear(&res);
// Giải phóng DLL
FreeLibrary(hInstLib);
std::wcin;
return 0;
}
//END COPY---------------------------------------------
Lời kết
Với yêu cầu bài toán liệt kê tổ hợp, chỉnh hợp sẽ có thể phải liệt kê số lượng số phần tử rất lớn, có thể lên đến hàng tỷ giá trị, việc hàm lấy vào một mảng rồi trả về cho người dùng có thể làm tràn bộ nhớ "Out of memory". Để giải quyết việc này chúng ta dùng phương pháp liệt kê từng đoạn rồi kiểm tra giá trị thỏa mãn hoặc gọi hàm callback như ứng dụng hàm API BS_COMBINLIST trong bài này để kiểm tra, nếu thấy thỏa mãn thì thoát khỏi chu trình chạy. Trong ví dụ bài viết này bạn có thể viết code trong hàm callback điền giá trị lấy được vào một tập tin để dùng làm dữ liệu phân tích sau nàycũng là một ý tưởng tốt.
(*) Toàn bộ mã nguồn trong bài viết này tôi lưu trong đường dẫn "
C:\A-Tools\HELP & DEMOS\A-Tools VBA Programming\BS_COMBINLIST API\" Bạn hãy mở để chạy kiểm thử.
Tác giả Nguyễn Duy Tuân
Download Add-in A-Tools