Hàm BS COMBINLIST Liệt kê các giá trị tổ hợp, chỉnh hợp, chỉnh hợp không lặp | Add-in A-Tools v10 (3 người xem)

Liên hệ QC

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

  • Nguyễn Duy Tuân

    Nghị Hách
    Thành viên danh dự
    Tham gia
    13/6/06
    Bài viết
    4,849
    Được thích
    10,341
    Giới tính
    Nam
    Nghề nghiệp
    Giáo viên, CEO tại Bluesofts
    Đây là một siêu hàm của Add-in A-Tools v10 (2024). Hàm cho phép tính ra số giá trị tổ hợp, liệt kê tất cả các giá trị tổ hợp trong Excel. Hàm có nhiều tùy chọn đáp ứng đa dạng về nhu cầu làm tổ hợp.

    Cấu trúc hàm

    BS_COMBINLIST(
    source, number_chosen, [result_type], [options])

    Các tham số


    Các tham số trong [ ] có thể bỏ qua.

    - source: là mảng giá trị phục vụ cho việc lấy tổ hợp. Nếu tham số này là số nguyên hàm trả về giá trị tổ hợp.

    - number_chosen: là số giá trị được gộp lại. number_chosen <= số phần tử mảng của source.

    + Nếu giá trị của tham số này giá trị là: < 0 hoặc lớn hơn số phần tử của source trả về lỗi #NUM!
    + Nếu giá trị tham số này là 0 hàm trả về một trường hợp mà các phần tử hợp thành chính là các phần tử của mảng source.

    - result_type: nếu là 0 - Các phần tử hợp lại tạo thành mảng; Nếu là 1 thì các phần tử được nối lại và ngăn cách bởi dấu phảy (,); Nếu là -1 hàm trả về số trường tính theo tổ hợp. : là giá trị cần tìm, là một con số hoặc biểu thức so sánh.

    - options: tham số này kiểu chuỗi, có thể bỏ qua. Các thuộc tính sử dụng:
    + Nếu muốn thay đổi ký tự ghép giá trị (khi tham số result_type=1), nhập "SEP=Delimiter;". Delimiter là ký tự bất kỳ, nếu là NULL hàm không dùng ký tự để ghép.
    + Quy đị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 quá thời gian TIMEOUT sẽ dừng lại. Nếu không khai báo hàm chạy đến khi hoàn thành.
    + Chỉ định số giá trị: "TOP=n;" n là số giá trị tối đa được lấy ra. Nếu không khai báo hàm lấy ra tất cả trường hợp. Hàm chạy trên bảng tính (UDF) lấy tối đa 1048576 giá trị với Excel 2007 trở lên, 65536 với phiên bản Excel thấp hơn. Nếu bạn muốn lấy số giá trị lớn hơn thì cần chạy hàm API trong môi trường VBA hay ngôn ngữ khác.

    Tùy chọn cách tính của hàm: Với tham số OPTIONS bạn nhập các thuộc tính để nhận kết quả khác nhau:
    "FUNC=PERMUTA;" hoặc "FUNC=1;" hàm tính chỉnh hợp lặp.
    "FUNC=PERMUT;" hoặc "FUNC=2;" hàm tính chỉnh hợp không lặp.
    "FUNC=COMBIN;" hoặc "FUNC=0;" hoặc không khai báo (ngầm định) hàm tính tổ hợp.

    Video hướng dẫn

    (*) Hướng dẫn chi tiết BS_COMBINLIST
    (*) Download Add-in A-Tools
     
    Lần chỉnh sửa cuối:
    Thiếu mất Anh Tú, Đức Tuân, Tuấn Anh, Hoàng max, Đạt trố...
     
    Từ phiên bản Add-in A-Tools v10 ngày 22-10-2024 cung cấp hàm API BS_COMBINLIST cho phép chạy đủ tính năng trong phiên bản FREE. Tại sao đã có hàm UDF này trong Add-in A-Toools rồi chúng tôi lại cung cấp API? Bởi vì với hàm UDF BS_COMBINLIST gọi ở trên bảng tính nó bị giới hạn tối đa 1048576 với Excel 2007 hoặc cao hơn, 65536 với Excel 2003 hoặc thấp hơn. Hàm API BS_COMBINLIST gọi trong môi trường lập trình cho phép xử lý hành tỷ giá trị mà không bị ảnh hưởng đến giới hạn của bộ nhớ máy tính. Đây làm hàm API được lập trình để người lập trình gọi nó trong nhiều ngôn ngữ lập trình như VBA, DELPHI, C#, VB.NET, C++. Mã nguồn các ngôn ngữ tôi trình bày trong link dưới đây các bạn có thể trải nghiệm.

    https://bluesofts.net/Lap-trinh-voi...-voi-ham-API-BS_COMBINLIST-cua-Add-in-A-Tools

    Hoặc xem ví dụ tại: "C:\A-Tools\HELP & DEMOS\A-Tools VBA Programming\BS_COMBINLIST API\"

    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:

    C#:
    '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

    BS_COMBINLIST-API-code1.png.aspx


    (*) Lưu ý

    Các tham số trong hàm API cơ bản giống với hàm UDF BS_COMBINLIST. Tuy nhiên có hai tham số và cách trả về của hàm API cần lưu ý dưới đây:

    - 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
    Function Callback(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:

    C++:
    //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:

    C++:
    //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.

    BS_COMBINLIST-API-code5-Delphi.png.aspx


    BS_COMBINLIST-API-code2-Delphi.png.aspx



    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:

    C#:
    //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:

    C#:
    //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.
     
    Lần chỉnh sửa cuối:
    Dự là rất ít người cần đến hàm này

    Các bài toán ứng dụng trong xác suất thống kê thì Excel có các hàm tính số tổ hợp - COMBIN; chỉnh hợp PERMUT; chỉnh hợp lặp PERMUTATIONA; hoán vị - FACT. Hàm BS_COMBINLIST tôi đưa thêm một dạng ứng dụng là liệt kê các trường hợp đó ngoài cách trả về số trường hợp. Những người làm kế toán, tài chính thì những hàm toán học, xác suất sẽ rất ít dùng, còn trong lính vực khác trong cuộc sống thì vẫn ứng dụng. Hàm này được dùng miễn phí trong Add-in A-Tools, thêm một hướng ứng dụng ngoài các hàm trọng tâm phục vụ kế toán, tài chính mà Add-in A-Tools đã có.
    Một vài câu hỏi liên quan các hàm xác suất tại đây: https://tech12h.com/bai-tap/chia-da...am-4-moi-10-hoc-sinh-em-hay-tinh-so-cach-phan
     
    Dựa vào gợi ý hàm của anh @Nguyễn Duy Tuân em viết thử bằng C# không biết có đúng không. Phải đặt giới hạn 10.000 không bị treo máy

    Mã:
    public static class M_CombinationHelper
    {
        public static object BS_COMBINLIST(string source, int number_chosen, int result_type = 0, string options = "")
        {
            try
            {
                // Phân tích cú pháp nguồn
                List<string> sourceList = ParseSource(source);
                if (sourceList == null || sourceList.Count == 0)
                    return "Invalid source";
    
                // Xác thực number_chosen
                if (number_chosen < 0 || number_chosen > sourceList.Count)
                    return "#NUM!";
                if (number_chosen == 0)
                    return sourceList.ToArray();
    
                // Tùy chọn phân tích cú pháp
                char delimiter = ',';
                int timeoutMinutes = -1;
                int top = -1;
                string funcType = "COMBIN";
    
                ParseOptions(options, ref delimiter, ref timeoutMinutes, ref top, ref funcType);
    
                // Tạo kết hợp hoặc hoán vị
                IEnumerable<IEnumerable<string>> combinations;
                if (funcType == "PERMUTA" || funcType == "1")
                {
                    combinations = GetPermutationsWithRepetition(sourceList, number_chosen);
                }
                else if (funcType == "PERMUT" || funcType == "2")
                {
                    combinations = GetPermutations(sourceList, number_chosen);
                }
                else
                {
                    combinations = GetCombinations(sourceList, number_chosen);
                }
    
                // Áp dụng giới hạn TOP nếu được chỉ định
                if (top > 0)
                {
                    combinations = combinations.Take(top);
                }
    
                // Xử lý result_type
                if (result_type == -1)
                {
                    return combinations.Count();
                }
                else if (result_type == 1)
                {
                    return combinations.Select(c => string.Join(delimiter.ToString(), c)).ToArray();
                }
                else
                {
                    return combinations.Select(c => c.ToArray()).ToArray();
                }
            }
            catch (Exception)
            {
                return "Error occurred";
            }
        }
    
        private static List<string> ParseSource(string source)
        {
            return source.Split(',').ToList();
        }
    
        private static void ParseOptions(string options, ref char delimiter, ref int timeoutMinutes, ref int top, ref string funcType)
        {
            if (string.IsNullOrEmpty(options)) return;
    
            var optionParts = options.Split(';');
            foreach (var part in optionParts)
            {
                if (part.StartsWith("SEP=", StringComparison.OrdinalIgnoreCase))
                {
                    delimiter = part.Substring(4)[0];
                }
                else if (part.StartsWith("TIMEOUT=", StringComparison.OrdinalIgnoreCase))
                {
                    if (int.TryParse(part.Substring(8), out int minutes))
                    {
                        timeoutMinutes = minutes;
                    }
                }
                else if (part.StartsWith("TOP=", StringComparison.OrdinalIgnoreCase))
                {
                    if (int.TryParse(part.Substring(4), out int value))
                    {
                        top = value;
                    }
                }
                else if (part.StartsWith("FUNC=", StringComparison.OrdinalIgnoreCase))
                {
                    funcType = part.Substring(5).ToUpper();
                }
            }
        }
    
        private static IEnumerable<IEnumerable<T>> GetCombinations<T>(IEnumerable<T> list, int length)
        {
            if (length == 1) return list.Select(t => new T[] { t });
            return GetCombinations(list, length - 1)
                .SelectMany(t => list.Where(e => !t.Contains(e)), (t1, t2) => t1.Concat(new T[] { t2 }));
        }
    
        private static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> list, int length)
        {
            if (length == 1) return list.Select(t => new T[] { t });
            return GetPermutations(list, length - 1)
                .SelectMany(t => list.Where(e => !t.Contains(e)), (t1, t2) => t1.Concat(new T[] { t2 }));
        }
    
        private static IEnumerable<IEnumerable<T>> GetPermutationsWithRepetition<T>(IEnumerable<T> list, int length)
        {
            if (length == 1) return list.Select(t => new T[] { t });
            return GetPermutationsWithRepetition(list, length - 1)
                .SelectMany(t => list, (t1, t2) => t1.Concat(new T[] { t2 }));
        }
    
        public static async Task Main()
        {
            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";
            var result = BS_COMBINLIST(s, 6, 1, "SEP=-;FUNC=COMBIN;TOP=10000;");
    
    
            Excel.Workbook xlWb = Globals.ThisAddIn.GetActiveWorkbook();
            Excel.Worksheet activeSheet = xlWb.ActiveSheet;
    
            activeSheet.Cells[1, 1].Value = "STT";
            activeSheet.Cells[1, 2].Value = "Tổ hợp có thể";
    
            int row = 2;
            foreach (var item in (object[])result)
            {
                activeSheet.Cells[row, 1].Value = row - 1;
                activeSheet.Cells[row, 2].Value = item;
                row++;
                if (row % 100 == 0)
                {
                    System.Windows.Forms.Application.DoEvents();
                }
            }
        }
    }
    1729693672957.png
     
    Dựa vào gợi ý hàm của anh @Nguyễn Duy Tuân em viết thử bằng C# không biết có đúng không. Phải đặt giới hạn 10.000 không bị treo máy

    Mã:
    public static class M_CombinationHelper
    {
        public static object BS_COMBINLIST(string source, int number_chosen, int result_type = 0, string options = "")
        {
            try
            {
                // Phân tích cú pháp nguồn
                List<string> sourceList = ParseSource(source);
                if (sourceList == null || sourceList.Count == 0)
                    return "Invalid source";
    
                // Xác thực number_chosen
                if (number_chosen < 0 || number_chosen > sourceList.Count)
                    return "#NUM!";
                if (number_chosen == 0)
                    return sourceList.ToArray();
    
                // Tùy chọn phân tích cú pháp
                char delimiter = ',';
                int timeoutMinutes = -1;
                int top = -1;
                string funcType = "COMBIN";
    
                ParseOptions(options, ref delimiter, ref timeoutMinutes, ref top, ref funcType);
    
                // Tạo kết hợp hoặc hoán vị
                IEnumerable<IEnumerable<string>> combinations;
                if (funcType == "PERMUTA" || funcType == "1")
                {
                    combinations = GetPermutationsWithRepetition(sourceList, number_chosen);
                }
                else if (funcType == "PERMUT" || funcType == "2")
                {
                    combinations = GetPermutations(sourceList, number_chosen);
                }
                else
                {
                    combinations = GetCombinations(sourceList, number_chosen);
                }
    
                // Áp dụng giới hạn TOP nếu được chỉ định
                if (top > 0)
                {
                    combinations = combinations.Take(top);
                }
    
                // Xử lý result_type
                if (result_type == -1)
                {
                    return combinations.Count();
                }
                else if (result_type == 1)
                {
                    return combinations.Select(c => string.Join(delimiter.ToString(), c)).ToArray();
                }
                else
                {
                    return combinations.Select(c => c.ToArray()).ToArray();
                }
            }
            catch (Exception)
            {
                return "Error occurred";
            }
        }
    
        private static List<string> ParseSource(string source)
        {
            return source.Split(',').ToList();
        }
    
        private static void ParseOptions(string options, ref char delimiter, ref int timeoutMinutes, ref int top, ref string funcType)
        {
            if (string.IsNullOrEmpty(options)) return;
    
            var optionParts = options.Split(';');
            foreach (var part in optionParts)
            {
                if (part.StartsWith("SEP=", StringComparison.OrdinalIgnoreCase))
                {
                    delimiter = part.Substring(4)[0];
                }
                else if (part.StartsWith("TIMEOUT=", StringComparison.OrdinalIgnoreCase))
                {
                    if (int.TryParse(part.Substring(8), out int minutes))
                    {
                        timeoutMinutes = minutes;
                    }
                }
                else if (part.StartsWith("TOP=", StringComparison.OrdinalIgnoreCase))
                {
                    if (int.TryParse(part.Substring(4), out int value))
                    {
                        top = value;
                    }
                }
                else if (part.StartsWith("FUNC=", StringComparison.OrdinalIgnoreCase))
                {
                    funcType = part.Substring(5).ToUpper();
                }
            }
        }
    
        private static IEnumerable<IEnumerable<T>> GetCombinations<T>(IEnumerable<T> list, int length)
        {
            if (length == 1) return list.Select(t => new T[] { t });
            return GetCombinations(list, length - 1)
                .SelectMany(t => list.Where(e => !t.Contains(e)), (t1, t2) => t1.Concat(new T[] { t2 }));
        }
    
        private static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> list, int length)
        {
            if (length == 1) return list.Select(t => new T[] { t });
            return GetPermutations(list, length - 1)
                .SelectMany(t => list.Where(e => !t.Contains(e)), (t1, t2) => t1.Concat(new T[] { t2 }));
        }
    
        private static IEnumerable<IEnumerable<T>> GetPermutationsWithRepetition<T>(IEnumerable<T> list, int length)
        {
            if (length == 1) return list.Select(t => new T[] { t });
            return GetPermutationsWithRepetition(list, length - 1)
                .SelectMany(t => list, (t1, t2) => t1.Concat(new T[] { t2 }));
        }
    
        public static async Task Main()
        {
            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";
            var result = BS_COMBINLIST(s, 6, 1, "SEP=-;FUNC=COMBIN;TOP=10000;");
    
    
            Excel.Workbook xlWb = Globals.ThisAddIn.GetActiveWorkbook();
            Excel.Worksheet activeSheet = xlWb.ActiveSheet;
    
            activeSheet.Cells[1, 1].Value = "STT";
            activeSheet.Cells[1, 2].Value = "Tổ hợp có thể";
    
            int row = 2;
            foreach (var item in (object[])result)
            {
                activeSheet.Cells[row, 1].Value = row - 1;
                activeSheet.Cells[row, 2].Value = item;
                row++;
                if (row % 100 == 0)
                {
                    System.Windows.Forms.Application.DoEvents();
                }
            }
        }
    }
    View attachment 304993

    Bạn kiểm tra lại mã nguồn của bạn, vì 10000 mà đã treo máy thì cần phải tối ưu hơn. Bạn viết code C# khá hay đấy.
     
    Bạn kiểm tra lại mã nguồn của bạn, vì 10000 mà đã treo máy thì cần phải tối ưu hơn. Bạn viết code C# khá hay đấy.
    Nếu không đặt giá trị TOP thì máy treo còn đặt giá trị TOP là 10.000 thì hết thời gian như hình, có thể do ghi xuống Sheet lâu hơn khi xuất kết quả ra console ạ

    1729733600843.png
     
    Nếu không đặt giá trị TOP thì máy treo còn đặt giá trị TOP là 10.000 thì hết thời gian như hình, có thể do ghi xuống Sheet lâu hơn khi xuất kết quả ra console ạ

    View attachment 304995

    Ghi giá trị ra sheet là ảnh hưởng đến tốc độ là đúng rồi đó. Nếu bạn muốn ghi ra sheet thì nên ghi theo mảng. Cứ đóng gói từng mảng với một kích thước nhất định, đủ thì điền ra, sau khi điền hết chu trình lấy giá trị thì điền mảng còn lại ra nốt.

    Range("A1").Resize(ArrayRowCount, ArrayColCount).Value = resArray;
     
    Mình thêm ví dụ nữa về dùng hàm API BS_COMBINLIST bằng ngôn ngữ C++, dự án tạo bằng Visual C++ 2019.
    (Toàn bộ mã nguồn ứng dụng hàm API với các ngôn ngữ lập trinhg VBA, DELPHI, C#, VB.NET, C++ tại đường dẫn: "C:\A-Tools\HELP & DEMOS\A-Tools VBA Programming\BS_COMBINLIST API\")

    C++:
    // Author: Nguyen Duy Tuan - duytuan@bluesofts.net
    //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ần chỉnh sửa cuối:
    Mình thêm ví dụ nữa về dùng hàm API BS_COMBINLIST bằng ngôn ngữ C++, dự án tạo bằng Visual C++ 2019.
    (Toàn bộ mã nguồn ứng dụng hàm API với các ngôn ngữ lập trinhg VBA, DELPHI, C#, VB.NET, C++ tại đường dẫn: "C:\A-Tools\HELP & DEMOS\A-Tools VBA Programming\BS_COMBINLIST API\")

    C++:
    // Author: Nguyen Duy Tuan - duytuan@bluesofts.net
    //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---------------------------------------------
    Xem qua code, tôi thấy nhiều chỗ hơi cấn:
    1. Nếu muốn dùng các lớp gói gọn việc giải phóng bộ nhớ của các kiểu BSTR, VARIANT thì chỉ cần dùng _bstr_t hay _variant_t có sẵn của trình biên dịch Visual C++ cho gọn nhẹ, sao lại dùng header của ATL Project trong khi cái này không phải ai cũng có điều kiện cài đặt vào máy tính.
    2. Code của bạn có nhiều chỗ không kiểm tra giá trị HRESULT hoặc con trỏ mà hàm trả về, đâu thể nào cứ chạy xong hàm đó là mặc định rằng hàm chạy ok hoặc trả về giá trị hợp lệ.
    VD:
    C++:
    // 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]);
        }
    Bạn tạo một mảng SAFEARRAY và trả về con trỏ psa, nhưng bạn lại không kiểm tra xem con trỏ psa trả về có bị NULL (hoặc nullptr, hoặc 0) hay không (nguyên nhân có thể máy tính hết bộ nhớ hoặc tham số truyền vào không hợp lệ) mà cứ thế dùng tiếp hàm SafeArrayPutElement, mà hàm ghi giá trị vào phần tử của mảng này lại tiếp tục không kiểm tra xem giá trị HRESULT đã S_OK hay chưa, nên việc sập chương trình (crash) rất dễ xảy ra.
    Tiếp theo,
    C++:
     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);
    Bạn khởi tạo biến varArr kiểu VARIANT nhưng lại không gọi hàm VariantInit trước khi sử dụng biến, điều này khá rủi ro khi biến varArr có thể mang giá trị không hợp lệ ngay từ đầu, khiến chương trình có hành vi không xác định được hoặc thậm chí gây sập chương trình. Dưới đây là khuyến cáo của Microsoft.
    1756269074581.png
    Ngoài ra trong các hàm có đối số là con trỏ, tôi không thấy bạn kiểm tra xem giá trị của con trỏ có bị NULL hay không mà cứ thế sử dụng.
    Trên đây là những ý kiến góp ý của tôi, hi vọng bạn có thể khắc phục để những người đọc sau này có thể sử dụng code một cách hiệu quả nhất.
     
    Xem qua code, tôi thấy nhiều chỗ hơi cấn:
    1. Nếu muốn dùng các lớp gói gọn việc giải phóng bộ nhớ của các kiểu BSTR, VARIANT thì chỉ cần dùng _bstr_t hay _variant_t có sẵn của trình biên dịch Visual C++ cho gọn nhẹ, sao lại dùng header của ATL Project trong khi cái này không phải ai cũng có điều kiện cài đặt vào máy tính.
    2. Code của bạn có nhiều chỗ không kiểm tra giá trị HRESULT hoặc con trỏ mà hàm trả về, đâu thể nào cứ chạy xong hàm đó là mặc định rằng hàm chạy ok hoặc trả về giá trị hợp lệ.
    VD:
    C++:
    // 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]);
        }
    Bạn tạo một mảng SAFEARRAY và trả về con trỏ psa, nhưng bạn lại không kiểm tra xem con trỏ psa trả về có bị NULL (hoặc nullptr, hoặc 0) hay không (nguyên nhân có thể máy tính hết bộ nhớ hoặc tham số truyền vào không hợp lệ) mà cứ thế dùng tiếp hàm SafeArrayPutElement, mà hàm ghi giá trị vào phần tử của mảng này lại tiếp tục không kiểm tra xem giá trị HRESULT đã S_OK hay chưa, nên việc sập chương trình (crash) rất dễ xảy ra.
    Tiếp theo,
    C++:
     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);
    Bạn khởi tạo biến varArr kiểu VARIANT nhưng lại không gọi hàm VariantInit trước khi sử dụng biến, điều này khá rủi ro khi biến varArr có thể mang giá trị không hợp lệ ngay từ đầu, khiến chương trình có hành vi không xác định được hoặc thậm chí gây sập chương trình. Dưới đây là khuyến cáo của Microsoft.
    View attachment 309434
    Ngoài ra trong các hàm có đối số là con trỏ, tôi không thấy bạn kiểm tra xem giá trị của con trỏ có bị NULL hay không mà cứ thế sử dụng.
    Trên đây là những ý kiến góp ý của tôi, hi vọng bạn có thể khắc phục để những người đọc sau này có thể sử dụng code một cách hiệu quả nhất.

    Biến varArr chuẩn thì phải dùng VariantInit(varArr) trước đó để đảm bảo nó sạch, tuy nhiên khi mình gán:
    varArr.vt = VT_ARRAY | VT_VARIANT;
    varArr.parray = psa;
    Tức đã đảm bảo đầu vào cho cấu trúc VARIANT là đủ rồi nên cứ để vậy.

    Một số các lệnh gọi hàm API mình bỏ qua việc kiểm tra HRESULT có bằng S_OK không vì kiểu làm nhanh, không phải làm kỹ càng, tạm thừa nhận mọi thứ diễn ra ok. Nếu kiểm tra kỹ từng dòng một khối lượng code nhiều quá, nên ai đó ứng dụng với ngôn ngữ C/C++ nếu họ có kiến thức sẽ tự thêm thắp cho chặt chẽ sau.

    Làm việc với kiểu VARIANT trong C++ mình thấy dùng lớp CComVariant thấy nó cũng tiện nên cứ lấy dùng. Tôi thấy bạn có kiến thức về C/C++ tốt nên nếu có thời gian bạn có thể viết lại ví dụ tại bài #12 bằng C/C++ nhé. Cảm ơn bạn đã góp ý.
     
    Biến varArr chuẩn thì phải dùng VariantInit(varArr) trước đó để đảm bảo nó sạch, tuy nhiên khi mình gán:
    varArr.vt = VT_ARRAY | VT_VARIANT;
    varArr.parray = psa;
    Tức đã đảm bảo đầu vào cho cấu trúc VARIANT là đủ rồi nên cứ để vậy.

    Một số các lệnh gọi hàm API mình bỏ qua việc kiểm tra HRESULT có bằng S_OK không vì kiểu làm nhanh, không phải làm kỹ càng, tạm thừa nhận mọi thứ diễn ra ok. Nếu kiểm tra kỹ từng dòng một khối lượng code nhiều quá, nên ai đó ứng dụng với ngôn ngữ C/C++ nếu họ có kiến thức sẽ tự thêm thắp cho chặt chẽ sau.

    Làm việc với kiểu VARIANT trong C++ mình thấy dùng lớp CComVariant thấy nó cũng tiện nên cứ lấy dùng. Tôi thấy bạn có kiến thức về C/C++ tốt nên nếu có thời gian bạn có thể viết lại ví dụ tại bài #12 bằng C/C++ nhé. Cảm ơn bạn đã góp ý.
    Khi lập trình bằng C/C++, phần quản lý bộ nhớ do lập trình viên chịu trách nhiệm hoàn toàn chứ không có kiểu tự động làm tất như VB6/VBA, mấy cái lớp gói gọn của Delphi, Java và .NET (trình dọn dẹp rác hay garbage collector) nên cần phải viết code thật kỹ, việc kiểm tra giá trị mà hàm trả về để xem hàm đã được thực thi thành công hay chưa cũng đóng vai trò quan trọng trong việc định hình luồng chương trình (thành công thì tiếp tục xử lý, còn thất bại thì tiến hành dọn dẹp bộ để đảm bảo tình trạng rò rỉ bộ nhớ không xảy ra). Không phải ngẫu nhiên mà những hàm liên quan đến COM đều có quy tắc là phải trả về giá trị HRESULT, các hàm Win32 API đa số đều trả về giá trị BOOL thay vì void.
    Mình không tính viết lại code của bạn mà chỉ thêm code theo ý của #13, code của bạn dùng cho ứng dụng dạng dòng lệnh (console application) nên phần dọn dẹp bộ nhớ ở cuối chương trình hơi thừa thãi vì khi kết thúc chương trình Windows sẽ tự thu hồi tài nguyên đã cấp phát cho chương trình, tuy vậy sẽ là thực hành tốt khi chủ động giải phóng tài nguyên được cấp phát trước khi kết thúc chương trình/hàm.
    C++:
    #include <windows.h>
    #include <iostream>
    #include <string>
    #include <vector>
    #include <ctime>
    #include <comutil.h>
    #include <stdio.h>
    #include <atlbase.h>
    
    int main() {
        /*
            Code khởi tạo các biến cần thiết
        */
    
        // Tạo mảng VARIANT (SAFEARRAY)
        HRESULT hr = S_OK;
        SAFEARRAYBOUND bounds[1];
        bounds[0].lLbound = 0;
        bounds[0].cElements = ULONG(arr.size());
        SAFEARRAY* psa = SafeArrayCreate(VT_VARIANT, 1, bounds);
        if (!psa) {
            wprintf_s(L"Không thể cấp phát bộ nhớ cho SAFEARRAY\n");
            //Code dọn dẹp vùng bộ nhớ khác đã cấp phát ở đây
            FreeLibrary(hInstLib);
    
            return 1;
        }
        for (LONG i = 0; i < LONG(arr.size()); ++i) {
            hr = SafeArrayPutElement(psa, &i, &arr[i]);
            if (FAILED(hr)) {
                wprintf_s(L"Hàm SafeArrayPutElement thực hiện không thành công. Mã lỗi: %u\n", hr);
                //Dọn dẹp mảng
                SafeArrayDestroy(psa);
                //Dọn dẹp vùng bộ nhớ khác đã cấp phát ở đây
                FreeLibrary(hInstLib);
    
                return 1;
            }
        }
        /*
            Khởi tạo biến varArr kiểu VARIANT (struct)
            Trường hợp này cấp phát trên vùng nhớ stack là ổn rồi vì dữ liệu ít, không cần truyền cho các hàm/thread khác
            Cách 1: VARIANT varArr{};
            Cách 2 (kiểu cổ điển của C):
                VARIANT varArr;
                ZeroMemory(&varArr, sizeof(VARIANT));
            Cách 3: VARIANT varArr = { 0 };
        */
        VARIANT varArr{};
        VariantInit(&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;
    }
    Tôi nghĩ với kiến thức của bạn về C++, bạn sẽ biết cách sửa lại code một cách tối ưu nhất.
     
    Tôi nghĩ với kiến thức của bạn về C++, bạn sẽ biết cách sửa lại code một cách tối ưu nhất.

    Uh. Vì trên đây là GPE nên khi chia sẻ thêm mã nguồn của ngôn ngữ khác ngoài VBA tôi chưa đầu tư code đầy đủ thực sự, chủ yếu mang tính định hình phần khung chính.
     
    Uh. Vì trên đây là GPE nên khi chia sẻ thêm mã nguồn của ngôn ngữ khác ngoài VBA tôi chưa đầu tư code đầy đủ thực sự, chủ yếu mang tính định hình phần khung chính.
    Nói chung là lập trình với C/C++ phải thật cẩn thận, kỹ lưỡng vì mọi việc đều phải do lập trình viên tự xử lý chứ không như những ngôn ngữ lập trình khác gánh vác hầu hết việc khó phía sau hậu trường cho lập trình viên, nên không thể nào mang tư duy của VBA vào C/C++ được.
    Việc viết code cho người học vì thế cũng cần phải kỹ lưỡng, ví dụ như một số code mẫu của Microsoft (thực chất đều viết bằng C), mỗi hàm xử lý đều có khối lệnh kiểm tra hàm đã thực thi thành công hay chưa, không thể chỉ vì muốn code ngắn gọn mà làm qua loa được.

    1756279570633.png
     

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

    Back
    Top Bottom