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 (2 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,790
Được thích
10,299
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:
Web KT

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

Back
Top Bottom