CÔNG TY CỔ PHẦN BLUESOFTS

Lập trình liệt kê tổ hợp, chỉnh hợp lặp, chỉnh hợp không lặp với hàm API BS_COMBINLIST của Add-in A-Tools

  Add-in A-Tools có hàm UDF BS_COMBINLIST để 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 Add-in A-Tools để thực hành các ví dụ trong bài viết này.

1. Ngôn ngữ lập trình VBA
(Liệt kê tôt hợp, chỉnh hợp băng VBA với hàm API BS_COMBINLIST)

Đầu tiên chúng ta cần khai báo hàm API - import hàm. BS_COMBINLIST trong AddinATools.dll
Tạo một module, copy mã nguồn VBA dưới đây:
'BEGIN COPY -----------------------------
' Import the CombinListAPI function from AddinATools.dll
#If VBA7 Then
Declare PtrSafe Function CombinListAPI Lib "AddinATools.dll" Alias "BS_COMBINLIST" ( _
                           ByVal SrcArr As Variant, _
                           ByVal number_chosen As Long, _
                           ByVal result_type As Long, _
                           ByVal Options As Variant, _
                           ByVal pFuncCallback As LongPtr, _
                           ByRef pRes As Variant) As LongPtr
#Else
Declare Function CombinListAPI Lib "AddinATools.dll" Alias "BS_COMBINLIST" ( _
                           ByVal SrcArr As Variant, _
                           ByVal number_chosen As Long, _
                           ByVal result_type As Long, _
                           ByVal Options As Variant, _
                           ByVal pFuncCallback As Long, _
                           ByRef res As Variant) As Long
#End If

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
 
- 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 '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 --------------------------------------------------------


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