VBA Nâng cao: Xử lý chuỗi và mảng cấp cao nhất tại Memory

Liên hệ QC

HeSanbi

Nam Nhân✨Hiếu Lễ Nghĩa Trí Tín✨
Tham gia
24/2/13
Bài viết
2,613
Được thích
4,055
Giới tính
Nam
Xin chào các bạn hôm nay tôi chia sẻ cho các bạn kiến thức xử lý chuỗi và mảng trong VBA tại Memory để tốc độ xử lý nhanh hơn.
Mẹo xử lý chuỗi và mảng tại Memory này rất đơn giản, giúp các bạn tăng tốc mã, tiết kiệm khá nhiều thời gian chạy.



XỬ LÝ KÝ TỰ CHUỖI

Bài viết dưới đây tận dụng hàm API RtlMoveMemory

JavaScript:
#If VBA7 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
#Else
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
#End If
Private Type mmStr
  s(1) As Byte
End Type

Ví dụ lặp qua từng ký tự chuỗi:
Đoạn mã dưới đây, có hai phương thức: 1 là sao chép bộ nhớ và 2 là sử dụng hàm MID để lặp qua 10 triệu ký tự.
Tốc độ là xử lý memory nhanh xấp xỉ gắp đôi hàm MID$ (có ký tự $ nhanh hơn)

JavaScript:
Private Sub SplitStringToCharArray_test()
  Dim a() As mmStr, s$, l&, b() As String, t1, t2, t3, v$
  s = String(10000000, "a"):
  t1 = Timer
  l = Len(s)
  ReDim a(1 To l)
  CopyMemory ByVal VarPtr(a(1)), ByVal StrPtr(s), l * 2
  For i = 1 To l
    v = a(i).s
  Next
  t2 = Timer
  ReDim b(1 To l)
  For i = 1 To l
    b(i) = Mid$(s, i, 1)
  Next
  Debug.Print "Mem: "; t2 - t1, "MID: " Timer - t2
End Sub
Giải thuật ở trên là sao chép lại chuỗi từ vùng nhớ gán vào Type có 2 byte bộ nhớ. Vì chuỗi cũng cần 2 byte để lưu trữ nên việc gán này sẽ tương thích.

Lưu ý: bộ nhớ từng kiểu biến là khác nhau, không thể tùy ý sao chép bộ nhớ. Sẽ làm sập tiến trình VBA và Excel.


XỬ LÝ MẢNG - TRANSPOSE

Dưới đây là hàm Transpose UDF nhanh hơn hàm Transpose (Application WorksheetFunction), kỹ thuật sao chép bộ nhớ tương đương xử lý chuỗi ở trên.
Giải thuật là sao chép bộ nhớ với kiểu lưu trữ là Variant (8 byte 32/16 byte 64), tạo một mảng bộ nhớ giả lập, gán lại mảng ban đầu, sau đó thay đổi chiều của vùng nhớ mảng .
Giải thuật này tốn kém thêm 2 lần bộ nhớ mảng đầu vào, nhưng đánh đổi lại là tốc độ.

JavaScript:
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
Private Declare PtrSafe Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As LongPtr)
#Else
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As Long)
#End If
#If VBA7 = 0 Then
   Private Enum LongPtr:[_]:End Enum
#End If
Private Const nullptr As LongPtr = 0

#If Win64 Then
Private Const PTR_SIZE& = 8
Private Const VAR_SIZE& = 24
#Else
Private Const PTR_SIZE& = 4
Private Const VAR_SIZE& = 16
#End If

Private Type SAFEARRAYBOUND
  cElements    As Long
  lLbound      As Long
End Type
Private Type SAFEARRAY
  cDims           As Integer
  fFeatures       As Integer
  cbElements      As Long
  cLocks          As Long
  pvData          As LongPtr
  rgsabound(1 To 2)  As SAFEARRAYBOUND
End Type

Private Type mmVar
  b(1 To VAR_SIZE) As Byte
End Type

Function ArrayTranspose(ByRef srcArray As Variant, Optional rowBase1 As Integer = -1, Optional columnBase1 As Integer = -1) As Boolean
  If Not IsArray(srcArray) Then Exit Function
  Dim p As LongPtr, pA As LongPtr, Pb As LongPtr, pSA As LongPtr, t As LongPtr
  Dim s As SAFEARRAY, bo As SAFEARRAYBOUND, a() As mmVar, b() As mmVar
  Dim r&, c&, j3&, l1&, l2&, u1&, u2&
  pSA = VarPtr(srcArray) + PTR_SIZE: CopyMemory pSA, ByVal pSA, PTR_SIZE: CopyMemory s, ByVal pSA, LenB(s)
  If s.pvData = 0 Then Exit Function
  With s
    Select Case .cDims
    Case 2: With .rgsabound(2): l1 = .lLbound: u1 = .cElements + l1 - 1: End With
    End Select
    With .rgsabound(1): l2 = .lLbound: u2 = .cElements + l2 - 1: End With
  End With
  Select Case s.cDims
  Case 1
    t = (u2 - l2 + 1) * VAR_SIZE
    ReDim arr(l2 To u2, 1 To 1)
    p = VarPtr(srcArray(l2)): pA = VarPtr(arr(l2, 1))
    CopyMemory ByVal pA, ByVal p, t
    ZeroMemory ByVal p, t
    srcArray = arr
  Case 2:
    t = (u1 - l1 + 1) * (u2 - l2 + 1) * VAR_SIZE
    ReDim a(l1 To u1, l2 To u2): ReDim b(l2 To u2, l1 To u1)
    ' lâìy con troÒ tõìi dýÞ liêòu maÒng
    p = VarPtr(srcArray(l1, l2)): pA = VarPtr(a(l1, l2)): Pb = VarPtr(b(l2, l1))
    ' Sao chép nôòi dung cuÒa maÒng src sang a 1 và lýu maÒng ðaÞ chuyêÒn ðôÒi vào b 1
    CopyMemory ByVal pA, ByVal p, t
    For r = l1 To u1: For c = l2 To u2: b(c, r) = a(r, c): Next c, r
    ' ghi các giá triò ðaÞ chuyêÒn ðôÒi trõÒ laòi maÒng src
    CopyMemory ByVal p, ByVal Pb, t
    ' thay ðôÒi kích thýõìc cuÒa maÒng src trong câìu trúc safearray
    With s
      bo = .rgsabound(1): .rgsabound(1) = .rgsabound(2): .rgsabound(2) = bo
      If rowBase1 > 0 Then .rgsabound(2).lLbound = rowBase1
      If columnBase1 > 0 Then .rgsabound(1).lLbound = columnBase1
    End With
    CopyMemory ByVal pSA, s, LenB(s)
    ZeroMemory ByVal Pb, t
  End Select
  ZeroMemory ByVal pA, t
  ArrayTranspose = True
End Function
(Tiếng Việt trong mã sử dụng font Courier new Vietnamese)

Lưu ý: Trong hàm ArrayTranspose tôi sử dụng vòng lặp For, để nhanh hơn nữa hãy tận dụng vòng lặp For Each.



XỬ LÝ MẢNG - ĐẢO, XOAY, CẮT



JavaScript:
Function ArrayRotate90(ByVal srcArray As Variant)
  ArrayRotate90 = srcArray: ArrayCarveMM ArrayRotate90, 1, 1, 0
'  Base Array:                     Transpose + FlipVertical:
'  ------------------------        -------------------
'  R1C1 R1C2 R1C3 R1C4 R1C5        R4C1 R3C1 R2C1 R1C1
'  R2C1 R2C2 R2C3 R2C4 R2C5        R4C2 R3C2 R2C2 R1C2
'  R3C1 R3C2 R3C3 R3C4 R3C5        R4C3 R3C3 R2C3 R1C3
'  R4C1 R4C2 R4C3 R4C4 R4C5        R4C4 R3C4 R2C4 R1C4
'  ////////////////////////        R4C5 R3C5 R2C5 R1C5
'                                  ///////////////////
End Function
Function ArrayRotate180(ByVal srcArray As Variant)
  ArrayRotate180 = srcArray: ArrayCarveMM ArrayRotate180, 0, 1, 1
'  Base Array:                     Flip:Vertical + Horizontal:
'  ------------------------        ------------------------
'  R1C1 R1C2 R1C3 R1C4 R1C5        R4C5 R4C4 R4C3 R4C2 R4C1
'  R2C1 R2C2 R2C3 R2C4 R2C5        R3C5 R3C4 R3C3 R3C2 R3C1
'  R3C1 R3C2 R3C3 R3C4 R3C5        R2C5 R2C4 R2C3 R2C2 R2C1
'  R4C1 R4C2 R4C3 R4C4 R4C5        R1C5 R1C4 R1C3 R1C2 R1C1
'  ////////////////////////        ////////////////////////
End Function
Function ArrayRotate270(ByVal srcArray As Variant)
  ArrayRotate270 = srcArray: ArrayCarveMM ArrayRotate270, 1, 0, 1
'  Base Array:                     Transpose + FlipHorizontal:
'  ------------------------        -------------------
'  R1C1 R1C2 R1C3 R1C4 R1C5        R1C5 R2C5 R3C5 R4C5
'  R2C1 R2C2 R2C3 R2C4 R2C5        R1C4 R2C4 R3C4 R4C4
'  R3C1 R3C2 R3C3 R3C4 R3C5        R1C3 R2C3 R3C3 R4C3
'  R4C1 R4C2 R4C3 R4C4 R4C5        R1C2 R2C2 R3C2 R4C2
'  ////////////////////////        R1C1 R2C1 R3C1 R4C1
'                                  ///////////////////
End Function


Function ArrayCarveMM(ByRef srcArray As Variant, _
                Optional ByVal transpose As Boolean = 0, _
                Optional ByVal FlipVertical As Boolean = False, _
                Optional ByVal FlipHorizontal As Boolean = False, _
                Optional ByVal indexCutFirstRows As Long = -1, _
                Optional ByVal indexCutFirstColumns As Long = -1, _
                Optional ByVal indexCutLastRows As Long = -1, _
                Optional ByVal indexCutLastColumns As Long = -1) As Boolean
  If Not IsArray(srcArray) Then Exit Function
  Dim p As LongPtr, pA As LongPtr, Pb As LongPtr, pSA As LongPtr, h As LongPtr, h2 As LongPtr
  Dim s As SAFEARRAY, bo As SAFEARRAYBOUND, a() As mmVar, b() As mmVar
  Dim r&, c&, j3&, l1&, l2&, u1&, u2&, i1%, i2%, IR&, IC&, tR&, tC&, F&(1), t&(1), y As Boolean
  pSA = VarPtr(srcArray) + PTR_SIZE: CopyMemory pSA, ByVal pSA, PTR_SIZE: CopyMemory s, ByVal pSA, LenB(s)
  If s.pvData = 0 Then Exit Function
  With s
    Select Case .cDims
    Case 2: With .rgsabound(2): l1 = .lLbound: u1 = .cElements + l1 - 1: End With
    End Select
    With .rgsabound(1): l2 = .lLbound: u2 = .cElements + l2 - 1: End With
  End With
  Select Case s.cDims
  Case 1
    h = (u2 - l2 + 1) * VAR_SIZE
    ReDim arr(l2 To u2, 1 To 1)
    p = VarPtr(srcArray(l2)): pA = VarPtr(arr(l2, 1))
    CopyMemory ByVal pA, ByVal p, h
    ZeroMemory ByVal p, h
    ZeroMemory ByVal pA, h
    srcArray = arr
  Case 2:
    h = (u1 - l1 + 1) * (u2 - l2 + 1) * VAR_SIZE

    ReDim a(l1 To u1, l2 To u2):
    p = VarPtr(srcArray(l1, l2)): pA = VarPtr(a(l1, l2)):
    CopyMemory ByVal pA, ByVal p, h
    F(0) = l1: F(1) = l2
    If indexCutFirstRows >= l1 And indexCutFirstRows < u1 Then IR = indexCutFirstRows + 1: y = True Else IR = l1
    If indexCutLastRows > IR And indexCutLastRows <= u1 Then tR = indexCutLastRows - 1: y = True Else tR = u1
    If indexCutFirstColumns >= l2 And indexCutFirstColumns < u2 Then IC = indexCutFirstColumns + 1: y = True Else IC = l2
    If indexCutLastColumns > IC And indexCutLastColumns <= u2 Then tC = indexCutLastColumns - 1: y = True Else tC = u2
    If transpose Then i1 = 1: y = True Else i2 = 1
    t(0) = tR - IR + l1: t(1) = tC - IC + l2
    h2 = (t(0) - F(0) + 1) * (t(1) - F(1) + 1) * VAR_SIZE
    ReDim b(F(i1) To t(i1), F(i2) To t(i2))
    If y Then
      With s.rgsabound(2): .lLbound = F(i1): .cElements = t(i1) - F(i1) + 1: End With
      With s.rgsabound(1): .lLbound = F(i2): .cElements = t(i2) - F(i2) + 1: End With
    End If
    Pb = VarPtr(b(F(i1), F(i2)))
    ' Sao chép nôòi dung cuÒa maÒng src sang a 1 và lýu maÒng ðaÞ chuyêÒn ðôÒi vào b 1
    If FlipVertical Then
      If FlipHorizontal Then
        For r = IR To tR: F(0) = tR + IR - r: For c = IC To tC: F(1) = tC + IC - c: b(F(i1), F(i2)) = a(r, c): Next c, r
      Else
        For r = IR To tR: F(0) = tR + IR - r: For c = IC To tC: F(1) = c - IC + 1: b(F(i1), F(i2)) = a(r, c): Next c, r
      End If
    Else
      If FlipHorizontal Then
        For r = IR To tR: F(0) = r - IR + 1: For c = IC To tC: F(1) = tC + IC - c: b(F(i1), F(i2)) = a(r, c): Next c, r
      Else
        For r = IR To tR: F(0) = r - IR + 1: For c = IC To tC: F(1) = c - IC + 1: b(F(i1), F(i2)) = a(r, c): Next c, r
      End If
    End If
    ' ghi các giá triò ðaÞ chuyêÒn ðôÒi trõÒ laòi maÒng src
    CopyMemory ByVal p, ByVal Pb, h2
'    ' thay ðôÒi kích thýõìc cuÒa maÒng src trong câìu trúc safearray
    If y Then CopyMemory ByVal pSA, ByVal VarPtr(s), LenB(s)
    ZeroMemory ByVal Pb, h2
  End Select
  ZeroMemory ByVal pA, h
  ArrayCarveMM = True
End Function
 
Lần chỉnh sửa cuối:
Bạn có thể thêm cho mình phần xắp xếp mảng (mà sử dụng netframework cao hơn 3.5) có được không?
Hiện mình đang dùng bản cũ, nhưng phải cài net frame work 3.5
 
Upvote 0
@anhdepjai Mình đang viết bài về VBA, không có đề cập liên quan đến NetFramework.
 
Upvote 0
1. @ tác giả bài #2: Microsoft có nền tảng tên là .NET Framework, đọc là Dot Net Framework.

2. @ thớt: ví dụ về chuỗi không vô tư và không công bằng. b() là mảng chuỗi trong khi v là chuỗi vô tư, tối thiểu cũng lợi hơn một con toán tính vị trí phần tử theo chỉ số.
Tôi không nói gì về tốc độ hàm được giới thiệu cả. Đây chỉ là code chứng minh chưa đầy đủ.
Khi tôi sửa v thành v() hoặc b() thành b thì kết quả ra khác hẳn.
Nếu bảo khác biệt xa giữa tốc độ thì tôi tạm đoán là do phép gán chứ không phải do biểu thức truy cập.

...

Bài viết dưới đây tận dụng hàm API RtlMoveMemory

JavaScript:
#If VBA7 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
#Else
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
#End If
Private Type mmStr
  s(1) As Byte
End Type

Ví dụ lặp qua từng ký tự chuỗi:
Đoạn mã dưới đây, có hai phương thức: 1 là sao chép bộ nhớ và 2 là sử dụng hàm MID để lặp qua 10 triệu ký tự.
Tốc độ là xử lý memory nhanh xấp xỉ gắp đôi hàm MID$ (có ký tự $ nhanh hơn)

JavaScript:
Private Sub SplitStringToCharArray_test()
  Dim a() As mmStr, s$, l&, b() As String, t1, t2, t3, v$
  s = String(10000000, "a"):
  t1 = Timer
  l = Len(s)
  ReDim a(1 To l)
  CopyMemory ByVal VarPtr(a(1)), ByVal StrPtr(s), l * 2
  For i = 1 To l
    v = a(i).s
  Next
  t2 = Timer
  ReDim b(1 To l)
  For i = 1 To l
    b(i) = Mid$(s, i, 1)
  Next
  Debug.Print "Mem: "; t2 - t1, "MID: " Timer - t2
End Sub
Giải thuật ở trên là sao chép lại chuỗi từ vùng nhớ gán vào Type có 2 byte bộ nhớ. Vì chuỗi cũng cần 2 byte để lưu trữ nên việc gán này sẽ tương thích.

Lưu ý: bộ nhớ từng kiểu biến là khác nhau, không thể tùy ý sao chép bộ nhớ. Sẽ làm sập tiến trình VBA và Excel.
...

[/CODE]
 
Upvote 0
Tôi ít quan tâm nhiều thứ nói nhanh, siêu nhanh hay cao cấp gì đó .. nhưng code két bất cứ ai viết không quan trọng .. quan trọng là phải thử xong mới có thể kết luận được. còn thử sao thì phải thử nhiều cách khác nhau

1/ Thử dữ liệu ít chạy ra kết quả = ok

2/ Thử nhiều chút với nhiều kiểu dữ liệu khác nhau có trong 1 bảng dữ liệu nếu ok thì thử cái mục thứ 3

3/ cho 1 bảng dữ liệu trong đó có nhiều kiểu dữ liệu khác nhau với số dòng là 1048570 dòng x 100 cột ( áp dụng cho Excel 2010 to 2021 )

Nếu mục trên không văng ra xem như sử dụng tốt ... còn văng ra thì xem xét lại hay phán xét gì tùy ý

4/ Nhanh, siêu nhanh, cao cấp hay siêu của các loại siêu cao cấp không quan trọng ... quan trọng là chạy có văng ra hay không đó mới là cái cần thử xong mới biết được ?!
 
Upvote 0
Code viết rườm rà, không chú thích, không giải thích giao diện thì ai hơi đâu thử. Và vì vậy, cái chuyện thử không cần tranh cãi. Mà có cần thì cũng chả cãi được.

Ai cũng biết C sử dụng con trỏ để đạt tốc độ khủng. Các hàm COM/OLE của Windows hầu hết viết bằng C hoặc C++ cho nên dùng giao diện con trỏ rất hiệu quả về tốc độ.
Lưu ý: C coi chuỗi là mảng có kiểu phần tử là ký tự. Thực ra ký tự trong C cũng là một loại số nguyên. Ở trên, thớt có bảo sử dụng bộ nhớ (memory) phải cẩn thận chính là do phương pháp định khoảng cách giữa phần tử đầu tiên và phần tử thứ i của mảng trong C (chỉ số).

Tuy nhiên, ở đây tôi đâu có nói về mấy cái hàm đó.

Chỉ là theo tôi biết thì phần lớn hàm chuỗi của VBA cũng sử dụng con trỏ bên trong cho nên viết hàm hiệu quả hơn chúng rất khó. Lướt qua, thấy thớt khẳng định rằng hàm của y vượt hẳn hàm MID$ cho nên tôi tò mò đọc qua code minh chứng. Cuối cùng thì đúc kết được kết luận rằng code minh chứng như vậy là ăn gian.

Trong hầu hết các ngôn ngữ lập trình, kiểu chuỗi (String) là Immutable (không thay trị được). Mõi lần thay đổi trị một biến đơn giản như Integer, VBA tìm đến địa chỉ ấy và nhét trị mới vào, địa chỉ vẫn giữ nguyên. Với String thì khác, vì String có thể thay đổi độ dài cho nên mỗi lần gán trị, VBA phải bỏ địa chỉ đương thời, tìm một chỗ mới để gán trị mới.
Để ép VBA giữ chỗ cũ, cách duy nhất là dùng Static string: chuỗi có độ dài cố định.
Ba lệnh ngoại lệ mà tôi biết là LSET, RSET và hàm MID đặt bên trái dấu gán (=)

Như vậy, code so sánh mà bỏ qua 4 yếu tố trên (static string, Lset, Rset, Mid) thì chưa đầy đủ.
 
Upvote 0
Bạn có thể viết thêm phần sắp xếp phần tử cho mảng có được không?
Mình muốn xin đoạn code để áp dụng vào các phần mình đã viết
Quan trọng là bạn đọc hiểu mã không, chứ tôi đã viết mã sắp xếp với giải thuật Memory. Mã tôi viết thì ít khi có chú thích.

Bạn có thể tham khảo thêm bài viết Sắp xếp tiếng Việt phân tầng, đa hàng đa cột, tôi cũng đã cập nhật giải thuật Memory.
Module chứa các API
JavaScript:
#If VBA7 = 0 Then
   Public Enum LongLong:[_]:End Enum
   Public Enum LongPtr:[_]:End Enum
#End If
Public Const nullptr As LongPtr = 0
Public Const vbLongPtr As Long = vbLong

#If Win64 = 0 Then
  Public Const vbLongLong As Long = 20
#End If

#If Win64 Then
Public Const PTR_SIZE& = 8
Public Const VAR_SIZE& = 24
#Else
Public Const PTR_SIZE& = 4
Public Const VAR_SIZE& = 16
#End If

Public Const STR_SIZE& = 4

#If VBA7 Then
  Public Declare PtrSafe Function VarPtrArray Lib "VBE7" Alias "VarPtr" (ByRef var() As Any) As LongPtr
#Else
  Public Declare Function VarPtrArray Lib "VBE6" Alias "VarPtr" (ByRef var() As Any) As Long
#End If

#If VBA7 Then

  Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
  Public Declare PtrSafe Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)
  Public Declare PtrSafe Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
  Public Declare PtrSafe Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As LongPtr)
#Else
  Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  Public Declare Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)
  Public Declare Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  Public Declare Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As Long)
#End If

Public Type mmVar
  b(1 To VAR_SIZE) As Byte
End Type
Public Type mmStr
  b(1) As Byte
End Type
Public Type SAFEARRAYBOUND
  cElements    As Long
  lLbound      As Long
End Type
Public Type SAFEARRAY
  cDims           As Integer
  fFeatures       As Integer
  cbElements      As Long
  cLocks          As Long
  pvData          As LongPtr
  rgsabound(1 To 2)  As SAFEARRAYBOUND
End Type

Class clsArrayQuickSort
JavaScript:
Option Explicit

Private Enum DATA_TYPE_RANK
  rkError = 1
  rkEmpty
  rkBoolean
  rkText
  rkDate
  rkNumber
End Enum

Private y As Boolean, nan As Boolean, sh As Boolean, mc As VbCompareMethod, vi As Boolean
Private cr%, ix&, vt&, v0, v1, v2, rk1&, rk2&, p&, vs&, charIndex$, arr
Private l1&, l2&, l0&, ii&, i1&, i2&, c1$, c2$, j%, sp1, sp2, lp1%, lp2%


Public Property Get Sort2D(ByVal srcArray, Optional indexSort&, Optional horizontal As Boolean, Optional matchCase As Boolean, Optional useTextNumberAsNumber As Boolean, Optional viName As Boolean, Optional transpose As Boolean)
  Dim ixs&(), r&, c&, l1&, l2&, u1&, u2&
  arr = srcArray: l1 = LBound(arr): u1 = UBound(arr): l2 = LBound(arr, 2): u2 = UBound(arr, 2)
  y = indexSort >= 0: nan = useTextNumberAsNumber: sh = horizontal: vi = viName: mc = IIf(matchCase, vbTextCompare, vbBinaryCompare)
  indexSort = Abs(indexSort)
  If sh Then
    ReDim ixs(1, u2 To l2): For r = l2 To u2: ixs(0, r) = r: v0 = arr(indexSort, r): GoSub r: Next
    QuickSort_Indexes ixs, indexSort, l2, u2
  Else
    ReDim ixs(1, l1 To u1): For r = l1 To u1: ixs(0, r) = r: v0 = arr(r, indexSort): GoSub r: Next
    QuickSort_Indexes ixs, indexSort, l1, u1
  End If
  GoSub s
  Sort2D = arr
  Erase arr
Exit Property
s:
  Dim p As LongPtr, pA As LongPtr, Pb As LongPtr, pSA As LongPtr, t As LongPtr
  Dim s As SAFEARRAY, a() As mmVar, b() As mmVar
  pSA = VarPtr(arr) + PTR_SIZE: CopyMemory pSA, ByVal pSA, PTR_SIZE: CopyMemory s, ByVal pSA, LenB(s)
  t = (u1 - l1 + 1) * (u2 - l2 + 1) * VAR_SIZE
  p = VarPtr(arr(l1, l2))
  ReDim a(l1 To u1, l2 To u2): pA = VarPtr(a(l1, l2)): CopyMemory ByVal pA, ByVal p, t
  If transpose Then
    ReDim b(l2 To u2, l1 To u1): Pb = VarPtr(b(l2, l1))
    If sh Then
      For r = l1 To u1: For c = l2 To u2: b(r, c) = a(ixs(0, c), r): Next c, r
    Else
      For r = l1 To u1: For c = l2 To u2: b(r, c) = a(c, ixs(0, r)): Next c, r
    End If
    CopyMemory ByVal p, ByVal Pb, t
    Dim bo As SAFEARRAYBOUND
    bo = s.rgsabound(1): s.rgsabound(1) = s.rgsabound(2): s.rgsabound(2) = bo
    CopyMemory ByVal pSA, s, LenB(s)
  Else
    ReDim b(l1 To u1, l2 To u2): Pb = VarPtr(b(l1, l2))
    If sh Then
      For r = l1 To u1: For c = l2 To u2: b(r, c) = a(r, ixs(0, c)): Next c, r
    Else
      For r = l1 To u1: For c = l2 To u2: b(r, c) = a(ixs(0, r), c): Next c, r
    End If
    CopyMemory ByVal p, ByVal Pb, t
  End If
  ZeroMemory ByVal pA, t
  ZeroMemory ByVal Pb, t
  Erase a: Erase b
Return
r:
  Select Case varType(v0)
  Case vbString: If v0 = Empty Then ixs(1, r) = rkEmpty Else ixs(1, r) = rkText: If nan Then If IsNumeric(v0) Then ixs(1, r) = rkNumber
  Case vbByte, vbInteger, vbLong, vbLongLong, vbCurrency, vbDecimal, vbDouble, vbSingle: ixs(1, r) = rkNumber
  Case vbDate: ixs(1, r) = rkDate
  Case vbEmpty: ixs(1, r) = rkEmpty
  Case vbError: ixs(1, r) = rkError
  Case vbBoolean: ixs(1, r) = rkBoolean
  End Select
Return
End Property

Private Sub QuickSort_Indexes(indexs() As Long, ByVal indexSort As Long, ByVal Low As Long, ByVal Hight As Long)
  If Low >= Hight Then Exit Sub
  Dim lo&, hi&: lo = Low: hi = Hight: p = (Low + Hight) \ 2: ix = indexs(0, p): vt = indexs(1, p)
  If Not sh Then
    v0 = arr(ix, indexSort)
    Do While lo <= hi
      Do While lo < Hight
        v1 = arr(indexs(0, lo), indexSort): v2 = v0: rk1 = indexs(1, lo): rk2 = vt: GoSub c1
        Select Case cr
        Case 1: If indexs(0, lo) >= ix Then Exit Do
        Case 2: Exit Do
        End Select
        lo = lo + 1
      Loop
      Do While hi > Low
        v1 = v0: v2 = arr(indexs(0, hi), indexSort): rk1 = vt: rk2 = indexs(1, hi): GoSub c1
        Select Case cr
        Case 1: If ix >= indexs(0, hi) Then Exit Do
        Case 2: Exit Do
        End Select
        hi = hi - 1
      Loop
      If lo <= hi Then vs = indexs(0, lo): indexs(0, lo) = indexs(0, hi): indexs(0, hi) = vs: vs = indexs(1, lo): indexs(1, lo) = indexs(1, hi): indexs(1, hi) = vs: lo = lo + 1: hi = hi - 1
    Loop
  Else
    v0 = arr(indexSort, ix)
    Do While lo <= hi
      Do While lo < Hight
        v1 = arr(indexSort, indexs(0, lo)): v2 = v0: rk1 = indexs(1, lo): rk2 = vt: GoSub c1
        Select Case cr
        Case 1: If indexs(0, lo) >= ix Then Exit Do
        Case 2: Exit Do
        End Select
        lo = lo + 1
      Loop
      Do While hi > Low
        v1 = v0: v2 = arr(indexSort, indexs(0, hi)): rk1 = vt: rk2 = indexs(1, hi): GoSub c1
        Select Case cr
        Case 1: If ix >= indexs(0, hi) Then Exit Do
        Case 2: Exit Do
        End Select
        hi = hi - 1
      Loop
      If lo <= hi Then vs = indexs(0, lo): indexs(0, lo) = indexs(0, hi): indexs(0, hi) = vs: vs = indexs(1, lo): indexs(1, lo) = indexs(1, hi): indexs(1, hi) = vs: lo = lo + 1: hi = hi - 1
    Loop
  End If
  QuickSort_Indexes indexs, indexSort, Low, hi
  QuickSort_Indexes indexs, indexSort, lo, Hight
Exit Sub
c1:
  cr = 0
  Select Case rk1
  Case Is < rk2: If (rk1 <= rkEmpty Or y) Then cr = 2
  Case Is > rk2: If Not (rk2 <= rkEmpty Or y) Then cr = 2
  Case Else
    Select Case rk1
    Case rkNumber: v1 = CDec(v1): v2 = CDec(v2): If v1 = v2 Then cr = 1 Else If (y Xor v1 < v2) Then cr = 2
    Case rkBoolean: If v1 = v2 Then cr = 1 Else If (y Xor v2) Then cr = 2
    Case rkText: If StrComp(v1, v2, mc) = 0 Then cr = 1: Return
      If Not vi Then
        l1 = Len(v1): l2 = Len(v2): l0 = IIf(l1 < l2, l1, l2)
        For ii = 1 To l0
          c1 = Mid$(v1, ii, 1): c2 = Mid$(v2, ii, 1): i1 = InStr(1, charIndex, c1, mc): i2 = InStr(1, charIndex, c2, mc)
          If i1 > 0 And i2 > 0 Then
            If i1 <> i2 Then cr = IIf(y Xor i1 < i2, 2, 0): Return
          Else
            i1 = StrComp(c1, c2, mc): If i1 <> 0 Then cr = IIf(y Xor i1 < 0, 2, 0): Return
          End If
        Next
        cr = IIf(l1 = l2, 1, IIf(y Xor l1 < l2, 2, 0))
      Else
        sp1 = Split(v1, " "): sp2 = Split(v2, " "): lp1 = UBound(sp1): lp2 = UBound(sp2)
        v1 = sp1(lp1): l1 = Len(v1): v2 = sp2(lp2): l2 = Len(v2)
        l0 = IIf(l1 < l2, l1, l2)
        For ii = 1 To l0
          c1 = Mid$(v1, ii, 1): c2 = Mid$(v2, ii, 1)
          i1 = InStr(1, charIndex, c1, mc): i2 = InStr(1, charIndex, c2, mc)
          If i1 > 0 And i2 > 0 Then
            If i1 <> i2 Then cr = IIf(y Xor i1 < i2, 2, 0): Return
          Else
            i1 = StrComp(c1, c2, mc): If i1 <> 0 Then cr = IIf(y Xor i1 < 0, 2, 0): Return
          End If
        Next

        If l1 <> l2 Then cr = IIf(y Xor l1 < l2, 2, 0): Return

        For j = 0 To lp1 - 1
          If j > lp2 - 1 Then cr = IIf(y, 2, 0): Return
          v1 = sp1(j): l1 = Len(v1): v2 = sp2(j): l2 = Len(v2): l0 = IIf(l1 < l2, l1, l2)
          For ii = 1 To l0
            c1 = Mid$(v1, ii, 1): c2 = Mid$(v2, ii, 1)
            i1 = InStr(1, charIndex, c1, mc): i2 = InStr(1, charIndex, c2, mc)
            If i1 > 0 And i2 > 0 Then
              If i1 <> i2 Then cr = IIf(y Xor i1 < i2, 2, 0): Return
            Else
              i1 = StrComp(c1, c2, mc): If i1 <> 0 Then cr = IIf(y Xor i1 < 0, 2, 0): Return
            End If
          Next
          If l1 <> l2 Then cr = IIf(y Xor l1 < l2, 2, 0): Return
        Next
        If lp1 < lp2 Xor y Then cr = 2
      End If
    Case rkError: i1 = StrComp(CStr(v1), CStr(v2), mc): If i1 = 0 Then cr = 1 Else If i1 <> 0 Then cr = IIf(y Xor i1 < 0, 2, 0): Return
    Case rkEmpty: cr = 1
    End Select
  End Select
Return
End Sub

Private Sub Class_Initialize()
  For Each v1 In Array(97, 65, 224, 192, 225, 193, 7843, 7842, 227, 195, 7841, 7840, 259, 258, 7855, 7854, 7857, 7856, 7859, 7858, 7861, 7860, 7863, 7862, _
      226, 194, 7845, 7844, 7847, 7846, 7849, 7848, 7851, 7850, 7853, 7852, 98, 66, 99, 67, 100, 68, 273, 272, 101, 69, 232, 200, _
      233, 201, 7867, 7866, 7869, 7868, 7865, 7864, 234, 202, 7871, 7870, 7873, 7872, 7875, 7874, 7877, 7876, 7879, 7878, _
      102, 70, 103, 71, 104, 72, 105, 73, 236, 204, 237, 205, 297, 296, 7881, 7880, 7883, 7882, 106, 74, 107, 75, 108, 76, 109, 77, 110, 78, _
      111, 79, 242, 210, 243, 211, 245, 213, 7887, 7886, 7885, 7884, 244, 212, 7889, 7888, 7891, 7890, 7893, 7892, 7895, 7894, 7897, 7896, _
      417, 416, 7899, 7898, 7901, 7900, 7903, 7902, 7905, 7904, 7907, 7906, 112, 80, 113, 81, 114, 82, 115, 83, 116, 84, 117, 85, _
      249, 217, 250, 218, 7911, 7910, 361, 360, 7909, 7908, 432, 431, 7913, 7912, 7915, 7914, 7917, 7916, 7919, 7918, 7921, 7920, 118, _
      86, 119, 87, 120, 88, 121, 89, 253, 221, 7923, 7922, 7927, 7926, 7929, 7928, 7925, 7924, 122, 90)
    charIndex = charIndex & ChrW$(v1)
  Next
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Quan trọng là bạn đọc hiểu mã không, chứ tôi đã viết mã sắp xếp với giải thuật Memory. Mã tôi viết thì ít khi có chú thích.

Bạn có thể tham khảo thêm bài viết Sắp xếp tiếng Việt phân tầng, đa hàng đa cột, tôi cũng đã cập nhật giải thuật Memory.
Module chứa các API
JavaScript:
#If VBA7 = 0 Then
   Public Enum LongLong:[_]:End Enum
   #If VBA6 = 0 Or Win64 = 0 Then
   Public Enum LongPtr:[_]:End Enum
   #End If
#End If
Public Const nullptr As LongPtr = 0
Public Const vbLongPtr As Long = vbLong

#If Win64 = 0 Then
  Public Const vbLongLong As Long = 20
#End If

#If Win64 Then
Public Const PTR_SIZE& = 8
Public Const VAR_SIZE& = 24
#Else
Public Const PTR_SIZE& = 4
Public Const VAR_SIZE& = 16
#End If

Public Const STR_SIZE& = 4

#If VBA7 Then
  Public Declare PtrSafe Function VarPtrArray Lib "VBE7" Alias "VarPtr" (ByRef var() As Any) As LongPtr
#ElseIf VBA6 <> 0 And Win64 <> 0 Then
  Public Declare PtrSafe Function VarPtrArray Lib "VBE6" Alias "VarPtr" (ByRef var() As Any) As LongPtr
#ElseIf VBA6 <> 0 And Win64 = 0 Then
  Public Declare PtrSafe Function VarPtrArray Lib "VBE6" Alias "VarPtr" (ByRef var() As Any) As Long
#Else
  Public Declare Function VarPtrArray Lib "VBA6" Alias "VarPtr" (ByRef var() As Any) As Long
#End If

#If VBA7 Then

  Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
  Public Declare PtrSafe Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)
  Public Declare PtrSafe Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
  Public Declare PtrSafe Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As LongPtr)
#Else
  Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  Public Declare Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)
  Public Declare Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  Public Declare Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As Long)
#End If

Public Type mmVar
  b(1 To VAR_SIZE) As Byte
End Type
Public Type mmStr
  b(1) As Byte
End Type
Public Type SAFEARRAYBOUND
  cElements    As Long
  lLbound      As Long
End Type
Public Type SAFEARRAY
  cDims           As Integer
  fFeatures       As Integer
  cbElements      As Long
  cLocks          As Long
  pvData          As LongPtr
  rgsabound(1 To 2)  As SAFEARRAYBOUND
End Type

Class clsArrayQuickSort
JavaScript:
Option Explicit

Private Enum DATA_TYPE_RANK
  rkError = 1
  rkEmpty
  rkBoolean
  rkText
  rkDate
  rkNumber
End Enum

Private y As Boolean, nan As Boolean, sh As Boolean, mc As VbCompareMethod, vi As Boolean
Private cr%, ix&, vt&, v0, v1, v2, rk1&, rk2&, p&, vs&, charIndex$, arr
Private l1&, l2&, l0&, ii&, i1&, i2&, c1$, c2$, j%, sp1, sp2, lp1%, lp2%


Public Property Get Sort2D(ByVal srcArray, Optional indexSort&, Optional horizontal As Boolean, Optional matchCase As Boolean, Optional useTextNumberAsNumber As Boolean, Optional viName As Boolean, Optional transpose As Boolean)
  Dim ixs&(), r&, c&, l1&, l2&, u1&, u2&
  arr = srcArray: l1 = LBound(arr): u1 = UBound(arr): l2 = LBound(arr, 2): u2 = UBound(arr, 2)
  y = indexSort >= 0: nan = useTextNumberAsNumber: sh = horizontal: vi = viName: mc = IIf(matchCase, vbTextCompare, vbBinaryCompare)
  indexSort = Abs(indexSort)
  If sh Then
    ReDim ixs(1, u2 To l2): For r = l2 To u2: ixs(0, r) = r: v0 = arr(indexSort, r): GoSub r: Next
    QuickSort_Indexes ixs, indexSort, l2, u2
  Else
    ReDim ixs(1, l1 To u1): For r = l1 To u1: ixs(0, r) = r: v0 = arr(r, indexSort): GoSub r: Next
    QuickSort_Indexes ixs, indexSort, l1, u1
  End If
  GoSub s
  Sort2D = arr
  Erase arr
Exit Property
s:
  Dim p As LongPtr, pA As LongPtr, Pb As LongPtr, pSA As LongPtr, t As LongPtr
  Dim s As SAFEARRAY, a() As mmVar, b() As mmVar
  pSA = VarPtr(arr) + PTR_SIZE: CopyMemory pSA, ByVal pSA, PTR_SIZE: CopyMemory s, ByVal pSA, LenB(s)
  t = (u1 - l1 + 1) * (u2 - l2 + 1) * VAR_SIZE
  p = VarPtr(arr(l1, l2))
  ReDim a(l1 To u1, l2 To u2): pA = VarPtr(a(l1, l2)): CopyMemory ByVal pA, ByVal p, t
  If transpose Then
    ReDim b(l2 To u2, l1 To u1): Pb = VarPtr(b(l2, l1))
    If sh Then
      For r = l1 To u1: For c = l2 To u2: b(r, c) = a(ixs(0, c), r): Next c, r
    Else
      For r = l1 To u1: For c = l2 To u2: b(r, c) = a(c, ixs(0, r)): Next c, r
    End If
    CopyMemory ByVal p, ByVal Pb, t
    Dim bo As SAFEARRAYBOUND
    bo = s.rgsabound(1): s.rgsabound(1) = s.rgsabound(2): s.rgsabound(2) = bo
    CopyMemory ByVal pSA, s, LenB(s)
  Else
    ReDim b(l1 To u1, l2 To u2): Pb = VarPtr(b(l1, l2))
    If sh Then
      For r = l1 To u1: For c = l2 To u2: b(r, c) = a(r, ixs(0, c)): Next c, r
    Else
      For r = l1 To u1: For c = l2 To u2: b(r, c) = a(ixs(0, r), c): Next c, r
    End If
    CopyMemory ByVal p, ByVal Pb, t
  End If
  ZeroMemory ByVal pA, t
  ZeroMemory ByVal Pb, t
  Erase a: Erase b
Return
r:
  Select Case varType(v0)
  Case vbString: If v0 = Empty Then ixs(1, r) = rkEmpty Else ixs(1, r) = rkText: If nan Then If IsNumeric(v0) Then ixs(1, r) = rkNumber
  Case vbByte, vbInteger, vbLong, vbLongLong, vbCurrency, vbDecimal, vbDouble, vbSingle: ixs(1, r) = rkNumber
  Case vbDate: ixs(1, r) = rkDate
  Case vbEmpty: ixs(1, r) = rkEmpty
  Case vbError: ixs(1, r) = rkError
  Case vbBoolean: ixs(1, r) = rkBoolean
  End Select
Return
End Property

Private Sub QuickSort_Indexes(indexs() As Long, ByVal indexSort As Long, ByVal Low As Long, ByVal Hight As Long)
  If Low >= Hight Then Exit Sub
  Dim lo&, hi&: lo = Low: hi = Hight: p = (Low + Hight) \ 2: ix = indexs(0, p): vt = indexs(1, p)
  If Not sh Then
    v0 = arr(ix, indexSort)
    Do While lo <= hi
      Do While lo < Hight
        v1 = arr(indexs(0, lo), indexSort): v2 = v0: rk1 = indexs(1, lo): rk2 = vt: GoSub c1
        Select Case cr
        Case 1: If indexs(0, lo) >= ix Then Exit Do
        Case 2: Exit Do
        End Select
        lo = lo + 1
      Loop
      Do While hi > Low
        v1 = v0: v2 = arr(indexs(0, hi), indexSort): rk1 = vt: rk2 = indexs(1, hi): GoSub c1
        Select Case cr
        Case 1: If ix >= indexs(0, hi) Then Exit Do
        Case 2: Exit Do
        End Select
        hi = hi - 1
      Loop
      If lo <= hi Then vs = indexs(0, lo): indexs(0, lo) = indexs(0, hi): indexs(0, hi) = vs: vs = indexs(1, lo): indexs(1, lo) = indexs(1, hi): indexs(1, hi) = vs: lo = lo + 1: hi = hi - 1
    Loop
  Else
    v0 = arr(indexSort, ix)
    Do While lo <= hi
      Do While lo < Hight
        v1 = arr(indexSort, indexs(0, lo)): v2 = v0: rk1 = indexs(1, lo): rk2 = vt: GoSub c1
        Select Case cr
        Case 1: If indexs(0, lo) >= ix Then Exit Do
        Case 2: Exit Do
        End Select
        lo = lo + 1
      Loop
      Do While hi > Low
        v1 = v0: v2 = arr(indexSort, indexs(0, hi)): rk1 = vt: rk2 = indexs(1, hi): GoSub c1
        Select Case cr
        Case 1: If ix >= indexs(0, hi) Then Exit Do
        Case 2: Exit Do
        End Select
        hi = hi - 1
      Loop
      If lo <= hi Then vs = indexs(0, lo): indexs(0, lo) = indexs(0, hi): indexs(0, hi) = vs: vs = indexs(1, lo): indexs(1, lo) = indexs(1, hi): indexs(1, hi) = vs: lo = lo + 1: hi = hi - 1
    Loop
  End If
  QuickSort_Indexes indexs, indexSort, Low, hi
  QuickSort_Indexes indexs, indexSort, lo, Hight
Exit Sub
c1:
  cr = 0
  Select Case rk1
  Case Is < rk2: If (rk1 <= rkEmpty Or y) Then cr = 2
  Case Is > rk2: If Not (rk2 <= rkEmpty Or y) Then cr = 2
  Case Else
    Select Case rk1
    Case rkNumber: v1 = CDec(v1): v2 = CDec(v2): If v1 = v2 Then cr = 1 Else If (y Xor v1 < v2) Then cr = 2
    Case rkBoolean: If v1 = v2 Then cr = 1 Else If (y Xor v2) Then cr = 2
    Case rkText: If StrComp(v1, v2, mc) = 0 Then cr = 1: Return
      If Not vi Then
        l1 = Len(v1): l2 = Len(v2): l0 = IIf(l1 < l2, l1, l2)
        For ii = 1 To l0
          c1 = Mid$(v1, ii, 1): c2 = Mid$(v2, ii, 1): i1 = InStr(1, charIndex, c1, mc): i2 = InStr(1, charIndex, c2, mc)
          If i1 > 0 And i2 > 0 Then
            If i1 <> i2 Then cr = IIf(y Xor i1 < i2, 2, 0): Return
          Else
            i1 = StrComp(c1, c2, mc): If i1 <> 0 Then cr = IIf(y Xor i1 < 0, 2, 0): Return
          End If
        Next
        cr = IIf(l1 = l2, 1, IIf(y Xor l1 < l2, 2, 0))
      Else
        sp1 = Split(v1, " "): sp2 = Split(v2, " "): lp1 = UBound(sp1): lp2 = UBound(sp2)
        v1 = sp1(lp1): l1 = Len(v1): v2 = sp2(lp2): l2 = Len(v2)
        l0 = IIf(l1 < l2, l1, l2)
        For ii = 1 To l0
          c1 = Mid$(v1, ii, 1): c2 = Mid$(v2, ii, 1)
          i1 = InStr(1, charIndex, c1, mc): i2 = InStr(1, charIndex, c2, mc)
          If i1 > 0 And i2 > 0 Then
            If i1 <> i2 Then cr = IIf(y Xor i1 < i2, 2, 0): Return
          Else
            i1 = StrComp(c1, c2, mc): If i1 <> 0 Then cr = IIf(y Xor i1 < 0, 2, 0): Return
          End If
        Next

        If l1 <> l2 Then cr = IIf(y Xor l1 < l2, 2, 0): Return

        For j = 0 To lp1 - 1
          If j > lp2 - 1 Then cr = IIf(y, 2, 0): Return
          v1 = sp1(j): l1 = Len(v1): v2 = sp2(j): l2 = Len(v2): l0 = IIf(l1 < l2, l1, l2)
          For ii = 1 To l0
            c1 = Mid$(v1, ii, 1): c2 = Mid$(v2, ii, 1)
            i1 = InStr(1, charIndex, c1, mc): i2 = InStr(1, charIndex, c2, mc)
            If i1 > 0 And i2 > 0 Then
              If i1 <> i2 Then cr = IIf(y Xor i1 < i2, 2, 0): Return
            Else
              i1 = StrComp(c1, c2, mc): If i1 <> 0 Then cr = IIf(y Xor i1 < 0, 2, 0): Return
            End If
          Next
          If l1 <> l2 Then cr = IIf(y Xor l1 < l2, 2, 0): Return
        Next
        If lp1 < lp2 Xor y Then cr = 2
      End If
    Case rkError: i1 = StrComp(CStr(v1), CStr(v2), mc): If i1 = 0 Then cr = 1 Else If i1 <> 0 Then cr = IIf(y Xor i1 < 0, 2, 0): Return
    Case rkEmpty: cr = 1
    End Select
  End Select
Return
End Sub

Private Sub Class_Initialize()
  For Each v1 In Array(97, 65, 224, 192, 225, 193, 7843, 7842, 227, 195, 7841, 7840, 259, 258, 7855, 7854, 7857, 7856, 7859, 7858, 7861, 7860, 7863, 7862, _
      226, 194, 7845, 7844, 7847, 7846, 7849, 7848, 7851, 7850, 7853, 7852, 98, 66, 99, 67, 100, 68, 273, 272, 101, 69, 232, 200, _
      233, 201, 7867, 7866, 7869, 7868, 7865, 7864, 234, 202, 7871, 7870, 7873, 7872, 7875, 7874, 7877, 7876, 7879, 7878, _
      102, 70, 103, 71, 104, 72, 105, 73, 236, 204, 237, 205, 297, 296, 7881, 7880, 7883, 7882, 106, 74, 107, 75, 108, 76, 109, 77, 110, 78, _
      111, 79, 242, 210, 243, 211, 245, 213, 7887, 7886, 7885, 7884, 244, 212, 7889, 7888, 7891, 7890, 7893, 7892, 7895, 7894, 7897, 7896, _
      417, 416, 7899, 7898, 7901, 7900, 7903, 7902, 7905, 7904, 7907, 7906, 112, 80, 113, 81, 114, 82, 115, 83, 116, 84, 117, 85, _
      249, 217, 250, 218, 7911, 7910, 361, 360, 7909, 7908, 432, 431, 7913, 7912, 7915, 7914, 7917, 7916, 7919, 7918, 7921, 7920, 118, _
      86, 119, 87, 120, 88, 121, 89, 253, 221, 7923, 7922, 7927, 7926, 7929, 7928, 7925, 7924, 122, 90)
    charIndex = charIndex & ChrW$(v1)
  Next
End Sub
Cảm ơn bạn, để tôi thử copy vào đoạn code của mình
 
Upvote 0
Web KT

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

Back
Top Bottom