在 VBA 中对多维数组进行排序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4873182/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 11:07:18  来源:igfitidea点击:

Sorting a multidimensionnal array in VBA

arraysvbasorting

提问by BlackLabrador

I have defined the following Array Dim myArray(10,5) as Longand would like to sort it. What would be the best method to do that?

我已经定义了以下数组Dim myArray(10,5) as Long并希望对其进行排序。什么是最好的方法来做到这一点?

I will need to handle a lot of data like a 1000 x 5 Matrix. It contains mainly numbers and dates and need to sort it according to a certain column

我需要处理大量数据,例如 1000 x 5 矩阵。主要包含数字和日期,需要按照某一列进行排序

回答by Nigel Heffernan

Here's a multi-column and a single-column QuickSort for VBA, modified from a code sample posted by Jim Rech on Usenet.

这是 VBA 的多列和单列 QuickSort,修改自 Jim Rech 在 Usenet 上发布的代码示例。

Notes:

笔记:

You'll notice that I do a lotmore defensive coding than you'll see in most of the code samples out there on the web: this is an Excel forum, and you've got to anticipate nulls and empty values... Or nested arrays and objects in arrays if your source array comes from (say) a third-party realtime market data source.

你会发现,我做了很多更具防御性的编码比你在大部分的代码示例看到那里的网站:这是一个Excel论坛,你必须事先估计到空和空值...或者如果您的源数组来自(例如)第三方实时市场数据源,则嵌套数组和数组中的对象。

Empty values and invalid items are sent to the end of the list.

空值和无效项目被发送到列表的末尾。

Your call will be:

您的电话将是:

 QuickSort MyArray,,,2
...将“2”作为列进行排序并排除通过搜索域的上限和下限的可选参数。

[EDITED] - fixed an odd formatting glitch in the <code> tags, which seem to have a problem with hyperlinks in code comments.

[已编辑] - 修复了 <code> 标签中一个奇怪的格式故障,这似乎在代码注释中存在超链接问题。

The Hyperlink I excised was Detecting an Array Variant in VBA.

我切除的超链接是Detecting an Array Variant in VBA

Public Sub QuickSortArray(ByRef SortArray As Variant, Optional lngMin As Long = -1, Optional lngMax As Long = -1, Optional lngColumn As Long = 0)
    On Error Resume Next

    'Sort a 2-Dimensional array

    ' SampleUsage: sort arrData by the contents of column 3
    '
    '   QuickSortArray arrData, , , 3

    '
    'Posted by Jim Rech 10/20/98 Excel.Programming

    'Modifications, Nigel Heffernan:

    '       ' Escape failed comparison with empty variant
    '       ' Defensive coding: check inputs

    Dim i As Long
    Dim j As Long
    Dim varMid As Variant
    Dim arrRowTemp As Variant
    Dim lngColTemp As Long

    If IsEmpty(SortArray) Then
        Exit Sub
    End If
    If InStr(TypeName(SortArray), "()") < 1 Then  'IsArray() is somewhat broken: Look for brackets in the type name
        Exit Sub
    End If
    If lngMin = -1 Then
        lngMin = LBound(SortArray, 1)
    End If
    If lngMax = -1 Then
        lngMax = UBound(SortArray, 1)
    End If
    If lngMin >= lngMax Then    ' no sorting required
        Exit Sub
    End If

    i = lngMin
    j = lngMax

    varMid = Empty
    varMid = SortArray((lngMin + lngMax) \ 2, lngColumn)

    ' We  send 'Empty' and invalid data items to the end of the list:
    If IsObject(varMid) Then  ' note that we don't check isObject(SortArray(n)) - varMid *might* pick up a valid default member or property
        i = lngMax
        j = lngMin
    ElseIf IsEmpty(varMid) Then
        i = lngMax
        j = lngMin
    ElseIf IsNull(varMid) Then
        i = lngMax
        j = lngMin
    ElseIf varMid = "" Then
        i = lngMax
        j = lngMin
    ElseIf VarType(varMid) = vbError Then
        i = lngMax
        j = lngMin
    ElseIf VarType(varMid) > 17 Then
        i = lngMax
        j = lngMin
    End If

    While i <= j
        While SortArray(i, lngColumn) < varMid And i < lngMax
            i = i + 1
        Wend
        While varMid < SortArray(j, lngColumn) And j > lngMin
            j = j - 1
        Wend

        If i <= j Then
            ' Swap the rows
            ReDim arrRowTemp(LBound(SortArray, 2) To UBound(SortArray, 2))
            For lngColTemp = LBound(SortArray, 2) To UBound(SortArray, 2)
                arrRowTemp(lngColTemp) = SortArray(i, lngColTemp)
                SortArray(i, lngColTemp) = SortArray(j, lngColTemp)
                SortArray(j, lngColTemp) = arrRowTemp(lngColTemp)
            Next lngColTemp
            Erase arrRowTemp

            i = i + 1
            j = j - 1
        End If
    Wend

    If (lngMin < j) Then Call QuickSortArray(SortArray, lngMin, j, lngColumn)
    If (i < lngMax) Then Call QuickSortArray(SortArray, i, lngMax, lngColumn)

End Sub

... And the single-column array version:

...和单列数组版本:

Public Sub QuickSortVector(ByRef SortArray As Variant, Optional lngMin As Long = -1, Optional lngMax As Long = -1)
    On Error Resume Next

    'Sort a 1-Dimensional array

    ' SampleUsage: sort arrData
    '
    '   QuickSortVector arrData

    '
    ' Originally posted by Jim Rech 10/20/98 Excel.Programming


    ' Modifications, Nigel Heffernan:
    '       ' Escape failed comparison with an empty variant in the array
    '       ' Defensive coding: check inputs

    Dim i As Long
    Dim j As Long
    Dim varMid As Variant
    Dim varX As Variant

    If IsEmpty(SortArray) Then
        Exit Sub
    End If
    If InStr(TypeName(SortArray), "()") < 1 Then  'IsArray() is somewhat broken: Look for brackets in the type name
        Exit Sub
    End If
    If lngMin = -1 Then
        lngMin = LBound(SortArray)
    End If
    If lngMax = -1 Then
        lngMax = UBound(SortArray)
    End If
    If lngMin >= lngMax Then    ' no sorting required
        Exit Sub
    End If

    i = lngMin
    j = lngMax

    varMid = Empty
    varMid = SortArray((lngMin + lngMax) \ 2)

    ' We  send 'Empty' and invalid data items to the end of the list:
    If IsObject(varMid) Then  ' note that we don't check isObject(SortArray(n)) - varMid *might* pick up a default member or property
        i = lngMax
        j = lngMin
    ElseIf IsEmpty(varMid) Then
        i = lngMax
        j = lngMin
    ElseIf IsNull(varMid) Then
        i = lngMax
        j = lngMin
    ElseIf varMid = "" Then
        i = lngMax
        j = lngMin
    ElseIf VarType(varMid) = vbError Then
        i = lngMax
        j = lngMin
    ElseIf VarType(varMid) > 17 Then
        i = lngMax
        j = lngMin
    End If

    While i <= j

        While SortArray(i) < varMid And i < lngMax
            i = i + 1
        Wend
        While varMid < SortArray(j) And j > lngMin
            j = j - 1
        Wend

        If i <= j Then
            ' Swap the item
            varX = SortArray(i)
            SortArray(i) = SortArray(j)
            SortArray(j) = varX

            i = i + 1
            j = j - 1
        End If

    Wend

    If (lngMin < j) Then Call QuickSortVector(SortArray, lngMin, j)
    If (i < lngMax) Then Call QuickSortVector(SortArray, i, lngMax)

End Sub

I used to use BubbleSort for this kind of thing, but it slows down, severely, after the array exceeds 1024 rows. I include the code below for your reference: please note that I haven't provided source code for ArrayDimensions, so this will not compile for you unless you refactor it - or split it out into 'Array' and 'vector' versions.

我曾经将 BubbleSort 用于这种事情,但在数组超过 1024 行后,它会严重变慢。我提供了以下代码供您参考:请注意,我没有提供 ArrayDimensions 的源代码,因此除非您对其进行重构或将其拆分为“Array”和“vector”版本,否则它不会为您编译。


Public Sub BubbleSort(ByRef InputArray, Optional SortColumn As Integer = 0, Optional Descending As Boolean = False)
' Sort a 1- or 2-Dimensional array.


Dim iFirstRow   As Integer
Dim iLastRow    As Integer
Dim iFirstCol   As Integer
Dim iLastCol    As Integer
Dim i           As Integer
Dim j           As Integer
Dim k           As Integer
Dim varTemp     As Variant
Dim OutputArray As Variant

Dim iDimensions As Integer



iDimensions = ArrayDimensions(InputArray)

    Select Case iDimensions
    Case 1

        iFirstRow = LBound(InputArray)
        iLastRow = UBound(InputArray)

        For i = iFirstRow To iLastRow - 1
            For j = i + 1 To iLastRow
                If InputArray(i) > InputArray(j) Then
                    varTemp = InputArray(j)
                    InputArray(j) = InputArray(i)
                    InputArray(i) = varTemp
                End If
            Next j
        Next i

    Case 2

        iFirstRow = LBound(InputArray, 1)
        iLastRow = UBound(InputArray, 1)

        iFirstCol = LBound(InputArray, 2)
        iLastCol = UBound(InputArray, 2)

        If SortColumn  InputArray(j, SortColumn) Then
                    For k = iFirstCol To iLastCol
                        varTemp = InputArray(j, k)
                        InputArray(j, k) = InputArray(i, k)
                        InputArray(i, k) = varTemp
                    Next k
                End If
            Next j
        Next i

    End Select


    If Descending Then

        OutputArray = InputArray

        For i = LBound(InputArray, 1) To UBound(InputArray, 1)

            k = 1 + UBound(InputArray, 1) - i
            For j = LBound(InputArray, 2) To UBound(InputArray, 2)
                InputArray(i, j) = OutputArray(k, j)
            Next j
        Next i

        Erase OutputArray

    End If


End Sub


This answer may have arrived a bit late to solve your problem when you needed to, but other people will pick it up when they Google for answers for similar problems.

当您需要时,这个答案可能来得有点晚,可以解决您的问题,但是其他人在 Google 搜索类似问题的答案时会选择它。

回答by Steve Jorgensen

The hard part is that VBA provides no straightforward way to swap rows in a 2D array. For each swap, you're going to have to loop over 5 elements and swap each one, which will be very inefficient.

困难的部分是 VBA 没有提供直接的方法来交换二维数组中的行。对于每次交换,您将不得不循环 5 个元素并交换每个元素,这将非常低效。

I'm guessing that a 2D array is really not what you should be using anyway though. Does each column have a specific meaning? If so, should you not be using an array of a user-defined type, or an array of objects that are instances of a class module? Even if the 5 columns don't have specific meanings, you could still do this, but define the UDT or class module to have just a single member that is a 5-element array.

我猜无论如何,二维数组确实不是您应该使用的。每列都有特定的含义吗?如果是这样,您不应该使用用户定义类型的数组或作为类模块实例的对象数组吗?即使这 5 列没有特定含义,您仍然可以这样做,但将 UDT 或类模块定义为只有一个 5 元素数组的成员。

For the sort algorithm itself, I would use a plain ol' Insertion Sort. 1000 items is actually not that big, and you probably won't notice the difference between an Insertion Sort and Quick Sort, so long as we've made sure that each swap will not be too slow. If you douse a Quick Sort, you'll need to code it carefully to make sure you won't run out of stack space, which can be done, but it's complicated, and Quick Sort is tricky enough already.

对于排序算法本身,我会使用普通的插入排序。1000 个项目实际上并没有那么大,您可能不会注意到插入排序和快速排序之间的区别,只要我们确保每次交换不会太慢。如果您确实使用快速排序,则需要对其进行仔细编码以确保不会耗尽堆栈空间,这是可以做到的,但它很复杂,而且快速排序已经足够棘手了。

So assuming you use an array of UDTs, and assuming the UDT contains variants named Field1 through Field5, and assuming we want to sort on Field2 (for example), then the code might look something like this...

因此,假设您使用一组 UDT,并假设 UDT 包含名为 Field1 到 Field5 的变体,并假设我们想要对 Field2 进行排序(例如),那么代码可能看起来像这样......

Type MyType
    Field1 As Variant
    Field2 As Variant
    Field3 As Variant
    Field4 As Variant
    Field5 As Variant
End Type

Sub SortMyDataByField2(ByRef Data() As MyType)
    Dim FirstIdx as Long, LastIdx as Long
    FirstIdx = LBound(Data)
    LastIdx = UBound(Data)

    Dim I as Long, J as Long, Temp As MyType
    For I=FirstIdx to LastIdx-1
        For J=I+1 to LastIdx
            If Data(I).Field2 > Data(J).Field2 Then
                Temp = Data(I)
                Data(I) = Data(J)
                Data(J) = Temp
            End If
        Next J
    Next I
End Sub

回答by swyx

sometimes the most brainless answer is the best answer.

有时候最无脑的答案就是最好的答案。

  1. add blank sheet
  2. download your array to that sheet
  3. add the sort fields
  4. apply the sort
  5. reupload the sheet data back to your array it will be the same dimension
  6. delete the sheet
  1. 添加空白表
  2. 将您的阵列下载到该工作表
  3. 添加排序字段
  4. 应用排序
  5. 将工作表数据重新上传回您的数组,它将具有相同的维度
  6. 删除工作表

tadaa. wont win you any programming prizes but it gets the job done fast.

塔达。不会为您赢得任何编程奖,但它可以快速完成工作。

回答by giveemheller

I'm going to offer up a slight bit of different code to Steve's approach.

我将为史蒂夫的方法提供一些不同的代码。

All valid points on efficiency, but to be frank.. when I was looking for a solution, I could have cared less about efficiency. Its VBA... I treat it like it deserves.

关于效率的所有有效点,但坦率地说..当我在寻找解决方案时,我本可以不那么关心效率。它的 VBA ......我像它应得的那样对待它。

You want to sort a 2-d array. Plain simple dirty simple insert sort that will accept a variable size array and sort on a selected column.

您想对二维数组进行排序。简单的简单脏简单插入排序,它将接受可变大小的数组并在选定的列上排序。

Sub sort_2d_array(ByRef arrayin As Variant, colid As Integer)
'theWidth = LBound(arrayin, 2) - UBound(arrayin, 2)
For i = LBound(arrayin, 1) To UBound(arrayin, 1)
    searchVar = arrayin(i, colid)
    For ii = LBound(arrayin, 1) To UBound(arrayin, 1)
        compareVar = arrayin(ii, colid)
        If (CInt(searchVar) > CInt(compareVar)) Then
            For jj = LBound(arrayin, 2) To UBound(arrayin, 2)
                larger1 = arrayin(i, jj)
                smaller1 = arrayin(ii, jj)
                arrayin(i, jj) = smaller1
                arrayin(ii, jj) = larger1
            Next jj
            i = LBound(arrayin, 1)
            searchVar = arrayin(i, colid)
        End If
        Next ii
    Next i
End Sub

回答by Dan

For what it's worth (I can't show code at this point...let me see if I can edit it to post), I created an array of custom objects (so each of the properties come with whichever element its sorted by), populated a set of cells with each elements object properties of interest then used the excel sort function through vba to sort the column. Im sure theres probably a more efficient way of sorting it, rather than exporting it to cells, I just havent figured it out yet. This actually helped me a lot because when I needed to add a dimension, I just added a let and get property for the next dimension of the array.

对于它的价值(此时我无法显示代码......让我看看我是否可以编辑它以发布),我创建了一个自定义对象数组(因此每个属性都带有它排序的任何元素) ,使用感兴趣的每个元素对象属性填充一组单元格,然后通过 vba 使用 excel 排序功能对列进行排序。我确定可能有一种更有效的排序方式,而不是将其导出到单元格,我只是还没有弄清楚。这实际上对我帮助很大,因为当我需要添加一个维度时,我只是为数组的下一个维度添加了一个 let 和 get 属性。

回答by BumKneesOhYeah

You could make a separate array with 2 columns. Column 1 would be what your sorting on and 2 is what row is in other array. Sort this array by column 1 (only switching the two columns when swap). Then you could use the 2 arrays to process data as needed. Huge arrays could give you memory problems though

您可以创建一个包含 2 列的单独数组。第 1 列是您的排序依据,第 2 列是其他数组中的行。按第 1 列对此数组进行排序(仅在交换时切换两列)。然后您可以根据需要使用这 2 个数组来处理数据。巨大的数组可能会给你带来内存问题

回答by Marcel

It seems to me that the QuickSort code above can not handle spaces. I have an array with numbers and spaces. When I sort this array, the records with spaces are mixed up between the records with numbers. It took me a lot of time to find out, so probably it is good to keep it in mind when you use this code.

在我看来,上面的 QuickSort 代码不能处理空格。我有一个包含数字和空格的数组。当我对这个数组进行排序时,带空格的记录在带数字的记录之间混杂在一起。我花了很多时间才弄明白,所以当你使用这段代码时,最好记住这一点。

best, Marcel

最好的,马塞尔